Advanced Angular Methods & Real-World Scenarios

 

1. ViewChild / ViewChildren

Purpose: Access DOM elements or child components programmatically.
Scenario: Video player controls in a media dashboard.

typescript
@Component({
  template: `
    <video #player></video>
    <button (click)="toggleFullscreen()">Fullscreen</button>
  `
})
export class VideoPlayerComponent implements AfterViewInit {
  @ViewChild('player', { static: false }) video!: ElementRef<HTMLVideoElement>;
  
  ngAfterViewInit() {
    this.video.nativeElement.addEventListener('timeupdate', this.handleProgress);
  }

  toggleFullscreen() {
    if (this.video.nativeElement.requestFullscreen) {
      this.video.nativeElement.requestFullscreen();
    }
  }
}

Key Use:

  • Direct DOM manipulation when 3rd party libraries require native elements

  • Accessing component APIs (e.g., playerComponent.play())


2. ContentChild / ContentChildren

Purpose: Project content manipulation (ng-content).
Scenario: Tab system with dynamic headers.

typescript
@Component({
  selector: 'app-tab',
  template: `<ng-content></ng-content>`
})
export class TabComponent {
  @ContentChild(TabHeaderComponent) header!: TabHeaderComponent;
}

@Component({
  template: `
    <app-tab>
      <app-tab-header #header></app-tab-header>
      <div>Tab Content</div>
    </app-tab>
  `
})
export class TabGroupComponent {
  @ViewChild(TabComponent) tab!: TabComponent;
  
  ngAfterViewInit() {
    console.log(this.tab.header); // Access projected header
  }
}

Key Use:

  • Creating component libraries with flexible content projection

  • Accessing projected components' APIs


3. @HostBinding & @HostListener

Purpose: Dynamic host element manipulation.
Scenario: Theme switcher directive.

typescript
@Directive({ selector: '[appTheme]' })
export class ThemeDirective {
  @HostBinding('class.dark-theme') isDark = false;
  @HostBinding('attr.data-theme') themeName = 'light';

  @HostListener('document:theme-change', ['$event'])
  onThemeChange(e: CustomEvent) {
    this.isDark = e.detail.dark;
    this.themeName = e.detail.name;
  }
}

Key Use:

  • Creating attribute-driven UI components

  • Responding to global events without service injection


4. Renderer2

Purpose: Safe DOM manipulation.
Scenario: Dynamic SVG chart rendering.

typescript
@Component({
  selector: 'app-chart',
  template: `<div #chartContainer></div>`
})
export class ChartComponent implements OnInit {
  @ViewChild('chartContainer', { static: true }) container!: ElementRef;

  constructor(private renderer: Renderer2) {}

  ngOnInit() {
    const svg = this.renderer.createElement('svg', 'http://www.w3.org/2000/svg');
    this.renderer.setAttribute(svg, 'width', '100%');
    this.renderer.appendChild(this.container.nativeElement, svg);
    
    // Add path element
    const path = this.renderer.createElement('path', 'http://www.w3.org/2000/svg');
    this.renderer.setAttribute(path, 'd', 'M10 10 H 90 V 90 H 10 Z');
    this.renderer.appendChild(svg, path);
  }
}

Key Use:

  • Server-side rendering (SSR) compatibility

  • Working in environments without direct DOM access


5. ChangeDetectorRef

Purpose: Manual change detection control.
Scenario: Integrating non-Angular visualization libraries.

typescript
@Component({
  template: `<div #d3Chart></div>`
})
export class D3WrapperComponent implements OnDestroy {
  @ViewChild('d3Chart') container!: ElementRef;
  private chart: any;

  constructor(private cdr: ChangeDetectorRef) {}

  ngOnInit() {
    this.chart = new ThirdPartyChart(this.container.nativeElement);
    this.chart.on('update', () => {
      // Manually trigger CD when library updates data
      this.cdr.detectChanges();
    });
  }

  ngOnDestroy() {
    this.chart.destroy();
  }
}

Key Use:

  • Optimizing performance with external JS libraries

  • OnPush components with async external events


6. NgZone

Purpose: Control Angular's execution context.
Scenario: High-frequency WebSocket data stream.

typescript
@Component({...})
export class CryptoPriceComponent {
  prices: any[] = [];

  constructor(private zone: NgZone) {
    const socket = new WebSocket('wss://crypto-stream.com');
    
    socket.onmessage = (event) => {
      // Run outside Angular to avoid excessive CD
      this.zone.runOutsideAngular(() => {
        this.processData(JSON.parse(event.data));
      });
    };
  }

