Reputation: 3
I want to create filter component which will be used in different places with different number of inner components.
filter.component.html
<select-filter name="somename" ></select-filter>
<input-filter name="somename"></input-filter>
...
Select-filter and input filter are components which implement Interface FilterItem
export interface FilterItem{
name: string;
getValue() : any;
}
I want to get instance of each component( for example call getValue() ) inside filter.component.ts; What is the best way to do it?
Upvotes: 0
Views: 1145
Reputation: 3
The solution is next
1) Parent component gets child component's data using @ContentChildren or @ViewChildren with a param as QueryList. One important thing that the parameter is abstract class. We can't use interface there. Actually we can't use abstract class too, but we use providers in nested components. In my case it is
export class FilterComponent{
@ContentChildren(FilterItem) filterItems: QueryList<FilterItem>;
constructor() {}
getItems(){
console.log(this.filterItems);
this.filterItems.forEach( i => {
console.log( 'My name is ' + i.name + 'My value is ' + i.getValue());
});
}
}
2) Nested component should extend abstract class and declare this abstract class as a provider. In my case
@Component({
selector: 'app-string-filter-item',
templateUrl: './string-filter-item.component.html',
styleUrls: ['./string-filter-item.component.scss'],
providers: [{provide: FilterItem, useExisting: forwardRef(() => StringFilterItemComponent)}]
})
export class StringFilterItemComponent extends FilterItem {
selectValue: string;
@Input()
name:string;
caption: 'SHow me smt';
getValue(){
return this.selectValue;
}
}
string-filter-item.component.html
<p>
<input type="text" [(ngModel)]="selectValue">
</p>
filter.component.html
<div class="filter-wr">
<ng-content></ng-content>
</div>
Using filter component anywhere you want ( select string component is another component which I use )
<app-filter>
<app-select-filter-item name="first"></app-select-filter-item>
<app-string-filter-item name="second"></app-string-filter-item>
<app-select-filter-item name="third"></app-select-filter-item>
<app-string-filter-item name="fourth"></app-string-filter-item>
</app-filter>
That's all! Thanks for you attention!
Upvotes: 0
Reputation: 2801
It sounds like you want to create component which are form controls.
If I'm right, try using ControlValueAccessor
instead:
https://angular.io/api/forms/ControlValueAccessor
There are plenty of example about how to use them. Here is an example of implementation taken from https://blog.thoughtram.io/angular/2016/07/27/custom-form-controls-in-angular-2.html
export function createCounterRangeValidator(maxValue, minValue) {
return (c: FormControl) => {
let err = {
rangeError: {
given: c.value,
max: maxValue || 10,
min: minValue || 0
}
};
return (c.value > +maxValue || c.value < +minValue) ? err: null;
}
}
@Component({
selector: 'counter-input',
template: `
<button (click)="increase()">+</button> {{counterValue}} <button (click)="decrease()">-</button>
`,
providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CounterInputComponent), multi: true },
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => CounterInputComponent), multi: true }
]
})
export class CounterInputComponent implements ControlValueAccessor, OnChanges {
propagateChange:any = () => {};
validateFn:any = () => {};
@Input('counterValue') _counterValue = 0;
@Input() counterRangeMax;
@Input() counterRangeMin;
get counterValue() {
return this._counterValue;
}
set counterValue(val) {
this._counterValue = val;
this.propagateChange(val);
}
ngOnChanges(inputs) {
if (inputs.counterRangeMax || inputs.counterRangeMin) {
this.validateFn = createCounterRangeValidator(this.counterRangeMax, this.counterRangeMin);
this.propagateChange(this.counterValue);
}
}
writeValue(value) {
if (value) {
this.counterValue = value;
}
}
registerOnChange(fn) {
this.propagateChange = fn;
}
registerOnTouched() {}
increase() {
this.counterValue++;
}
decrease() {
this.counterValue--;
}
validate(c: FormControl) {
return this.validateFn(c);
}
}
Upvotes: 1