Skip to main content

ViewChildren Decorator

The @ViewChildren decorator in Angular is a query that allows you to access a list of elements or directives from the view DOM. Unlike @ViewChild, which only gives you access to a single child element or directive, @ViewChildren grants access to all child elements or directives that match a given selector. This is particularly useful when you need to interact with multiple child components or elements of the same type.

How to Use @ViewChildren

  1. Importing ViewChildren: First, import ViewChildren from @angular/core.

    import { Component, ViewChildren, QueryList } from '@angular/core';
  2. Applying ViewChildren in Component Class: Use @ViewChildren to bind a property to a list of elements or directives.

    @Component({/* ... */})
    export class AppComponent {
    @ViewChildren(ChildDirective) children: QueryList<ChildDirective>;
    }
  3. Accessing the Elements or Directives: @ViewChildren provides a QueryList of elements or directives. QueryList is a live list, meaning it's automatically updated when the state of the application changes.

    ngAfterViewInit() {
    this.children.forEach(child => /* ... */);
    }
  4. Using with Template Reference Variables: You can also use @ViewChildren with template reference variables.

    <div #myDiv>...</div>
    <div #myDiv>...</div>
    @ViewChildren('myDiv') divs: QueryList<ElementRef>;

Use Cases for @ViewChildren

  • Performing Bulk Operations: If you need to perform an operation on all instances of a component or all elements with a specific directive, @ViewChildren lets you iterate over them.
  • Dynamic Content: When dealing with dynamic content where the number of child components or directives isn't fixed, @ViewChildren provides a way to access these dynamic elements.
  • Accessing Native Elements: Similar to @ViewChild, @ViewChildren can be used to access and manipulate native DOM elements, but for multiple elements.
  • Interaction Among Child Components: Useful for scenarios where child components need to communicate or share data with each other.

Example 1: Interacting with Multiple Form Inputs

If you have a form with multiple input fields and you need to perform a bulk operation, such as resetting all fields:

Parent Component Template:

<form>
<input #inputRef type="text" name="input1">
<input #inputRef type="text" name="input2">
<input #inputRef type="text" name="input3">
<button (click)="resetInputs()">Reset</button>
</form>

Parent Component Class:

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

@Component({/* ... */})
export class FormComponent {
@ViewChildren('inputRef') inputs: QueryList<ElementRef>;

resetInputs() {
this.inputs.forEach(input => input.nativeElement.value = '');
}
}

In this example, clicking the "Reset" button will clear all the text inputs.

Example 2: Managing a Collection of Child Components

If you have a set of collapsible panels and want to implement an "Expand All/Collapse All" feature:

Panel Component (panel.component.ts):

@Component({
selector: 'app-panel',
template: `<div [class.collapsed]="collapsed">Panel Content</div>`
})
export class PanelComponent {
collapsed = true;

toggle() {
this.collapsed = !this.collapsed;
}
}

Accordion Component (accordion.component.ts):

@Component({
selector: 'app-accordion',
template: `
<app-panel></app-panel>
<app-panel></app-panel>
<app-panel></app-panel>
<button (click)="toggleAll()">Toggle All</button>
`
})
export class AccordionComponent {
@ViewChildren(PanelComponent) panels: QueryList<PanelComponent>;

toggleAll() {
this.panels.forEach(panel => panel.toggle());
}
}

Clicking "Toggle All" will expand or collapse all panels.

Example 3: Dynamic Querying with @ViewChildren

Using @ViewChildren to query elements dynamically based on some condition:

Parent Component Template:

<div *ngFor="let item of items; let i = index">
<div #dynamicDiv *ngIf="item.isActive">{{ item.content }}</div>
</div>

Parent Component Class:

import { Component, ViewChildren, QueryList, ElementRef, AfterViewInit } from '@angular/core';

@Component({/* ... */})
export class DynamicComponent implements AfterViewInit {
@ViewChildren('dynamicDiv') dynamicDivs: QueryList<ElementRef>;

ngAfterViewInit() {
this.dynamicDivs.changes.subscribe(divs => console.log(divs));
}
}

In this example, dynamicDivs will contain references to all active divs, and it will update if the list changes.

Example 4: Performing Actions on Child Directives

Suppose you have a directive applied to multiple elements and you want to perform an action on all these elements:

Highlight Directive:

@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
highlight(color: string) {
// Highlight logic
}
}

Parent Component:

<p appHighlight #highlightRef="appHighlight">Text 1</p>
<p appHighlight #highlightRef="appHighlight">Text 2</p>
<p appHighlight #highlightRef="appHighlight">Text 3</p>
<button (click)="highlightAll('yellow')">Highlight All</button>
import { Component, ViewChildren, QueryList } from '@angular/core';
import { HighlightDirective } from './highlight.directive';

@Component({/* ... */})
export class AppComponent {
@ViewChildren('highlightRef') highlightDirectives: QueryList<HighlightDirective>;

highlightAll(color: string) {
this.highlightDirectives.forEach(dir => dir.highlight(color));
}
}

Clicking "Highlight All" will apply the highlight to all paragraphs.

QueryList

The @ViewChildren decorator provides access to a QueryList, which is a live collection of child components, directives, or DOM elements. The QueryList instance comes with several useful properties and methods:

Properties of QueryList

  1. changes: Observable<any>
    • An Observable that emits a notification each time the content of the query list changes. It's useful for reacting to changes in the number or order of elements, like when new elements are added or existing ones are removed.

