【问题标题】:How do I get ngModel of children in parent directive?如何在父指令中获取孩子的 ngModel?
【发布时间】:2019-11-11 10:22:47
【问题描述】:

我创建了一个指令并将其分配给一个元素,它的子元素是 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。

【问题讨论】:

标签: angular


【解决方案1】:

您可以使用 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>

要了解如何使用 select 实现功能,请参阅 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
    }
  }

你可以使用like,例如

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

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

Update 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】:

    尝试创建一个名为childModelChange() 的Output 属性。然后 Angular 会自动更新父属性。然后我们需要在孩子改变值时emit()一个事件:

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

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

    @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);
        }
    }
    

    你的父组件:

    @Component({
        selector: 'parent',
        template: `
            <div>Parent yourSharedVariableParent: {{yourSharedVariableParent}}</div>
            <yourChild [(yourSharedVariable)]="yourSharedVariableParent"></yourChild>
        `,
        directives: [yourChildComponent]
    })
    export class ParentComponent {
        yourSharedVariableParent ='hello';
        constructor() { console.clear(); }
    }
    

    【讨论】:

      【解决方案3】:

      谢谢大家的回复,

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

      喜欢

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

      目前在我的情况下只有一个孩子,所以我暂时以this.input.last.control.setValue(null); 访问 如果有多个 ngModel 怎么办?如何获得特定的 ngModel

      顺便说一句,我已经创建了 Material Select Stackblitz 的搜索,打开时我正在关注输入字段(搜索框)和关闭时清除其 ngModel

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

      【讨论】:

      • @Sammer Khan,我更新了我的答案,我不知道这是否可以帮助你
      • @Eliseo,我想我没有清楚地解释我的问题,抱歉。我正在使用搜索创建选择 [Something Like This] (cuppalabs.github.io/angular2-multiselect-dropdown/#/…) OnClosing select input 它清除搜索关键字,而不是添加按钮
      • 一个新的更新:)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-11-24
      • 2014-12-14
      • 2021-11-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多