ViewChild and ContentChild in Angular

Question 1: What are ViewChild and ContentChild, and how do they differ?

Answer: ViewChild and ContentChild are decorators for accessing child elements:

  • ViewChild: Accesses elements/components in the template view
  • ContentChild: Accesses projected content elements

Key differences:

  1. ViewChild queries view DOM
  2. ContentChild queries projected content
  3. Different lifecycle timing (AfterViewInit vs AfterContentInit)

Question 2: How do you implement ViewChild in modern Angular?

Answer: Here are modern ViewChild implementations:

// Child component
@Component({
  selector: 'app-chart',
  standalone: true,
  template: `<canvas #chartCanvas></canvas>`
})
export class ChartComponent {
  @ViewChild('chartCanvas')
  canvas!: ElementRef<HTMLCanvasElement>;
  
  // Methods that parent might need
  drawChart(data: any) {
    const ctx = this.canvas.nativeElement.getContext('2d');
    // Chart drawing logic
  }
  
  clear() {
    const ctx = this.canvas.nativeElement.getContext('2d');
    ctx?.clearRect(0, 0, this.canvas.nativeElement.width, 
      this.canvas.nativeElement.height);
  }
}

// Parent component
@Component({
  selector: 'app-dashboard',
  standalone: true,
  imports: [ChartComponent],
  template: `
    <app-chart />
    <button (click)="updateChart()">Update</button>
    <button (click)="clearChart()">Clear</button>
  `
})
export class DashboardComponent implements AfterViewInit {
  @ViewChild(ChartComponent)
  chartComponent!: ChartComponent;
  
  ngAfterViewInit() {
    // Safe to access chart component
    this.chartComponent.drawChart(initialData);
  }
  
  updateChart() {
    this.chartComponent.drawChart(newData);
  }
  
  clearChart() {
    this.chartComponent.clear();
  }
}

// Multiple ViewChild queries
@Component({
  selector: 'app-tabs',
  template: `
    <div #tabContainer class="tabs">
      <button 
        #tabButton
        *ngFor="let tab of tabs"
        (click)="selectTab(tab)"
      >
        {{ tab.title }}
      </button>
    </div>
  `
})
export class TabsComponent implements AfterViewInit {
  @ViewChild('tabContainer')
  container!: ElementRef<HTMLDivElement>;
  
  @ViewChildren('tabButton')
  buttons!: QueryList<ElementRef<HTMLButtonElement>>;
  
  ngAfterViewInit() {
    // Access multiple elements
    this.buttons.forEach(button => {
      // Initialize buttons
    });
    
    // Listen for changes
    this.buttons.changes.pipe(
      takeUntilDestroyed()
    ).subscribe(() => {
      // Handle dynamic button changes
    });
  }
}

Question 3: How do you implement ContentChild in modern Angular?

Answer: Here’s how to work with ContentChild:

// Template component
@Component({
  selector: 'app-card',
  template: `
    <div class="card">
      <div class="header">
        <ng-content select="[header]"></ng-content>
      </div>
      <div class="body">
        <ng-content></ng-content>
      </div>
      <div class="footer">
        <ng-content select="[footer]"></ng-content>
      </div>
    </div>
  `
})
export class CardComponent implements AfterContentInit {
  @ContentChild('header')
  headerContent!: ElementRef;
  
  @ContentChildren(CustomComponent)
  customComponents!: QueryList<CustomComponent>;
  
  ngAfterContentInit() {
    // Access projected content
    this.customComponents.forEach(component => {
      component.initialize();
    });
    
    // Listen for content changes
    this.customComponents.changes.pipe(
      takeUntilDestroyed()
    ).subscribe(() => {
      // Handle content changes
    });
  }
}

// Usage
@Component({
  selector: 'app-user-card',
  template: `
    <app-card>
      <h2 #header>User Profile</h2>
      <custom-component></custom-component>
      <button footer>Edit</button>
    </app-card>
  `
})
export class UserCardComponent {}

Question 4: How do you handle dynamic content and views?

Answer: Here’s how to work with dynamic content:

@Component({
  selector: 'app-dynamic-container',
  template: `
    <div #container></div>
    
    <ng-template #template>
      <div>Dynamic Content</div>
    </ng-template>
  `
})
export class DynamicContainerComponent 
  implements AfterViewInit {
  
  @ViewChild('container', { read: ViewContainerRef })
  container!: ViewContainerRef;
  
  @ViewChild('template', { read: TemplateRef })
  template!: TemplateRef<any>;
  
  ngAfterViewInit() {
    // Create view dynamically
    const view = this.template.createEmbeddedView({});
    this.container.insert(view);
  }
  
  // Dynamic component creation
  createComponent() {
    const factory = this.cfr.resolveComponentFactory(
      DynamicComponent
    );
    const componentRef = this.container.createComponent(
      factory
    );
    
    // Access and initialize component
    componentRef.instance.data = 'Initial Data';
    componentRef.instance.output.subscribe(
      data => this.handleOutput(data)
    );
  }
}

Interview Tips 💡

  1. Lifecycle Timing

    @Component({
      template: `
        <child-component></child-component>
        <ng-content></ng-content>
      `
    })
    export class ParentComponent implements 
      AfterContentInit,
      AfterViewInit {
      
      @ContentChild(ChildComponent)
      contentChild!: ChildComponent;
      
      @ViewChild(ChildComponent)
      viewChild!: ChildComponent;
      
      ngAfterContentInit() {
        // ContentChild is available
        this.contentChild.initialize();
      }
      
      ngAfterViewInit() {
        // ViewChild is available
        this.viewChild.initialize();
      }
    }
  2. Static Option

    @Component({
      template: `<div #static>Static</div>`
    })
    export class Component {
      // Available in ngOnInit
      @ViewChild('static', { static: true })
      staticElement!: ElementRef;
      
      // Only available in ngAfterViewInit
      @ViewChild('dynamic')
      dynamicElement!: ElementRef;
    }
  3. Error Handling

    @Component({
      template: `
        <div #optional>Optional Content</div>
      `
    })
    export class SafeComponent {
      @ViewChild('optional')
      optionalElement?: ElementRef;
      
      ngAfterViewInit() {
        // Safe access
        if (this.optionalElement) {
          // Use element
        }
      }
    }
  4. Testing

    describe('Component', () => {
      it('should access ViewChild', () => {
        const fixture = TestBed.createComponent(
          Component
        );
        fixture.detectChanges();
        
        const component = fixture.componentInstance;
        expect(component.viewChild).toBeTruthy();
      });
    });
  5. Performance

    @Component({
      template: `
        @defer (on viewport) {
          <heavy-component #heavy />
        }
      `
    })
    export class OptimizedComponent {
      @ViewChild('heavy')
      heavyComponent?: HeavyComponent;
      
      // Handle null before component loads
      initialize() {
        if (this.heavyComponent) {
          this.heavyComponent.start();
        }
      }
    }
  6. Best Practices

    @Component({
      template: `
        <div #container>
          @for (item of items; track item.id) {
            <child-component />
          }
        </div>
      `
    })
    export class BestPracticesComponent {
      // Query all children
      @ViewChildren(ChildComponent)
      children!: QueryList<ChildComponent>;
      
      // Clean up subscriptions
      private destroy$ = new Subject<void>();
      
      ngOnInit() {
        this.children.changes.pipe(
          takeUntil(this.destroy$)
        ).subscribe(() => {
          // Handle changes
        });
      }
      
      ngOnDestroy() {
        this.destroy$.next();
        this.destroy$.complete();
      }
    }

Remember: In interviews, focus on:

  • Understanding the differences
  • Lifecycle timing
  • Error handling
  • Performance implications
  • Testing strategies
  • Best practices
  • Real-world scenarios

Key points to emphasize:

  1. ViewChild vs ContentChild differences
  2. Lifecycle hooks timing
  3. Static vs Dynamic queries
  4. Error handling
  5. Performance considerations
  6. Testing approaches
  7. Best practices

Test Your Knowledge

Take a quick quiz to test your understanding of this topic.

Test Your Angular Knowledge

Ready to put your skills to the test? Take our interactive Angular quiz and get instant feedback on your answers.