1. ViewChild
/ ViewChildren
Purpose: Access DOM elements or child components programmatically.
Scenario: Video player controls in a media dashboard.
@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.
@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.
@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.
@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.
@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.
@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.
@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.
@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:
// 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 widgetsNgZone.runOutsideAngular()
for data processingCustom
HasRoleDirective
withTemplateRef
NgRx selectors with memoized dashboard state
RxJS operators for real-time optimization
Pro Interview Tips:
Contextualize Answers:
"We usedRenderer2
instead of native DOM methods because our app required SSR support for SEO optimization."Compare Alternatives:
"I choseViewContainerRef
over deprecatedComponentFactoryResolver
for future-proofing our dynamic module loader."Performance Metrics:
"ImplementingChangeDetectorRef.detach()
for our grid component reduced CPU usage by 40% during scrolling."Error Handling:
"We wrapViewContainerRef.createComponent()
with try/catch to handle missing widget modules gracefully."Evolution Patterns:
*"Initially we used pure pipes for filtering, but migrated to memoized selectors when dealing with 10k+ records.
No comments:
Post a Comment