ContentChild, ViewChild, template reference variables

In Angular, it is customary to write declarative code. This means that we should not manually request the entities we need. The framework has tools for working with template elements that will help us. We’ll talk about them today.

Who is who

To begin with, let’s figure out what is view and what is content.

view is the template of our component, the directives do not have it.

Content is what our component or directive wraps.

Components will need to add a tag ng-content into the template, otherwise all content will be replaced with the component’s template when rendering.

Performance Tip: even if ng-content is hidden behind *ngIf and is not attached to the document, it still renders and goes through all the change checking cycles. If you need lazy initialization – use ng-template.

ViewChild

When we create a component, we may need to access parts of its template. It can be obtained through the decorator @ViewChild. First you need to provide a selector – you can mark an element with a string:

<div #ref>...</div>

And then request it through a decorator: @ViewChild(‘ref’).

#ref called template reference variableand we will discuss them in detail below.

You can also use a class if you need to get a component or directive: @ViewChild(MyComponent). It can be a DI token: @ViewChild(MY_TOKEN).

Requests through decorators are processed within lifecycle hooks, ngOnInit, ngAfterViewInit and others. More about them in the next article.

The decorator’s second argument, the options object, is especially useful. The first key is simple – static: boolean. It tells Angular that an element always exists, not by condition:

  • static: true means that this element will be available in ngOnInit;

  • static: false means the element will only appear in ngAfterViewInitwhen the whole pattern is validated.

The default value is falsewhich means that the result will be obtained only after passing the first change check.

<div #static>
   Я статичный ребёнок
</div>
<div *ngIf="true" #dynamic>
   Я не статичный ребёнок
</div

I note that even static queries give results only in ngOnInitso technically some time the value is undefined. It’s good practice to mark this in types so you don’t accidentally refer to them in the constructor.

The second key is read. The first decorator argument tells Angular to “find me an injector that has this entity” and read says what to take from this injector. If we do not write it, then we will get the token itself from the first argument.

You can request any entity from the target injector: service, token, component, and so on. The most common use of this is to get the DOM element of the target component:

@ViewChild(MyComponent, { read: ElementRef })
readonly elementRef?: ElementRef<HTMLElement>

Template Reference Variables

Often in @ViewChild no need at all – in many cases it is perfect template reference variable and passing it to the event handler:

<input #input type="text" [(ngModel)]="model">
<button (click)="onClick(input)">Focus input</button>"
// Обратите внимание, что тут HTMLElement, а не ElementRef
onClick(element: HTMLElement) {
   element.focus();
}

You can consider the template reference variable as a kind of closure in the template – the reference is there, but it is available only where needed. This keeps the component code clean.

The Template reference variable refers to the component’s instance if placed on it, or to the DOM element if the component is not there. You can also get the essence of the directive, for this you use exportAs in decorator @Directive:

@Directive({
 selector: '[myDirective]',
 exportAs: 'someName',
})
export class MyDirective {}
<div myDirective #ref="someName">...</div>

ViewChildren

Sometimes you need to get many elements of the same type. In such a situation, you can use @ViewChildren – mark many elements in the template with the same line and get the entire collection.

All of the above applies here as well. static is not available for lists and the field type will be QueryList<T>. This allows you to loop through each element as needed. Consider an example of a tab component: instead of a horizontal scroll, we want to hide extra tabs in the “More” item. IN stackblitz below @ViewChildren used for implementation like this:

Content

Content can be a convenient way to customize components. In Angular, it resembles slots from native web components and allows you to project content to different parts of the template using the tag ng-content.

ng-content allows you to flexibly scatter parts of content to different places using an attribute select. Its syntax is similar to selector in directives and components – you can bind to tag names, classes, attributes and combine it in every possible way, up to negation through :not(). You can always leave ng-content without a selector so that all other content falls into it.

It’s important to remember that while the content is DOM-bound within a component’s view, it’s actually part of the parent view and follows its change-check cycles. This means that if an element in the content is marked for validation, for example, through the occurrence of an event from @HostListener, then the view of the content-wrapping component will not be checked. So if a component depends on content, make sure you don’t miss changes to it.

changes: Observable<void> from QueryList will help keep track of changes in the lists in the content.

ContentChild and ContentChildren

Content can be accessed in the same way as a component template. Angular has similar decorators @ContentChild And @ContentChildren with the same syntax.

Here is an example of a menu component where items are passed as content. This allows the developer to hook into events such as clicks or key presses without messing with the menu logic. The component itself is responsible for keyboard navigation.

Some of the syntax for content decorators differs from view. They have an additional option parameter descendants: boolean. It allows you to get children from content that is in the content of another nested component, but not in their view:

<!-- @ContentChild('child', { descendants: true }) -->
<my-component>
 <another-component>
   <div #child>...</div>
 </another-component>
</my-component>

With such features in Angular, you can achieve a lot. Practice in working with views and content will allow you to notice where this knowledge will help you create reliable, easily maintainable components!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *