Following in the footsteps from @ContentChild
and @ContentChildren
, we need to utilize the power of a live list in order to configure multiple live queries for @ViewChild
. And we have just the right decorator. It is called @ViewChildren
.
@ViewChildren
We use @ViewChildren
to configure a view query. Remembering what we learned from @ContentChildren
, we would use a QueryList
to add, remove, update or modify elements in real-life. And to just double down on the QueryList
theoretical basics, we should know that QueryList
implements the iterable interface and is therefore filled with a lot of different and useful methods – some of which are similar to those we use in regular arrays such as map
, filter
, find
, reduce
, forEach
, and others. Another functionality that comes pre-included is the ability to subscribe to changes in the list using the changes
observable, which will emit the new value of the QueryList
on any change.
These view queries are only visible in the ngAfterViewInit
lifecycle hook.
@ViewChildren
supports the same kinds of selectors as the previously mentioned decorators. We can select any @Component
or @Directive
, or a template reference variable (using the hash character), or any provider defined in the child component tree of the current provider, any provider defined through a string token, or a TemplateRef
.
Using @ViewChildren
Let us duplicate our <app-id>
selector a couple of times inside user-card.component.html
.
<!-- only a section of the template --> <app-id></app-id> <app-id></app-id> <app-id></app-id> <!-- only a section of the template -->
In order to utilize the power of @ViewChildren
we also need to change a couple of things inside our component:
import { AfterViewInit, Component, OnInit, QueryList, ViewChildren, } from '@angular/core'; import { IdComponent } from '../id/id.component'; @Component({ selector: 'app-user-card', templateUrl: './user-card.component.html', styleUrls: ['./user-card.component.css'], }) export class UserCardComponent implements OnInit, AfterViewInit { @ViewChildren(IdComponent) idComponents: QueryList<IdComponent>; constructor() {} ngOnInit() {} ngAfterViewInit() { console.log(this.idComponents); } }
We basically reuse the same syntax from @ContentChildren
, only this time we use the @ViewChildren
decorator. The result of the console.log(this.idComponents);
is the QueryList type of object:
What we can do now is to dynamically loop through all of our view children and inject already defined properties from our IdComponent
.
ngAfterViewInit() { this.idComponents.forEach((idComponent: IdComponent, index: number) => { if (index % 2 === 0) { idComponent.backgroundColor = 'indigo'; idComponent.fontWeight = 'bold'; idComponent.foregroundColor = 'white'; } else { idComponent.backgroundColor = 'lightCoral'; idComponent.fontWeight = 'bold'; idComponent.foregroundColor = 'olive'; } }); }
We iterate through our view children and set different properties. The factor we depend on is the value of the index (even or odd). And we get the following result:
Wow, looks cool, right! It seems like we are done here… But wait! If we open the console, we are greeted with a pretty nasty error.
As you can see we are getting this cryptic error – ExpressionChangedAfterItHasBeenCheckedError
. What does it mean? First of all, this error only appears in development mode and won’t leak into the production environment. But, let us find a solution for this problem.
ExpressionChangedAfterItHasBeenChecked
Quite a mouthful, right? Well, to find the source of the error we should dig deep at how Angular detects changes. During change detection Angular performs different operations in a specific order, starting with checking and updating bound properties of components, running lifecycle hooks such as ngOnInit
, ngOnChanges
and ngDoCheck
, updating DOM
and so forth.
In development mode, Angular does an additional check to ensure bound values haven’t changed, and this can be a sign of unstabilized change detection. For more information, you can look up Angular’s view on the issue here.
And there are a couple of different solutions – from using a setTimeout()
method, to moving our code into a different lifecycle hook.
We will use the ChangeDetectorRef
in this particular case. ChangeDetectorRef is a class that provides change detector capabilities. It consists of a couple of useful methods, and we are especially interested in detectChanges()
, which checks the view and its changes and implements local detection strategies.
All we have to do is the following, and the error is gone!
ngAfterViewInit() { this.idComponents.forEach((idComponent: IdComponent, index: number) => { if (index % 2 === 0) { idComponent.backgroundColor = 'indigo'; idComponent.fontWeight = 'bold'; idComponent.foregroundColor = 'white'; } else { idComponent.backgroundColor = 'lightCoral'; idComponent.fontWeight = 'bold'; idComponent.foregroundColor = 'olive'; } }); this.cdref.detectChanges();
For the complete project, please look at the Stackblitz below:
Final Words
With @ViewChildren
we finish our Angular decorators mini-series. @ViewChildren can be used in order to get references to templated elements, or custom components, and to pull out, extract or change public properties of those elements. Keep tuned for the next Angular article.
For more articles please click below, or check the blog.