What are Directives in Angular?
Question 1: What are Directives in Angular and what are their types?
Answer: Directives in Angular are classes that add behavior to elements in your applications. There are three types:
- Component Directives: Components are directives with a template
- Structural Directives: Change the DOM layout by adding/removing elements
- Attribute Directives: Change the appearance or behavior of an element
Example of each type:
// 1. Component Directive
@Component({
selector: 'app-user',
template: `<h1>{{name}}</h1>`
})
class UserComponent {
name = 'John';
}
// 2. Structural Directive
@Directive({
selector: '[appUnless]'
})
class UnlessDirective {
@Input() set appUnless(condition: boolean) {
if (!condition) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
}
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef
) {}
}
// 3. Attribute Directive
@Directive({
selector: '[appHighlight]'
})
class HighlightDirective {
@Input() highlightColor = 'yellow';
@HostListener('mouseenter')
onMouseEnter() {
this.highlight(this.highlightColor);
}
@HostListener('mouseleave')
onMouseLeave() {
this.highlight(null);
}
private highlight(color: string | null) {
this.el.nativeElement.style.backgroundColor = color;
}
constructor(private el: ElementRef) {}
}
Question 2: How do you create and use Custom Directives in Angular?
Answer: Custom directives are created using the @Directive decorator and can interact with the host element through ElementRef, HostListener, and HostBinding.
// Custom attribute directive
@Directive({
selector: '[appTooltip]',
standalone: true
})
class TooltipDirective {
@Input('appTooltip') tooltipText = '';
// Host element binding
@HostBinding('style.position') position = 'relative';
// Host element event listener
@HostListener('mouseenter')
showTooltip() {
this.tooltip.style.display = 'block';
}
@HostListener('mouseleave')
hideTooltip() {
this.tooltip.style.display = 'none';
}
private tooltip: HTMLElement;
constructor(private el: ElementRef) {
this.createTooltip();
}
private createTooltip() {
this.tooltip = document.createElement('div');
this.tooltip.className = 'tooltip';
this.tooltip.textContent = this.tooltipText;
this.el.nativeElement.appendChild(this.tooltip);
}
}
// Using the directive
@Component({
template: `
<button [appTooltip]="'Click me!'">
Hover me
</button>
`
})
class AppComponent { }
Question 3: How do Structural Directives work in Angular?
Answer: Structural directives manipulate DOM elements using microsyntax (*directive) and work with TemplateRef and ViewContainerRef to add/remove elements.
// Custom structural directive
@Directive({
selector: '[appRepeat]'
})
class RepeatDirective {
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef
) {}
@Input() set appRepeatTimes(count: number) {
this.viewContainer.clear();
for (let i = 0; i < count; i++) {
this.viewContainer.createEmbeddedView(
this.templateRef,
{ index: i }
);
}
}
}
// Using structural directive
@Component({
template: `
<!-- Repeat element 3 times -->
<div *appRepeat="3; let i=index">
Item {{i}}
</div>
<!-- Built-in structural directives -->
<div *ngIf="show">Conditional</div>
<div *ngFor="let item of items">{{item}}</div>
<div [ngSwitch]="value">
<div *ngSwitchCase="'A'">A</div>
<div *ngSwitchDefault>Default</div>
</div>
`
})
class AppComponent { }
Question 4: How do you share data between directives and components?
Answer: Data can be shared using Input/Output decorators, services, or by accessing the host component through dependency injection.
// Directive with inputs and outputs
@Directive({
selector: '[appInteractive]'
})
class InteractiveDirective {
@Input() config: any;
@Output() action = new EventEmitter<string>();
@HostListener('click')
onClick() {
this.action.emit('clicked');
}
}
// Service for sharing data
@Injectable()
class SharedService {
private data = new BehaviorSubject<any>(null);
data$ = this.data.asObservable();
updateData(value: any) {
this.data.next(value);
}
}
// Component using directive and service
@Component({
template: `
<div [appInteractive]="config"
(action)="handleAction($event)">
Interactive Element
</div>
`
})
class AppComponent {
constructor(private shared: SharedService) {}
handleAction(event: string) {
this.shared.updateData(event);
}
}
Question 5: What are the lifecycle hooks available for directives?
Answer: Directives have access to the same lifecycle hooks as components, such as OnInit, OnDestroy, OnChanges, etc.
@Directive({
selector: '[appLifecycle]'
})
class LifecycleDirective implements OnInit,
OnDestroy, OnChanges {
@Input() config: any;
ngOnInit() {
console.log('Directive initialized');
}
ngOnChanges(changes: SimpleChanges) {
if (changes['config']) {
console.log('Config changed:',
changes['config'].currentValue);
}
}
ngOnDestroy() {
console.log('Directive destroyed');
}
}
Common Interview Follow-up Questions:
Q: What’s the difference between HostBinding and ElementRef? A: HostBinding provides declarative binding to host element properties, while ElementRef gives direct access to the DOM element:
@Directive({ selector: '[appStyle]' }) class StyleDirective { // Declarative binding @HostBinding('style.color') color = 'red'; // Direct DOM access constructor(private el: ElementRef) { el.nativeElement.style.fontSize = '16px'; } }
Q: How do you optimize directive performance? A: Use OnPush change detection, avoid expensive operations in lifecycle hooks, and implement pure pipes for transformations:
@Directive({ selector: '[appOptimized]', changeDetection: ChangeDetectionStrategy.OnPush }) class OptimizedDirective { @Input() set data(value: any) { // Cache expensive computations if (this.cached !== value) { this.cached = value; this.compute(); } } }
Q: How do you test directives? A: Test directives using TestBed and ComponentFixture:
describe('HighlightDirective', () => { let component: TestComponent; let fixture: ComponentFixture<TestComponent>; beforeEach(() => { TestBed.configureTestingModule({ declarations: [ TestComponent, HighlightDirective ] }); fixture = TestBed.createComponent(TestComponent); component = fixture.componentInstance; }); it('should highlight on hover', () => { const element = fixture.debugElement .query(By.directive(HighlightDirective)); element.triggerEventHandler('mouseenter', null); expect(element.nativeElement.style.backgroundColor) .toBe('yellow'); }); });
Q: What are the best practices for creating directives? A: Follow these guidelines:
- Use meaningful selectors with prefixes
- Keep directives focused and single-responsibility
- Provide good documentation
- Make them reusable and configurable
@Directive({ selector: '[appPrefix]', standalone: true }) class WellDesignedDirective { // Clear input names @Input() config: DirectiveConfig; // Document public methods /** * Updates the directive's state * @param newState The new state to apply */ updateState(newState: State) { // Implementation } }