如何观察ng-content中的输入元素变化

时间:2016-04-23 01:14:46

标签: angular

当子组件观察到输入发生变化时,如何调用父组件的功能?

以下是HTML结构。

# app.comopnent.html
<form>
  <textbox>
    <input type="text">
  </textbox>
</form>

# textbox.component.html
<div class="textbox-wrapper">
  <ng-content>
</div>

限制如下。

  • TextboxComponent有ng-content,需要将input元素投射到它。
  • 当输入input元素时,在TextboxComponent中发出一个事件。
  • 不要让input元素拥有更多属性,例如<input type="text" (input)="event()">

我正在编写代码,但无法找到解决方案......

# input.directive.ts
@Directive({ selector: 'input', ... })
export class InputDirective {
  ngOnChanges(): void {
    // ngOnChanges() can observe only properties defined from @Input Decorator...
  }
}

# textbox.component.ts
@Component({ selector: 'textbox', ... })
export class TextboxComponent {
  @ContentChildren(InputDirective) inputs: QueryList<InputDirective>;
  ngAfterContentInit(): void {
    this.inputs.changes.subscribe((): void => {
      // QueryList has a changes property, it can observe changes when the component add/remove.
      // But cannot observe input changes...
    });
  }
}

5 个答案:

答案 0 :(得分:9)

input事件正在冒泡,可以在父组件上进行侦听

<div class="textbox-wrapper" (input)="inputChanged($event)">
  <ng-content></ng-content>
</div> 

Plunker example

答案 1 :(得分:5)

ngAfterViewInit()中,找到感兴趣的元素,然后命令性地添加事件监听器。下面的代码只假设一个输入:

@Component({
    selector: 'textbox',
    template: `<h3>textbox value: {{inputValue}}</h3>
      <div class="textbox-wrapper">
        <ng-content></ng-content>
      </div>`,
})
export class TextboxComp {
  inputValue:string;
  removeListenerFunc: Function;
  constructor(private _elRef:ElementRef, private _renderer:Renderer) {}
  ngAfterContentInit() {
    let inputElement = this._elRef.nativeElement.querySelector('input');
    this.removeListenerFunc = this._renderer.listen(
      inputElement, 'input', 
      event => this.inputValue = event.target.value)
  }
  ngOnDestroy() {
    this.removeListenerFunc();
  }
}

Plunker

与Günter的声明式方法相比,这个答案本质上是一种必要的方法。如果您有多个输入,这种方法可能更容易扩展。

似乎没有办法使用@ContentChild()(或@ContentChildren())在用户提供的模板中查找DOM元素(即ng-content内容)......像@ContentChild(input)似乎不存在。因此我使用querySelector()的原因。

在这篇博客文章http://angularjs.blogspot.co.at/2016/04/5-rookie-mistakes-to-avoid-with-angular.html中,Kara建议使用input选择器定义指令(比如InputItem),然后使用@ContentChildren(InputItem) inputs: QueryList<InputItem>;。然后我们不需要使用querySelector()。 但是,我并不特别喜欢这种方法,因为TextboxComponent的用户必须知道在directives数组中也包含InputItem(我想一些组件文档可以解决问题,但我仍然不是风扇)。这是这种方法的plunker

答案 2 :(得分:1)

您可以使用以下方案,它在输入指令

上具有hostbinding属性

input.directive.ts

import {Directive, HostBinding} from 'angular2/core';
import {Observer} from 'rxjs/Observer';
import {Observable} from 'rxjs/Observable';

@Directive({
  selector: 'input',
  host: {'(input)': 'onInput($event)'}
})
export class InputDirective {
  inputChange$: Observable<string>;
  private _observer: Observer<any>;
  constructor() {
    this.inputChange$ = new Observable(observer => this._observer = observer);
  }
  onInput(event) {
    this._observer.next(event.target.value);
  }
}

然后你的TextBoxComponent将订阅上面在InputDirective类中定义的Observable对象。

textbox.component.ts

import {Component, ContentChildren,QueryList} from 'angular2/core';
import {InputDirective} from './input.directive';
@Component({
  selector: 'textbox',
  template: `
    <div class="textbox-wrapper">
      <ng-content></ng-content>
      <div *ngFor="#change of changes">
        {{change}}
      </div>
    </div>
  `
})
export class TextboxComponent {
  private changes: Array<string> = [];
  @ContentChildren(InputDirective) inputs: QueryList<InputDirective>;

  onChange(value, index) {
    this.changes.push(`input${index}: ${value}`);
  }

  ngAfterContentInit(): void {
    this.inputs.toArray().forEach((input, index) => {
      input.inputChange$.subscribe(value => this.onChange(value, index + 1));
    });
  }
}

这是plunker sample

答案 3 :(得分:0)

您可以在输入元素上添加ngControl。

<form>
  <textbox>
    <input ngControl="test"/>
  </textbox>
</form>

这样您就可以在ContentChild中使用NgControl。它可以访问您可以注册的valueChanges属性,以获得更新通知。

@Component({ selector: 'textbox', ... })
export class TextboxComponent {
  @ContentChild(NgControl) input: NgControl;
  ngAfterContentInit(): void {
    this.input.control.valueChanges.subscribe((): void => {
      (...)
    });
  }
}

答案 4 :(得分:0)

您可以使用Angular CDK观察者https://material.angular.io/cdk/observers/api

在您的模块中导入此模块

import { ObserversModule } from '@angular/cdk/observers';

然后在ng-content's父元素

中使用
<div class="projected-content-wrapper (cdkObserveContent)="contentChanged()">
 <ng-content></ng-content>
</div>