How to call parent component's function when child component observed input changes?
The below is HTML structure.
# app.comopnent.html
<form>
<textbox>
<input type="text">
</textbox>
</form>
# textbox.component.html
<div class="textbox-wrapper">
<ng-content>
</div>
Restrictions are like following.
ng-content
and need to project input
element to it.input
element is inputted something.input
element to have more attributes, e.g. <input type="text" (input)="event()">
.I was writing code, but cannot 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...
});
}
}
If you want to style the projected content within <ng-content>, you can do so using :host and ::ng-deep to apply styling to all nested elements within the <contact> component.
The ng-content is used when we want to insert the content dynamically inside the component that helps to increase component reusability. Using ng-content we can pass content inside the component selector and when angular parses that content that appears at the place of ng-content.
ng-content is an important concept to create reusable and flexible components. With the help of ng-content content can be projected by a parent component into a predefined slot. In some cases, you want to apply some styles to the projected content.
The input
event is bubbling and can be listened on the parent component
<div class="textbox-wrapper" (input)="inputChanged($event)">
<ng-content></ng-content>
</div>
Plunker example
You can use Angular CDK observers https://material.angular.io/cdk/observers/api
import this module in your module
import { ObserversModule } from '@angular/cdk/observers';
then use in ng-content's
parent element
<div class="projected-content-wrapper (cdkObserveContent)="contentChanged()">
<ng-content></ng-content>
</div>
In ngAfterViewInit()
, find the element(s) of interest, then imperatively add event listener(s). 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 Günter's declarative approach. This approach may be easier to extend if you have multiple inputs.
There doesn't seem to be a way to use @ContentChild()
(or @ContentChildren()
) to find DOM elements in the user-supplied template (i.e, the ng-content content)... something like @ContentChild(input)
doesn't seem to exist. Hence the reason 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 (say InputItem) with an input
selector and then using @ContentChildren(InputItem) inputs: QueryList<InputItem>;
. Then we don't need to use querySelector()
.
However, I don't particularly like this approach because the user of the TextboxComponent has to somehow know to also include InputItem in the directives
array (I guess some component documentation could solve the problem, but I'm still not a fan). Here's a plunker for this approach.
You could use the following scenario, wich has hostbinding property on 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 on Observable object that defined above in 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's plunker sample
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With