Back to all posts

ViewChild, ContentChild and ContentChildren in Angular


  1. ViewChild
  2. ContentChild and ContentChildren

1. ViewChild:

  • Used to access elements/components in the template of the current component
  • Available after the view is initialized (ngAfterViewInit)
  • Can reference elements by template reference variable or component type (Ex: #divElement)
  • The static option determines when the query is resolved.
// counter.component.ts
@Component({
  selector: 'app-counter',
  template: `<p>Count: {{ count }}</p>`
})
export class CounterComponent {
  count = 0;

  increment() {
    this.count++;
  }
}

// parent.component.ts
import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';

@Component({
  selector: 'app-parent',
  template: `
    <!-- 1. Basic element reference -->
    <div #greeting>Hello World</div>

    <!-- 2. Component reference -->
    <app-counter #counter></app-counter>

    <!-- 3. Static reference (available in ngOnInit) -->
    <input #staticInput type="text" value="Static Content">

    <!-- 4. Dynamic reference (only available in ngAfterViewInit) -->
    <div *ngIf="showDynamic">
      <p #dynamicText>Dynamic Content</p>
    </div>

    <button (click)="showContent()">Show Content</button>
    <button (click)="incrementCounter()">Increment Counter</button>
  `
})
export class ParentComponent implements AfterViewInit {
  showDynamic = false;

  // 1. Basic element reference
  @ViewChild('greeting') 
  greetingDiv!: ElementRef;

  // 2. Component reference
  @ViewChild('counter') 
  counterComponent!: CounterComponent;

  // 3. Static reference available in ngOnInit
  @ViewChild('staticInput', { static: true })
  staticInput!: ElementRef;

  // 4. Dynamic reference
  @ViewChild('dynamicText') 
  dynamicText!: ElementRef;

  ngOnInit() {
    // Static content is available here
    console.log('Static input value:', this.staticInput.nativeElement.value);
  }

  ngAfterViewInit() {
    // 1. Accessing element reference
    console.log('Greeting text:', this.greetingDiv.nativeElement.textContent);

    // 2. Accessing component methods
    this.counterComponent.increment();

    // 3. Static content is still available
    console.log('Static input in AfterViewInit:', this.staticInput.nativeElement.value);

    // 4. Dynamic content might be undefined if not shown
    if (this.dynamicText) {
      console.log('Dynamic text:', this.dynamicText.nativeElement.textContent);
    }
  }

  showContent() {
    this.showDynamic = true;
  }

  incrementCounter() {
    this.counterComponent.increment();
  }
}

2. ContentChild:

  • Used to access content projected into the component
  • Available after content is initialised (ngAfterContentInit)
  • Accesses the first matching element in projectedContent
  • Useful for accessing elements passed via ng-content

3. ContentChildren:

  • Similar to ContentChild but return multiple elements as QueryList
  • Can query all instances of a component type or template refernce
  • Provides real-time updates when content changes
  • Includes methods like forEach, Map, filter on the QueryList

Example:

// form-field.component.ts
@Component({
  selector: 'app-form-field',
  template: `
    <div class="form-group">
      <label>{{ label }}</label>
      <input [type]="type" 
             [formControlName]="fieldName" 
             class="form-control">
      <span *ngIf="error" class="error">{{ error }}</span>
    </div>
  `
})
export class FormFieldComponent {
  @Input() label: string = '';
  @Input() type: string = 'text';
  @Input() fieldName: string = '';
  @Input() error: string = '';
}

// form-container.component.ts
@Component({
  selector: 'app-form-container',
  template: `
    <form [formGroup]="form" (ngSubmit)="onSubmit()">
      <!-- Primary form field (ContentChild) -->
      <ng-content select="[primary]"></ng-content>
      
      <!-- Additional form fields (ContentChildren) -->
      <ng-content></ng-content>
      
      <div class="form-actions">
        <button type="submit" [disabled]="!form.valid">Submit</button>
        <button type="button" (click)="resetForm()">Reset</button>
      </div>
    </form>
  `
})
export class FormContainerComponent implements AfterContentInit {
  @Input() formName: string = '';
  
  // Access primary form field using ContentChild
  @ContentChild('primaryField')
  primaryField!: FormFieldComponent;
  
  // Access all form fields using ContentChildren
  @ContentChildren(FormFieldComponent)
  formFields!: QueryList<FormFieldComponent>;
  
  form: FormGroup;
  
  constructor(private fb: FormBuilder) {
    this.form = this.fb.group({});
  }
  
  ngAfterContentInit() {
    // Access primary field
    console.log('Primary field:', this.primaryField);
    
    // Initialize form controls based on projected fields
    this.formFields.forEach(field => {
      this.form.addControl(field.fieldName, new FormControl('', Validators.required));
    });
    
    // Listen for changes in form fields
    this.formFields.changes.subscribe(fields => {
      console.log('Form fields updated:', fields);
    });
  }
  
  onSubmit() {
    if (this.form.valid) {
      console.log('Form values:', this.form.value);
    }
  }
  
  resetForm() {
    this.form.reset();
  }
  
  // Method to validate specific field
  validateField(fieldName: string) {
    const control = this.form.get(fieldName);
    if (control && control.errors) {
      const field = this.formFields.find(f => f.fieldName === fieldName);
      if (field) {
        field.error = 'This field is required';
      }
    }
  }
}

// app.component.ts
@Component({
  selector: 'app-root',
  template: `
    <app-form-container formName="userForm">
      <!-- Primary field accessed via ContentChild -->
      <app-form-field
        #primaryField
        primary
        label="Email"
        type="email"
        fieldName="email">
      </app-form-field>
      
      <!-- Additional fields accessed via ContentChildren -->
      <app-form-field
        label="First Name"
        type="text"
        fieldName="firstName">
      </app-form-field>
      
      <app-form-field
        label="Last Name"
        type="text"
        fieldName="lastName">
      </app-form-field>
      
      <app-form-field
        label="Phone"
        type="tel"
        fieldName="phone">
      </app-form-field>
    </app-form-container>
  `
})
export class AppComponent {}

Key differences:

  • ViewChild queries elements in the component’s own template
  • ContentChild queries elements projected into the component
  • ViewChild is resolved after view initialization
  • ContentChild is resolved after content initialization

Common use cases:

  1. ViewChild:
    • Accessing DOM elements directly
    • Calling methods on child components
    • Manipulating view elements programmatically
  2. ContentChild/ContentChildren:
    • Building reusable components that wrap projected content
    • Creating container components
    • Implementing advanced component communication