Component vs Directive Selector

選擇器類型概述

Angular 提供了兩種主要的裝飾器來創建可重用的 UI 元素:

  • @Component: 完整的 UI 元件,包含模板、樣式和邏輯

  • @Directive: 只包含行為和樣式修改的指令

1. Component 選擇器模式

// 1. 元素選擇器(最常見)
@Component({
  selector: 'app-hero',  // 使用: <app-hero></app-hero>
  template: `
    <div class="hero">
      <h2>{{hero.name}}</h2>
    </div>
  `
})
export class HeroComponent { }

// 2. 屬性選擇器
@Component({
  selector: 'button[appButton]',  // 使用: <button appButton>Click me</button>
  template: `
    <ng-content></ng-content>  // 允許內容投影
  `,
  styleUrls: ['./button.component.css']
})
export class ButtonComponent {
  @Input() color: string = 'primary';
  // 元件邏輯...
}

// 3. 類選擇器(較少使用)
@Component({
  selector: '.app-special',  // 使用: <div class="app-special"></div>
  template: `
    <div>Special content</div>
  `
})
export class SpecialComponent { }

2. Directive 選擇器模式

// 1. 屬性型指令(Attribute Directive)
@Directive({
  selector: '[appHighlight]'  // 注意:需要 app 前綴
})
export class HighlightDirective {
  @Input() highlightColor: string = 'yellow';

  constructor(private el: ElementRef) { }

  @HostListener('mouseenter')
  onMouseEnter() {
    this.highlight(this.highlightColor);
  }

  private highlight(color: string) {
    this.el.nativeElement.style.backgroundColor = color;
  }
}

// 2. 結構型指令(Structural Directive)
@Directive({
  selector: '[customIf]'  // 注意:不需要 app 前綴
})
export class CustomIfDirective {
  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef
  ) { }

  @Input() set customIf(condition: boolean) {
    if (condition) {
      this.viewContainer.createEmbeddedView(this.templateRef);
    } else {
      this.viewContainer.clear();
    }
  }
}

DOM 渲染比較

1. 使用元件選擇器的 DOM 結構

<!-- 使用元素選擇器的元件 -->
<app-hero>  <!-- 外層包裝元素 -->
  <div class="hero">  <!-- 模板內容 -->
    <h2>Windstorm</h2>
  </div>
</app-hero>

<!-- 使用屬性選擇器的元件 -->
<button appButton>  <!-- 單一層級 -->
  Click me
</button>

2. 使用指令的 DOM 結構

<!-- 屬性型指令 -->
<div appHighlight>  <!-- 直接修改現有元素 -->
  Hover me to highlight
</div>

<!-- 結構型指令 -->
<div *customIf="condition">  <!-- 條件性渲染 -->
  This content is conditionally rendered
</div>

使用場景建議

使用 Component 的情況

  1. 需要完整的 UI 元件

  2. 需要模板和樣式

  3. 需要內容投影(ng-content)

  4. 有複雜的元件邏輯

  5. 需要生命週期鉤子

@Component({
  selector: 'button[appButton]',
  template: `
    <span class="icon" *ngIf="icon">
      <i class="fas fa-{{icon}}"></i>
    </span>
    <ng-content></ng-content>
    <span class="spinner" *ngIf="loading"></span>
  `,
  styleUrls: ['./button.component.scss']
})
export class ButtonComponent implements OnInit {
  @Input() icon?: string;
  @Input() loading: boolean = false;
  @Output() clicked = new EventEmitter<void>();

  ngOnInit() {
    // 初始化邏輯
  }

  // 更多元件方法...
}

使用 Directive 的情況

  1. 只需要修改元素行為

  2. 只需要添加樣式

  3. 需要重用簡單的 DOM 操作

  4. 不需要模板

@Directive({
  selector: '[appTooltip]'
})
export class TooltipDirective {
  @Input('appTooltip') tooltipText: string = '';

  constructor(private el: ElementRef) { }

  @HostListener('mouseenter')
  onMouseEnter() {
    // 顯示工具提示
  }

  @HostListener('mouseleave')
  onMouseLeave() {
    // 隱藏工具提示
  }
}

實踐建議

  1. 選擇器命名規範:

    • Component: 使用有意義的前綴(如 app-

    • Attribute Directive: 必須使用 app 前綴

    • Structural Directive: 不使用前綴

  2. DOM 結構考量:

    • 使用屬性選擇器可以減少 DOM 層級

    • 考慮性能和調試需求

  3. 功能劃分:

    • 複雜的 UI 邏輯使用 Component

    • 簡單的行為修改使用 Directive

Last updated