mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
227 lines
7.1 KiB
Markdown
227 lines
7.1 KiB
Markdown
# Directive composition API
|
|
|
|
Angular directives offer a great way to encapsulate reusable behaviors— directives can apply
|
|
attributes, CSS classes, and event listeners to an element.
|
|
|
|
The *directive composition API* lets you apply directives to a component's host element from
|
|
_within_ the component TypeScript class.
|
|
|
|
## Adding directives to a component
|
|
|
|
You apply directives to a component by adding a `hostDirectives` property to a component's
|
|
decorator. We call such directives *host directives*.
|
|
|
|
In this example, we apply the directive `MenuBehavior` to the host element of `AdminMenu`. This
|
|
works similarly to applying the `MenuBehavior` to the `<admin-menu>` element in a template.
|
|
|
|
```typescript
|
|
@Component({
|
|
standalone: true,
|
|
selector: 'admin-menu',
|
|
template: 'admin-menu.html',
|
|
hostDirectives: [MenuBehavior],
|
|
})
|
|
export class AdminMenu { }
|
|
```
|
|
|
|
When the framework renders a component, Angular also creates an instance of each host directive. The
|
|
directives' host bindings apply to the component's host element. By default, host directive inputs
|
|
and outputs are not exposed as part of the component's public API. See
|
|
[Including inputs and outputs](#including-inputs-and-outputs) below for more information.
|
|
|
|
**Angular applies host directives statically at compile time.** You cannot dynamically add
|
|
directives at runtime.
|
|
|
|
**Directives used in `hostDirectives` must be `standalone: true`.**
|
|
|
|
**Angular ignores the `selector` of directives applied in the `hostDirectives` property.**
|
|
|
|
## Including inputs and outputs
|
|
|
|
When you apply `hostDirectives` to your component, the inputs and outputs from the host directives
|
|
are not included in your component's API by default. You can explicitly include inputs and outputs
|
|
in your component's API by expanding the entry in `hostDirectives`:
|
|
|
|
```typescript
|
|
@Component({
|
|
standalone: true,
|
|
selector: 'admin-menu',
|
|
template: 'admin-menu.html',
|
|
hostDirectives: [{
|
|
directive: MenuBehavior,
|
|
inputs: ['menuId'],
|
|
outputs: ['menuClosed'],
|
|
}],
|
|
})
|
|
export class AdminMenu { }
|
|
```
|
|
|
|
By explicitly specifying the inputs and outputs, consumers of the component with `hostDirective` can
|
|
bind them in a template:
|
|
|
|
```html
|
|
<admin-menu menuId="top-menu" (menuClosed)="logMenuClosed()">
|
|
```
|
|
|
|
Furthermore, you can alias inputs and outputs from `hostDirective` to customize the API of your
|
|
component:
|
|
|
|
```typescript
|
|
@Component({
|
|
standalone: true,
|
|
selector: 'admin-menu',
|
|
template: 'admin-menu.html',
|
|
hostDirectives: [{
|
|
directive: MenuBehavior,
|
|
inputs: ['menuId: id'],
|
|
outputs: ['menuClosed: closed'],
|
|
}],
|
|
})
|
|
export class AdminMenu { }
|
|
```
|
|
|
|
```html
|
|
<admin-menu id="top-menu" (closed)="logMenuClosed()">
|
|
```
|
|
|
|
## Adding directives to another directive
|
|
|
|
You can also add `hostDirectives` to other directives, in addition to components. This enables the
|
|
transitive aggregation of multiple behaviors.
|
|
|
|
In the following example, we define two directives, `Menu` and `Tooltip`. We then compose the behavior
|
|
of these two directives in `MenuWithTooltip`. Finally, we apply `MenuWithTooltip`
|
|
to `SpecializedMenuWithTooltip`.
|
|
|
|
When `SpecializedMenuWithTooltip` is used in a template, it creates instances of all of `Menu`
|
|
, `Tooltip`, and `MenuWithTooltip`. Each of these directives' host bindings apply to the host
|
|
element of `SpecializedMenuWithTooltip`.
|
|
|
|
```typescript
|
|
@Directive({...})
|
|
export class Menu { }
|
|
|
|
@Directive({...})
|
|
export class Tooltip { }
|
|
|
|
// MenuWithTooltip can compose behaviors from multiple other directives
|
|
@Directive({
|
|
standalone: true,
|
|
hostDirectives: [Tooltip, Menu],
|
|
})
|
|
export class MenuWithTooltip { }
|
|
|
|
// CustomWidget can apply the already-composed behaviors from MenuWithTooltip
|
|
@Directive({
|
|
standalone: true,
|
|
hostDirectives: [MenuWithTooltip],
|
|
})
|
|
export class SpecializedMenuWithTooltip { }
|
|
```
|
|
|
|
## Host directive semantics
|
|
|
|
### Directive execution order
|
|
|
|
Host directives go through the same lifecycle as components and directives used directly in a
|
|
template. However, host directives always execute their constructor, lifecycle hooks, and bindings _before_ the component or directive on which they are applied.
|
|
|
|
The following example shows minimal use of a host directive:
|
|
|
|
```typescript
|
|
@Component({
|
|
standalone: true,
|
|
selector: 'admin-menu',
|
|
template: 'admin-menu.html',
|
|
hostDirectives: [MenuBehavior],
|
|
})
|
|
export class AdminMenu { }
|
|
```
|
|
|
|
The order of execution here is:
|
|
|
|
1. `MenuBehavior` instantiated
|
|
2. `AdminMenu` instantiated
|
|
3. `MenuBehavior` receives inputs (`ngOnInit`)
|
|
4. `AdminMenu` receives inputs (`ngOnInit`)
|
|
5. `MenuBehavior` applies host bindings
|
|
6. `AdminMenu` applies host bindings
|
|
|
|
This order of operations means that components with `hostDirectives` can override any host bindings
|
|
specified by a host directive.
|
|
|
|
This order of operations extends to nested chains of host directives, as shown in the following
|
|
example.
|
|
|
|
```typescript
|
|
@Directive({...})
|
|
export class Tooltip { }
|
|
|
|
@Directive({
|
|
standalone: true,
|
|
hostDirectives: [Tooltip],
|
|
})
|
|
export class CustomTooltip { }
|
|
|
|
@Directive({
|
|
standalone: true,
|
|
hostDirectives: [CustomTooltip],
|
|
})
|
|
export class EvenMoreCustomTooltip { }
|
|
```
|
|
|
|
In the example above, the order of execution is:
|
|
|
|
1. `Tooltip` instantiated
|
|
2. `CustomTooltip` instantiated
|
|
3. `EvenMoreCustomTooltip` instantiated
|
|
4. `Tooltip` receives inputs (`ngOnInit`)
|
|
5. `CustomTooltip` receives inputs (`ngOnInit`)
|
|
6. `EvenMoreCustomTooltip` receives inputs (`ngOnInit`)
|
|
7. `Tooltip` applies host bindings
|
|
8. `CustomTooltip` applies host bindings
|
|
9. `EvenMoreCustomTooltip` applies host bindings
|
|
|
|
### Dependency injection
|
|
|
|
A component or directive that specifies `hostDirectives` can inject the instances of those host
|
|
directives and vice versa.
|
|
|
|
When applying host directives to a component, both the component and host directives can define
|
|
providers.
|
|
|
|
If a component or directive with `hostDirectives` and those host directives both provide the same
|
|
injection token, the providers defined by class with `hostDirectives` take precedence over providers
|
|
defined by the host directives.
|
|
|
|
### Performance
|
|
|
|
While the directive composition API offers a powerful tool for reusing common behaviors, excessive
|
|
use of host directives can impact your application's memory use. If you create components or
|
|
directives that use _many_ host directives, you may inadvertently balloon the memory used by your
|
|
application.
|
|
|
|
The following example shows a component that applies several host directives.
|
|
|
|
```typescript
|
|
@Component({
|
|
standalone: true,
|
|
hostDirectives: [
|
|
DisabledState,
|
|
RequiredState,
|
|
ValidationState,
|
|
ColorState,
|
|
RippleBehavior,
|
|
],
|
|
})
|
|
export class CustomCheckbox { }
|
|
```
|
|
|
|
This example declares a custom checkbox component that includes five host directives. This
|
|
means that Angular will create six objects each time a `CustomCheckbox` renders— one for the
|
|
component and one for each host directive. For a few checkboxes on a page, this won't pose any
|
|
significant issues. However, if your page renders _hundreds_ of checkboxes, such as in a table, then
|
|
you could start to see an impact of the additional object allocations. Always be sure to profile
|
|
your application to determine the right composition pattern for your use case.
|
|
|
|
@reviewed 2022-12-11
|