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:

  1. Component Directives: Components are directives with a template
  2. Structural Directives: Change the DOM layout by adding/removing elements
  3. 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:

  1. 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';
      }
    }
  2. 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();
        }
      }
    }
  3. 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');
      });
    });
  4. 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
      }
    }

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.