Evgeniy Shevchenko
Evgeniy Shevchenko

Reputation: 3

Getting component inside component which is declared in HTML template

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

Answers (2)

Evgeniy Shevchenko
Evgeniy Shevchenko

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

Gr&#233;gory Elhaimer
Gr&#233;gory Elhaimer

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

Related Questions