avatarAlexander Inkin

Summary

The web content discusses a method for handling dynamic content children in Angular's OnPush components, specifically within a tabs component scenario.

Abstract

The article addresses a challenge faced by developers when working with Angular's OnPush change detection strategy in components that have content children, such as a tabs component. The author explains that when the list of tabs needs to be dynamically updated, such as adding a "Profile" tab upon user login, the parent view's lifecycle does not trigger an update in the OnPush component's view. To resolve this, the author suggests subscribing to the changes Observable property of QueryList to manually trigger change detection in the wrapper component. A solution is provided, which involves using an *ngIf directive bound to tabs.changes | async in the template to ensure the view is updated when the content changes.

Opinions

  • The author, Alex, positions the discussed trick as an "easy" solution to a common problem in Angular development.
  • Alex emphasizes the importance of giving users full control over tabs within a tabs component, suggesting that this level of customization is a desirable feature.
  • The use of @ContentChildren and templates is presented as a flexible approach to arranging tabs dynamically, including scenarios where tabs might be hidden or placed within a "More" dropdown.
  • The article implies that developers might encounter "expression changed" errors when using @HostBinding on getters, highlighting a potential pitfall in Angular development.
  • By sharing the solution on Twitter and subsequently in the article, Alex demonstrates a commitment to community engagement and knowledge sharing within the Angular development community.
  • The author's credentials as a Lead Angular developer, co-creator of Taiga UI, and Google Developer Expert lend credibility to the advice given in the article.

A simple trick you need for dynamic ContentChildren

Developing OnPush components that operate with content children might be tricky. Let’s consider the following example. We have a tabs component and we want to give user full control of those tabs. So we make a wrapping component and allow defining tabs in its content. We also want to be able to arrange them differently. Maybe we have a separator template, or we hide extra tabs into «More» dropdown.

Tabs hiding extra items from Taiga UI

To do so we must use templates or structural directives. We access them with @ContentChildren query inside our wrapper. Then we instantiate templates the way we need. But what if the list of tabs can change? Take a look at the example below. Logging in adds another tab — «Profile». Try hitting that button and see what happens:

The issue stems from the fact that content belongs to the parent view lifecycle. Even though it is inside our wrapper component and it changed — OnPush change detection of the wrapper component will not update the view.

@HostBinding also belongs to the parent view, which can cause expression changed errors when used on getters!

Fortunately, there’s an easy trick that can solve this issue. QueryList has an Observable property changes. We can subscribe to it and kick change detection in our wrapper component. I’ve posted the easiest way to do it on Twitter with a question “why would I do something like that?” 🙂

This article is an answer. Adding this line to tabs.template.html fixes the issue:

<ng-container *ngIf="tabs.changes | async"></ng-container>

Hope this little trick will help you too!

About me

👋 I’m Alex, Lead Angular developer, co-creator of Taiga UI components library, Google Developer Expert, open source author, tech writer and speaker. If you are looking for Angular consulting — you can book time with me on superpeer 🤙

Angular
Ng Container
Ng Content
Ng Template
Contentchildren
Recommended from ReadMedium