Querying ContentChildren of Unknown Type

October 20th 2017 Angular Ionic 2

To reference DOM elements in the component template from the component code, ViewChild and ViewChildren decorators can be used:

import { Slides, Slide } from 'ionic-angular';
import { Component, QueryList, ViewChild, ViewChildren } from '@angular/core';

@Component({
  selector: 'my-component',
  templateUrl: 'my-component.html'
})
export class MyComponent {
  @ViewChild(Slides) slidesComponent: Slides;
  @ViewChildren(Slide) slideComponents: QueryList<Slide>
}

Similarly, ContentChild and ContentChildren decorators will provide access to DOM elements in the component content, i.e. those declared in the template that is using the component:

<my-component>
  <div>First</div>
  <div>Second</div>
  <div>Third</div>
</my-component>

All four decorators mentioned above support two types of selection criteria for matching the DOM elements:

  • by component type,
  • by template variable name.

With only these two options available, I had a hard time coming up with a way to use ContentChildren in a scenario where the consumer should be able to tag some of the DOM elements that need to be processed by the component, e.g. have an animation applied to them:

<my-component>
  <div>Tagged</div>
  <div>Not tagged</div>
  <div>
    <div>Not tagged</div>
    <div>Tagged</div>
  </div>
</my-component>

Of course, component type is not a suitable selection criterion in this case. For some reason, I was firmly convinced that template variable names had to be unique, which would make them unsuitable as well. Since I was running out of ideas, I decided to try it any way, although I couldn't find any reference that would confirm or deny my belief:

<my-component>
  <div #process>Tagged</div>
  <div>Not tagged</div>
  <div>
    <div>Not tagged</div>
    <div #process>Tagged</div>
  </div>
</my-component>

It worked exactly as I needed it to: Angular did not complain because of duplicate names and the QueryList returned references to both tagged elements:

import {
  Component,
  QueryList,
  ContentChildren,
  ElementRef,
  AfterContentInit
} from '@angular/core';

@Component({
  selector: 'my-component',
  templateUrl: 'my-component.html'
})
export class MyComponent implements AfterContentInit {
  @ContentChildren('process') elementsToProcess: QueryList<ElementRef>

  ngAfterContentInit(): void {
    this.elementsToProcess.forEach(element => {
      // process the element
    });
  }
}

Lesson learned: when you're not sure whether something will work, it might be worth try it out anyway before you start looking for alternative solutions.

Copyright
Creative Commons License