  processData(data: any) {
    // Heavy data processing
    const filtered = this.filterPrices(data);
    
    // Re-enter Angular zone for UI update
    this.zone.run(() => {
      this.prices = filtered;
    });
  }
}

Key Use:

  • Optimizing performance for real-time data streams

  • Integrating non-Angular event systems


7. ViewContainerRef

Purpose: Dynamic component rendering.
Scenario: Plugin-based dashboard system.

typescript
@Component({
  template: `<ng-template #pluginHost></ng-template>`
})
export class DashboardComponent implements AfterViewInit {
  @ViewChild('pluginHost', { read: ViewContainerRef }) host!: ViewContainerRef;

  constructor(private injector: Injector) {}

  async loadPlugin(pluginId: string) {
    const { PluginComponent } = await import(`./plugins/${pluginId}.ts`);
    this.host.clear();
    const componentRef = this.host.createComponent(PluginComponent, {
      injector: this.createPluginInjector(pluginId)
    });
    componentRef.instance.config = this.getPluginConfig(pluginId);
  }

  private createPluginInjector(pluginId: string) {
    return Injector.create({
      providers: [{ provide: PLUGIN_TOKEN, useValue: pluginId }],
      parent: this.injector
    });
  }
}

Key Use:

  • Micro-frontend architectures

  • Dynamic module federation


8. Custom Pipes with Memoization

Purpose: Optimized data transformation.
Scenario: Large dataset filtering.

typescript
@Pipe({ name: 'advancedFilter', pure: true })
export class AdvancedFilterPipe implements PipeTransform {
  private cache = new Map<string, any>();

  transform(items: any[], filters: FilterOptions): any[] {
    const cacheKey = JSON.stringify(filters);
    
    if (this.cache.has(cacheKey)) {
      return this.cache.get(cacheKey);
    }

    const result = items.filter(item => {
      return Object.keys(filters).every(key => 
        item[key].includes(filters[key])
      );
    });

    this.cache.set(cacheKey, result);
    return result;
  }
}

Key Use:

  • Preventing recalculations in large lists

  • Complex filtering with multiple parameters


Advanced Scenario: Enterprise Admin Dashboard

Challenge:

  • 50+ dynamic widgets

  • Real-time data updates

  • Role-based access control

  • Cross-widget communication

Solution Architecture:

typescript
// 1. Widget Container (Dynamic Loading)
<ng-container *ngFor="let widget of widgets">
  <ng-template [appWidgetHost]="widget.type"></ng-template>
</ng-container>

// 2. Custom Directive for Dynamic Loading
@Directive({ selector: '[appWidgetHost]' })
export class WidgetDirective implements OnChanges {
  @Input() widgetType!: string;
  
  constructor(public vcr: ViewContainerRef) {}

  ngOnChanges() {
    this.loadWidget();
  }

  private async loadWidget() {
    const { component } = await this.widgetLoader.load(this.widgetType);
    this.vcr.createComponent(component);
  }
}

// 3. State Management with NgRx
this.store.dispatch(loadDashboard({ user }));

// 4. Real-time Updates
merge(
  this.dataService.liveUpdates$,
  this.refreshTrigger$
).pipe(
  throttleTime(300),
  switchMap(() => this.fetchData())
).subscribe(data => {
  this.zone.runOutsideAngular(() => this.processData(data));
});

// 5. Security Directive
<finance-widget *appHasRole="'FINANCE_ADMIN'"></finance-widget>

Key Methods Used:

  • ViewContainerRef.createComponent() for dynamic widgets

  • NgZone.runOutsideAngular() for data processing

  • Custom HasRoleDirective with TemplateRef

  • NgRx selectors with memoized dashboard state

  • RxJS operators for real-time optimization


Pro Interview Tips:

  1. Contextualize Answers:
    "We used Renderer2 instead of native DOM methods because our app required SSR support for SEO optimization."

  2. Compare Alternatives:
    "I chose ViewContainerRef over deprecated ComponentFactoryResolver for future-proofing our dynamic module loader."

  3. Performance Metrics:
    "Implementing ChangeDetectorRef.detach() for our grid component reduced CPU usage by 40% during scrolling."

  4. Error Handling:
    "We wrap ViewContainerRef.createComponent() with try/catch to handle missing widget modules gracefully."

  5. Evolution Patterns:
    *"Initially we used pure pipes for filtering, but migrated to memoized selectors when dealing with 10k+ records.

No comments:

Post a Comment

Advanced Angular Methods & Real-World Scenarios

  1.  ViewChild  /  ViewChildren Purpose : Access DOM elements or child components programmatically. Scenario : Video player controls in a m...

Best for you