Methods of QueryList

  1. get(index: number): T | undefined

    • Returns the item at the specified index from the query list.
  2. forEach(fn: (item: T, index: number, array: T[]) => void): void

    • Executes a function for each item in the query list.
  3. toArray(): T[]

    • Converts the QueryList into a regular array.
  4. map<R>(fn: (item: T, index: number, array: T[]) => R): R[]

    • Transforms the items in the query list by applying a function and returns an array of the results.
  5. filter(fn: (item: T, index: number, array: T[]) => boolean): T[]

    • Creates a new array containing all of the items from the query list that satisfy the provided testing function.
  6. find(fn: (item: T, index: number, array: T[]) => boolean): T | undefined

    • Returns the first item in the query list that satisfies the provided testing function.
  7. reduce<R>(fn: (previousValue: R, currentValue: T, currentIndex: number, array: T[]) => R, initialReducerValue: R): R

    • Applies a function against an accumulator and each value of the query list (from left to right) to reduce it to a single value.
  8. some(fn: (value: T, index: number, array: T[]) => boolean): boolean

    • Returns true if at least one item in the query list satisfies the provided testing function.
  9. every(fn: (value: T, index: number, array: T[]) => boolean): boolean

    • Returns true if all items in the query list satisfy the provided testing function.
  10. length: number

    • The number of elements in the QueryList.
  11. first: T

    • The first item in the QueryList.
  12. last: T

    • The last item in the QueryList.

Usage Considerations

  • The QueryList is updated as part of Angular's change detection cycle, so its content may not be immediately available at all lifecycle hooks.
  • The changes observable property of QueryList is particularly useful for setting up a subscription that reacts to changes in the child elements or components.

Example

Using @ViewChildren to query a list of child components and subscribing to changes:

@ViewChildren(ChildComponent) children: QueryList<ChildComponent>;

ngAfterViewInit() {
this.children.changes.subscribe((comps: QueryList<ChildComponent>) => {
// React to changes in the child components
});
}

More examples

Below are various use cases illustrating the versatility of QueryList:

1. Dynamic Interaction with Child Components

Suppose you have a set of tabs implemented as child components, and you want to control them from a parent component.

Child Component (Tab Component):

@Component({
selector: 'app-tab',
template: `<!-- Tab content -->`
})
export class TabComponent {
active = false;

activate() {
this.active = true;
}

deactivate() {
this.active = false;
}
}

Parent Component:

@Component({
selector: 'app-tabs-container',
template: `
<app-tab></app-tab>
<app-tab></app-tab>
<app-tab></app-tab>
`
})
export class TabsContainerComponent implements AfterViewInit {
@ViewChildren(TabComponent) tabs: QueryList<TabComponent>;

ngAfterViewInit() {
// Activate the first tab
this.tabs.first.activate();
}

deactivateAllTabs() {
this.tabs.forEach(tab => tab.deactivate());
}
}

In this example, the parent component can control the state of each tab (e.g., activating or deactivating them).

2. Managing Form Input Fields

If you have a dynamic form with multiple input fields and you need to validate or manipulate them:

Parent Component Template:

<form>
<input #inputField type="text" *ngFor="let item of items">
<button (click)="validateInputs()">Validate</button>
</form>

Parent Component Class:

@Component({/* ... */})
export class FormComponent implements AfterViewInit {
@ViewChildren('inputField') inputFields: QueryList<ElementRef>;

validateInputs() {
this.inputFields.forEach(field => {
// Perform validation or other operations on each field
});
}
}

This example allows the parent component to access and manipulate all dynamically generated input fields.

3. Responding to Changes in Content

Detecting and responding to changes in the number of elements or components:

Parent Component Class:

@Component({/* ... */})
export class DynamicComponent implements AfterViewInit {
@ViewChildren(SomeDirective) items: QueryList<SomeDirective>;

ngAfterViewInit() {
this.items.changes.subscribe((queryList: QueryList<SomeDirective>) => {
// Respond to changes
console.log('Items count:', queryList.length);
});
}
}

This setup is useful for scenarios where the elements or components might be added or removed dynamically, and you need to react to these changes.

4. Dynamic Rendering and Querying

In a scenario where you dynamically render components and need to perform operations on them:

Parent Component Template:

<ng-container *ngFor="let item of data">
<app-dynamic-component #dynamicComponent></app-dynamic-component>
</ng-container>

Parent Component Class:

@Component({/* ... */})
export class ParentComponent implements AfterViewInit {
@ViewChildren('dynamicComponent') dynamicComponents: QueryList<DynamicComponent>;

ngAfterViewInit() {
this.dynamicComponents.forEach(component => {
// Interact with each dynamic component instance
});
}
}

5. Coordinating Among Directive Instances

If you have a custom directive applied to multiple elements and you need to coordinate their behavior:

Directive:

@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
highlight() {
// Highlight logic
}
}

Component Using Directives:

@Component({
template: `
<p appHighlight #highlightRef="appHighlight">...</p>
<p appHighlight #highlightRef="appHighlight">...</p>
<button (click)="highlightAll()">Highlight All</button>
`
})
export class SomeComponent {
@ViewChildren('highlightRef') highlights: QueryList<HighlightDirective>;

highlightAll() {
this.highlights.forEach(highlight => highlight.highlight());
}
}

In this example, clicking the "Highlight All" button will trigger the highlight effect on all paragraphs with the HighlightDirective.