How to observe input element changes in ng content

How to call the function of the parent component when the observed changes are child components?

Below is the HTML structure.

# app.comopnent.html <form> <textbox> <input type="text"> </textbox> </form> # textbox.component.html <div class="textbox-wrapper"> <ng-content> </div> 

The restrictions are similar to the following.

  • TextboxComponent has ng-content and needs to project an input element on it.
  • Output an event in a TextboxComponent when something is being input .
  • You do not want the input element to have more attributes, for example. <input type="text" (input)="event()"> .

I wrote the code, but I can not find a solution ...

 # 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... }); } } 
+6
source share
5 answers

The input event is input and can be listened to on the parent component.

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

Plunger example

+5
source

In ngAfterViewInit() find the element (s) of interest, then be sure to add event listeners. The code below assumes only one input:

 @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

This answer is essentially an imperative approach, in contrast to Gunther's declarative approach. This approach might be easier to expand if you have multiple inputs.


It seems that there is no way to use @ContentChild() (or @ContentChildren() ) to search for DOM elements in a user template (i.e. ng content content) ... something like @ContentChild(input) does not seem to exist. Therefore, I use querySelector() .

In this blog post http://angularjs.blogspot.co.at/2016/04/5-rookie-mistakes-to-avoid-with-angular.html , Kara suggests defining a directive (e.g. InputItem) using the input selector and then use @ContentChildren(InputItem) inputs: QueryList<InputItem>; . Then we do not need to use querySelector() . However, I don't particularly like this approach, because the TextboxComponent user must somehow also know that the InputItem is in the directives array (I think some kind of component documentation might solve the problem, but I'm still not a fan) Here is the plunker for this approach.

+4
source

You can use the following script having the hostbinding property in the input directive

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

And then your TextBoxComponent will subscribe to the Observable object defined above in the InputDirective class.

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

Here is a plunker sample

+1
source

You can add ngControl to your input element.

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

That way you can use NgControl in ContentChild. It changes access to the valueChanges property, for which you can register to receive notification of updates.

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

You can use Angular CDK watchers https://material.angular.io/cdk/observers/api

import this module into your module

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

then use parent element in ng-content's

 <div class="projected-content-wrapper (cdkObserveContent)="contentChanged()"> <ng-content></ng-content> </div> 
0
source

All Articles