avatarAlexander Inkin

Summary

The provided context discusses Angular's ContentChild, ViewChild, and Template Reference features, which are essential for querying elements in templates, views, and content, and understanding the Angular framework's instruments for declarative code execution.

Abstract

Angular utilizes ContentChild and ViewChild decorators to allow developers to interact with elements in a declarative manner without manual queries. ViewChild is used for querying elements within a component's template, while ContentChild targets the content projected into the component. The distinction between view and content is significant in Angular: a view is the component's own template, and content is the external markup passed into the component, typically through the <ng-content> tag. The framework provides a way to lazily initialize content with the *ngIf directive and templates, to optimize performance by skipping unnecessary change detection cycles. Additionally, developers can use ViewChildren and ContentChildren to query multiple elements and manage them collectively. The context emphasizes that content children are part of the parent view's change detection cycle, and it provides strategies for handling content changes using QueryList observables. The article concludes with practical examples using ViewChild, ContentChild, and Template Reference variables, showcasing their use in Angular components such as tabs and dropdown menus.

Opinions

  • The author suggests that Angular's approach to querying elements via ContentChild, ViewChild, and Template Reference variables enables developers to write clean, maintainable code by leveraging the framework's declarative nature.
  • Performance is a concern when using ng-content, as the author notes that it is always rendered, even if it's not visible due to *ngIf. The recommendation is to use templates for lazy content initialization to avoid unnecessary change detection cycles.
  • The author indicates that ViewChild is more versatile with the optional second argument, the options object, which includes static and read properties. These properties allow developers to fine-tune the timing and target of the query.
  • Direct interaction with elements using Template Reference variables is encouraged for simple use cases as a cleaner alternative to using @ViewChild.
  • The article opines that understanding the Angular change detection mechanism, especially in relation to content children, is crucial for ensuring that changes to the content are properly managed.
  • The exportAs property in directives is highlighted as useful for exposing directive instances to templates for referencing.
  • Practical examples and demos are provided to illustrate the concepts discussed, demonstrating the author's view that real-world applications of these features lead to robust and maintainable components.

The magic of ContentChild, ViewChild and Template References

In Angular we tend to write declarative code. This means we are not supposed to manually query things when we need them. There are instruments in the framework that allow us to do so. First, lets recap what content and view mean.

View is template of your component. This means it doesn’t exist in directives.

Content is what your component (or directive) is wrapped around. For components you need to add ng‑content tag in your view to preserve content. Otherwise it is replaced with view upon render.

💡 Performance tip: even if ng‑content is hidden with *ngIf and detached from DOM, it is still rendered. And it still goes through change detection cycles. If you need lazy content initialization you need to use templates.

ViewChild and ViewChildren

When you create component, you might need access to some parts of its template. @ViewChild decorator allows you to do so and more. First, you need to provide selector. You can mark item with a string in the template: <div #ref>...</div>. And then query it with a decorator: @ViewChild('ref')

This is a template reference variable and we will discuss it in detail later.

It could also be class if you aim at a component or a directive: @ViewChild(MyComponent)

It can even be an injection token: @ViewChild(MY_TOKEN)

Real strength though comes with the second argument — an options object. First option is trivial: static: boolean. It tells Angular whether some child always exists or present with a condition. static: true means it can be queried in ngOnInit. static: false means it is not available until ngAfterViewInit. Default value is false.

<div #static>
    I'm a static child
</div>
<div *ngIf="true" #dynamic>
    I'm not a static child
</div>

Note that even static queries are resolved only in ngOnInit. So technically they are undefined for some time. It is a good practice to reflect that with proper type so you do not try to accidentally access it in a constructor. Hence a question mark in type in examples above.

Second option is read. First argument tells Angular where to look and read tells it what to look for. We can get everything from target injector - be it a service, a token or a component instance.

By default, @ViewChild retrieves a component when we query it. The most common use for read option is to get ElementRef instead:

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

Quite often though you don’t need @ViewChild at all. In a lot of cases you can just use template reference variable and pass it straight to the callback:

<input #input type="text" [(ngModel)]="model">
<button (click)="onClick(input)">Focus input</button>"
// Notice that it's HTMLElement not ElementRef here in this case
onClick(element: HTMLElement) {
    element.focus();
}

You can think of template reference variables as kind of closures in template in this case. The reference is there, but it only exists in a closed circuit where it is needed. It helps you keep component’s code clean.

Template reference variable targets component when it’s on one and an HTML element when it’s not. It can also target directives, that’s what exportAs is for in @Directive decorator:

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

Sometimes you need to query multiple items of the same kind. In this case you can use @ViewChildren. You can mark many items in template with the same reference and retrieve a whole collection. All advices above are applicable here too, except the type would be QueryList<T> and static is not available for lists. This allows you to go over every item when you need to. Consider an example with tabs component. Instead of horizontal scroll you want to hide extra tabs into "More" button. See the demo below utilizing @ViewChildren to do the job:

ContentChild and ContentChildren

A handy way to customize component is to use content. In Angular, similar to native Web Components, you can wrap your component around some DOM elements and project them into component view. This is what built-in tag ng‑content is for. And while in most cases you will just project all content in one place, you can define what content goes where. ng‑content has an optional attribute select. It is a lot like selector for directives or components and supports the same syntax. This means you can hook to tag names, classes, attributes and combine them any way you like. You can always leave single ng‑content without selector. Everything that did not match slots with selectors will go there.

An important thing to keep in mind is this: even though content seems to appear in your component view it is actually part of the parent view and follows its change detection cycle. It means that if content element gets marked for checking due to subscribed event for example, your component view will not be checked. Because technically it is parallel to content and this is not how change detection propagates in Angular. Since your component might be dependent on its content children you need to make sure changes to content will not go unnoticed.

changes: Observable<void> from QueryList is a handy way to keep your component up-to-date with content changes.

You can query content the same way you query view. Angular has @ContentChild and @ContentChildren decorators for this purpose. The syntax isthe same as with their view counterparts.

Below you can find an example of a dropdown menu where items are passed as content to a wrapping component. This allows you to hook into events like clicks or key presses, sparing component this logic. And component can implement navigation logic.

One thing in syntax that differs is @ContentChild also has descendants: boolean to be able to query children nested inside other components (but not from their views):

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

This wraps it up for this Angular feature. There’s quite a lot you can do with it, make sure to practice to be able to spot where it can help you design robust, maintainable components!

Angular
Typescript
JavaScript
Frontend
HTML
Recommended from ReadMedium