# 指令组合 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
添加到其他指令。这启用了多个行为的可传递聚合能力。
在以下示例中,我们定义了两个指令,Menu
和 Tooltip
。然后,我们会在 MenuWithTooltip
中组合这两个指令的行为。最后,我们将 MenuWithTooltip
应用到 SpecializedMenuWithTooltip
上。
在模板中使用 SpecializedMenuWithTooltip
时,它会创建 Menu
、Tooltip
和 MenuWithTooltip
的所有实例。这些指令的宿主绑定中的每一个都会应用于 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 将创建六个对象 —— 组件用一个,每个宿主指令用一个。对于页面上的少量复选框,这不会构成任何重大问题。但是,如果你的页面渲染数百个复选框(例如在表格中),那么你可能会开始看到额外对象分配的影响。始终确保对你的应用程序进行性能剖析,以便为你的用例确定正确的组合模式。
← 结构型指令 Angular 中的依赖注入 →