如何在父指令中获取子项的ngModel?

时间:2019-06-30 10:41:37

标签: angular

我创建了一个指令并将其分配给一个元素,它的子元素是ngModel的输入,我想从父指令中重置子ngModel

我已经尝试过

HTML :-


<div wrapper [childModel]="childElm">
  <input type="text" [ngModel]="name" #childElm>
</div>

TS :-

包装器指令

@Input(childModel) childModel;

someFunc() {
   this.childModel.control.setValue(null);
}

还有其他方法,而无需使用@Input。因为以这种方式,我必须创建本地模板变量并将其分配给@Input。

3 个答案:

答案 0 :(得分:0)

尝试创建一个名为Output的{​​{1}}属性。然后,Angular将自动更新父属性。然后,只要孩子更改值,我们就需要childModelChange()事件:

您可以设置从子级到父级的事件发射器通信(输出)。例如这样的

从“ angular2 / core”导入{Component,EventEmitter,Input,Output}

emit()

您的父组件:

@Component({
    selector: 'yourChild',
    template: `
        <p>yourChild yourSharedVariable: {{yourSharedVariable}}</p>
        <input [ngModel]="yourSharedVariable" (ngModelChange)="change($event)">
    `
})
export class yourChildComponent {
    @Input() yourSharedVariable: string;
    @Output() yourSharedVariableChange = new EventEmitter();
    change(newValue) {
      console.log('newvalue', newValue)
      this.yourSharedVariable = newValue;
      this.yourSharedVariableChange.emit(newValue);
    }
}

答案 1 :(得分:0)

您可以使用ContentChild或ContentChildren。当您要查找NgModel时,您的指令将变为

  @ContentChild(NgControl, { static: true }) input

  someFunc() {
    this.input.control.setValue(null);
  }

(NgControl)是任何带有[ngModel]的输入或带有[formControl]的输入,如果我们正在使用ReactiveForm,则是formControlName

我们可以有一个类似app.component的

<div  hello >
  <input [ngModel]="value"/>
</div>
<button (click)="do()">clear</button>

export class AppComponent  {
  @ViewChild(HelloDirective,{static:true}) wrapper
  do()
  {
    this.wrapper.someFunc()
  }
}

请参见stackblitz

注意:真正要清除输入不是必须创建指令,但是我想这是更复杂的一部分

已更新,当我们想在Angular Material中使用“清除”按钮时,docs的第4个例子将是一个机会

<mat-form-field class="example-form-field">
  <input matInput type="text" placeholder="Clearable input" [(ngModel)]="value">
  <button mat-button *ngIf="value" matSuffix mat-icon-button aria-label="Clear" (click)="value=''">
    <mat-icon>close</mat-icon>
  </button>
</mat-form-field>

要了解如何通过选择功能发挥作用,请参见stackoverflow question and answer

但是问题是,我们可以制定一条指令来实现类似的目标吗? (答案是正确的)。但是我们的指令必须使用Renderer2,并且-我还没有找到其他方法-用.css创建十字架。

.css就像

.close {
  position: absolute;
  right: -1rem;
  top: -2.5rem;
  width: .8rem;
  height: .8rem;
  opacity: 0.7;
}
.close:hover {
  opacity: 1;
}
.close:before, .close:after {
  position: absolute;
  left: .4rem;
  content: ' ';
  height: .7rem;
  width: 1px;
  background-color: #333;
}
.close:before {
  transform: rotate(45deg);
}
.close:after {
  transform: rotate(-45deg);
}

指令变为(请参见注释以获取简要说明)

@Directive({
  selector: '[clear]',
  host: {  //we need an "extra" space to the rigth
    '[style.margin-right]': '"1.5rem"',
  }
})
export class ClearDirective implements OnInit {

  div: any;
  @ContentChild(NgControl) control; //we get the "input"

  @HostListener('click', ['$event']) click($event) {
    //we add a HostListener "click" and take account is we click
    //in the "cross" we created, is a span with class="close"
    if ($event.target.getAttribute('class') == 'close') {
      this.control.control.setValue(null);  //remember, the control is the NgControl
      $event.stopPropagation();
    }
  }
  constructor(private renderer: Renderer2, private el: ElementRef) { }
  ngOnInit() {
    //we need take account about when the value of the control is "something"
    //or none
    if (this.control)
      this.control.valueChanges.subscribe((value) => {
        if (!value) {  //if no value
          this.clearCross()  //remove the cross
        }
        else {
          if (!this.div) {  //if not yet the cross
            this.createCross()  //create
          }
        }
      })

  }
  createCross() {
    //we wan create some like
    /*
       <div class="mat-form-field-suffix">
         <span class="close"></span>
       </div>
    */
    this.div = this.renderer.createElement('div');
    this.renderer.addClass(this.div, "mat-form-field-suffix")

    const span = this.renderer.createElement('span')
    this.renderer.addClass(span, "close")

    this.renderer.appendChild(this.div, span);
    this.renderer.appendChild(this.el.nativeElement, this.div);
  }
  clearCross() {
    if (this.div)  //simply remove the "cross"
    {
      this.renderer.removeChild(this.el.nativeElement, this.div)
      this.div=null
    }
  }

例如,您可以使用

<mat-form-field clear>
  <input matInput type="text" placeholder="Clearable input" [(ngModel)]="value">
</mat-form-field>

好吧,完整示例(基于@SammerKant的分叉堆栈闪电)是here

更新2 ,好吧,我不喜欢的是该指令超出了输入范围。我真的不喜欢因此,以相同的观点,更改css来调整位置,新指令位于this stackblitz

export class ClearDirective implements OnInit {

  div: any;
  constructor(@Optional() private control:NgControl,private renderer: Renderer2, private el: ElementRef) { }
  ngOnInit() {
    if (this.control)
      this.control.valueChanges.subscribe((value) => {
        if (!value) {
          this.clearCross()
        }
        else {
          if (!this.div) {
            this.createCross()
          }
        }
      })

  }
  createCross() {
    this.div = this.renderer.createElement('div');
    this.renderer.addClass(this.div, "wrapper")

    const span = this.renderer.createElement('span')
    this.renderer.addClass(span, "close")

    this.renderer.appendChild(this.div, span);
    this.renderer.insertBefore(this.renderer.parentNode(this.el.nativeElement),this.div,this.el.nativeElement,);
    this.renderer.listen(this.div, 'click', ($event) => {
           this.control.control.setValue(null);
           $event.stopPropagation();
       });
  }
  clearCross() {
    if (this.div)
    {
      this.renderer.removeChild(this.renderer.parentNode(this.el.nativeElement), this.div)
      this.div=null
    }
  }
}

答案 2 :(得分:0)

谢谢大家的答复,

这可以在@ContentChildren上使用,因为@ContentChild总是给出第一个/父ngModel,但是我需要一个特定的ngModel,所以我使用了@ContentChildren

喜欢

@ContentChildren(NgControl) input;
this.input.last.control.setValue(null);

就我而言,目前只有一个孩子,所以暂时我以this.input.last.control.setValue(null);的身份访问 如果有多个ngModel,怎么办?如何获取特定的ngModel

顺便说一句,我已经创建了对材料选择Stackblitz的搜索,在打开时,我将焦点放在输入字段(搜索框)上,并在关闭时,清除其ngModel

注意:我不想创建组件,因为这会增加大量的代码和逻辑