# 指令组合 API

Angular 指令提供了一种封装可复用行为的好方法 —— 指令可以将属性、CSS 类和事件侦听器应用于元素。

指令组合 API允许你从组件内部将指令应用于此组件的宿主元素。

# 向组件添加指令

你可以通过将 hostDirectives 属性添加到组件的装饰器来将指令应用于组件。我们称这样的指令为宿主指令。

在此示例中,我们将指令 MenuBehavior应用于 AdminMenu的宿主元素。这类似于将 MenuBehavior应用于模板中的 <admin-menu>元素。

@Component({
  selector: 'admin-menu',
  template: 'admin-menu.html',
  hostDirectives: [MenuBehavior],
})
export class AdminMenu { }

当框架渲染组件时,Angular 还会创建每个宿主指令的实例。指令的宿主绑定被应用于组件的宿主元素。默认情况下,宿主指令的输入和输出不会作为组件公共 API 的一部分公开。有关更多信息,请参阅下面的包含输入属性和输出属性。

Angular 会在编译时静态应用宿主指令。你不能在运行时动态添加指令。

hostDirectives中使用的指令必须是 standalone: true的。

Angular 会忽略 hostDirectives属性中所应用的那些指令的 selector

# 包含输入属性和输出属性

默认情况下,当你将 hostDirectives应用于组件时,宿主指令的输入属性和输出属性不会包含在组件的 API 中。你可以通过扩展 hostDirectives中的条目来在组件的 API 中显式包含输入和输出:

@Component({
  selector: 'admin-menu',
  template: 'admin-menu.html',
  hostDirectives: [{
    directive: MenuBehavior,
    inputs: ['menuId'],
    outputs: ['menuClosed'],
  }],
})
export class AdminMenu { }

通过显式指定输入和输出,使用 hostDirective的组件的使用者可以将它们绑定在模板中:

<admin-menu menuId="top-menu" (menuClosed)="logMenuClosed()">

此外,你可以为 hostDirective的输入和输出起别名来自定义组件的 API:

@Component({
  selector: 'admin-menu',
  template: 'admin-menu.html',
  hostDirectives: [{
    directive: MenuBehavior,
    inputs: ['menuId: id'],
    outputs: ['menuClosed: closed'],
  }],
})
export class AdminMenu { }
<admin-menu id="top-menu" (closed)="logMenuClosed()">

# 将指令添加到另一个指令

除了组件之外,你还可以将 hostDirectives添加到其他指令。这启用了多个行为的可传递聚合能力。

在以下示例中,我们定义了两个指令,MenuTooltip。然后,我们会在 MenuWithTooltip中组合这两个指令的行为。最后,我们将 MenuWithTooltip应用到 SpecializedMenuWithTooltip上。

在模板中使用 SpecializedMenuWithTooltip时,它会创建 MenuTooltipMenuWithTooltip的所有实例。这些指令的宿主绑定中的每一个都会应用于 SpecializedMenuWithTooltip的宿主元素。

@Directive({...})
export class Menu { }

@Directive({...})
export class Tooltip { }

// MenuWithTooltip can compose behaviors from multiple other directives
@Directive({
  hostDirectives: [Tooltip, Menu],
})
export class MenuWithTooltip { }

// CustomWidget can apply the already-composed behaviors from MenuWithTooltip
@Directive({
  hostDirectives: [MenuWithTooltip],
})
export class SpecializedMenuWithTooltip { }

# 指令的执行顺序

宿主指令和直接在模板中使用的组件和指令会经历相同的生命周期。但是,宿主指令总是会在应用它们的组件或指令之前执行它们的构造函数、生命周期钩子和绑定。

以下示例显示了宿主指令的最小化使用:

@Component({
  selector: 'admin-menu',
  template: 'admin-menu.html',
  hostDirectives: [MenuBehavior],
})
export class AdminMenu { }

这里的执行顺序是:

  • MenuBehavior实例化

  • AdminMenu实例化

  • MenuBehavior接收输入( ngOnInit

  • AdminMenu接收输入 ( ngOnInit)

  • MenuBehavior应用宿主绑定

  • AdminMenu应用宿主绑定

这种操作顺序意味着带有 hostDirectives的组件可以改写(override)宿主指令指定的任何宿主绑定。

展开宿主指令的嵌套链的操作顺序,如下例所示。

@Directive({...})
export class Tooltip { }

@Directive({
  hostDirectives: [Tooltip],
})
export class CustomTooltip { }

@Directive({
  hostDirectives: [CustomTooltip],
})
export class EvenMoreCustomTooltip { }

在上面的示例中,执行顺序是:

  • Tooltip实例化

  • CustomTooltip实例化

  • EvenMoreCustomTooltip实例化

  • Tooltip接收输入 ( ngOnInit)

  • CustomTooltip接收输入 ( ngOnInit)

  • EvenMoreCustomTooltip接收输入 ( ngOnInit)

  • Tooltip应用宿主绑定

  • CustomTooltip应用宿主绑定

  • EvenMoreCustomTooltip应用宿主绑定

# 依赖注入

指定了 hostDirectives的组件或指令可以注入这些宿主指令的实例,反之亦然。

当把宿主指令应用于组件时,组件和宿主指令都可以定义提供者。

如果带有 hostDirectives的组件或指令以及这些宿主指令都提供相同的注入令牌,则带有 hostDirectives的类定义的提供者会优先于宿主指令定义的提供者。

# 性能

虽然指令组合 API 提供了一个强大的工具来复用常见行为,但过度使用宿主指令会影响应用程序的内存使用。如果你创建使用许多个宿主指令的组件或指令,你可能会无意中让应用程序占用的内存膨胀。

以下示例显示了一个应用多个宿主指令的组件。

@Component({
  hostDirectives: [
    DisabledState,
    RequiredState,
    ValidationState,
    ColorState,
    RippleBehavior,
  ],
})
export class CustomCheckbox { }

此示例声明了一个自定义复选框组件,其中包含五个宿主指令。这意味着每次 CustomCheckbox渲染时,Angular 将创建六个对象 —— 组件用一个,每个宿主指令用一个。对于页面上的少量复选框,这不会构成任何重大问题。但是,如果你的页面渲染数百个复选框(例如在表格中),那么你可能会开始看到额外对象分配的影响。始终确保对你的应用程序进行性能剖析,以便为你的用例确定正确的组合模式。

Last Updated: 5/13/2023, 10:57:08 AM