mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
docs: modernize directives guides (#69822)
Co-authored-by: Matthieu Riegler <kyro38@gmail.com> PR Close #69822
This commit is contained in:
committed by
Alex Rickabaugh
parent
4e7aaa48f5
commit
17845308ea
@@ -2,81 +2,21 @@
|
||||
|
||||
Attribute directives change the appearance or behavior of DOM elements and Angular components.
|
||||
|
||||
## Built-in attribute directives
|
||||
## Use template bindings for one-off behavior
|
||||
|
||||
Angular includes several built-in attribute directives for common tasks:
|
||||
Angular's template syntax already covers changing a single element's classes, styles, properties, and events:
|
||||
|
||||
| Common directives | Details |
|
||||
| :----------------------------------------------------- | :------------------------------------------------- |
|
||||
| [`NgClass`](#adding-and-removing-classes-with-ngclass) | Adds and removes a set of CSS classes. |
|
||||
| [`NgStyle`](#setting-inline-styles-with-ngstyle) | Adds and removes a set of HTML styles. |
|
||||
| [`NgModel`](guide/forms/template-driven-forms) | Adds two-way data binding to an HTML form element. |
|
||||
- [Class and style bindings](guide/templates/binding#css-class-and-style-property-bindings) add and remove CSS classes and inline styles.
|
||||
- [Property and attribute bindings](guide/templates/binding) set DOM properties and HTML attributes.
|
||||
- [Event listeners](guide/templates/event-listeners) respond to user interaction.
|
||||
|
||||
HELPFUL: Built-in directives use only public APIs. They do not have special access to any private APIs that other directives can't access.
|
||||
|
||||
### Adding and removing classes with `NgClass`
|
||||
|
||||
Add or remove multiple CSS classes simultaneously by binding `[ngClass]` to an expression. To use `NgClass`, add it to the component's `imports` list:
|
||||
|
||||
```angular-ts
|
||||
import {NgClass} from '@angular/common';
|
||||
|
||||
@Component({
|
||||
/* ... */
|
||||
imports: [NgClass],
|
||||
})
|
||||
export class AppComponent {}
|
||||
```
|
||||
|
||||
To toggle a single class, bind `[ngClass]` to a conditional expression that returns the class name. In this example, `ngClass` applies the `special` class when `isSpecial` is `true`:
|
||||
|
||||
<docs-code header="app.component.html" path="adev/src/content/examples/built-in-directives/src/app/app.component.html" region="special-div"/>
|
||||
|
||||
To toggle several classes at once, bind `[ngClass]` to an object. Each key is a class name, and Angular adds the class when its value is truthy and removes it when its value is falsy:
|
||||
|
||||
```angular-html
|
||||
<div [ngClass]="{'saveable': canSave, 'modified': !isUnchanged, 'special': isSpecial}">
|
||||
This div's classes reflect the current component state.
|
||||
</div>
|
||||
```
|
||||
|
||||
HELPFUL: To add or remove a _single_ class, use [class binding](/guide/templates/binding#css-class-and-style-property-bindings) rather than `NgClass`.
|
||||
|
||||
### Setting inline styles with `NgStyle`
|
||||
|
||||
Set multiple inline styles simultaneously by binding `[ngStyle]` to an object. To use `NgStyle`, add it to the component's `imports` list:
|
||||
|
||||
```angular-ts
|
||||
import {NgStyle} from '@angular/common';
|
||||
|
||||
@Component({
|
||||
/* ... */
|
||||
imports: [NgStyle],
|
||||
})
|
||||
export class AppComponent {}
|
||||
```
|
||||
|
||||
Each key in the object is a CSS property name and each value is the style to apply:
|
||||
|
||||
```angular-html
|
||||
<div
|
||||
[ngStyle]="{
|
||||
'font-style': canSave ? 'italic' : 'normal',
|
||||
'font-weight': !isUnchanged ? 'bold' : 'normal',
|
||||
'font-size': isSpecial ? '24px' : '12px',
|
||||
}"
|
||||
>
|
||||
This div's styles reflect the current component state.
|
||||
</div>
|
||||
```
|
||||
|
||||
HELPFUL: To add or remove a _single_ style, use [style bindings](guide/templates/binding#css-class-and-style-property-bindings) rather than `NgStyle`.
|
||||
Attribute directives are useful when you want to package this kind of behavior into a reusable unit that you can apply to any element or component.
|
||||
|
||||
## Building an attribute directive
|
||||
|
||||
A custom attribute directive is a class with the `@Directive()` decorator. The decorator's `selector` defines the attribute that applies the directive. By convention, custom selectors use a prefix such as `app` and wrap the name in square brackets to form an attribute selector:
|
||||
A custom attribute directive is a JavaScript class with the `@Directive()` decorator. The decorator's `selector` defines the attribute that applies the directive. The square brackets make this an attribute selector, so the directive matches elements that carry the attribute. By convention, use a prefix such as `app` to avoid naming collisions:
|
||||
|
||||
```angular-ts
|
||||
```ts
|
||||
import {Directive} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
@@ -87,7 +27,7 @@ export class HighlightDirective {}
|
||||
|
||||
HELPFUL: The CLI command [`ng generate directive`](tools/cli/schematics) scaffolds a directive along with its test file.
|
||||
|
||||
To change the host element, a directive needs a reference to it. Inject [`ElementRef`](guide/di) to reach the element through its `nativeElement` property. This directive sets the background to yellow when Angular creates it:
|
||||
A directive can change its host declaratively through host bindings or imperatively through a reference to the host element. This example [injects](guide/di) [`ElementRef`](api/core/ElementRef) and accesses the element through its `nativeElement` property to set the background to yellow:
|
||||
|
||||
<docs-code header="highlight.directive.ts" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.1.ts"/>
|
||||
|
||||
@@ -113,22 +53,22 @@ To respond to user interaction, bind host element events to handler methods thro
|
||||
|
||||
The `host` property maps the `mouseenter` and `mouseleave` events to the `onMouseEnter()` and `onMouseLeave()` methods, which delegate to a `highlight()` helper that sets the background color on the host element. For more on host event bindings, see [binding to the host element](guide/components/host-elements#binding-to-the-host-element).
|
||||
|
||||
<img alt="Second Highlight" src="assets/images/guide/attribute-directives/highlight-directive-anim.gif">
|
||||
|
||||
## Accepting input values
|
||||
|
||||
Like components, directives accept inputs through the [`input()`](guide/components/inputs) function. Give an input the same name as the selector so that a single binding both applies the directive and passes a value to it:
|
||||
|
||||
<docs-code header="highlight.directive.ts" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.3.ts" region="input"/>
|
||||
|
||||
Read the input by calling its signal, and fall back to a default when no value is bound:
|
||||
Read the input by calling it as a signal, and fall back to a default when no color is set:
|
||||
|
||||
<docs-code header="highlight.directive.ts" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.3.ts" region="mouse-enter"/>
|
||||
|
||||
In the template, bind the value to the selector. Because the input shares the selector's name, `[appHighlight]` both applies the directive and sets its value:
|
||||
In the template, bind the value to the selector. Because the input shares the selector's name, `[appHighlight]` both applies the directive and sets its value. Here the bound `color` is a property on the component:
|
||||
|
||||
<docs-code header="app.component.html" path="adev/src/content/examples/attribute-directives/src/app/app.component.html" region="color"/>
|
||||
|
||||
<docs-code header="app.component.ts" path="adev/src/content/examples/attribute-directives/src/app/app.component.ts" region="class"/>
|
||||
|
||||
A directive can declare more than one input. The following directive adds a `defaultColor` input, then falls back through `appHighlight`, `defaultColor`, and finally `red`:
|
||||
|
||||
<docs-code header="highlight.directive.ts" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.ts"/>
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
# 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.
|
||||
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.
|
||||
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_.
|
||||
You apply directives to a component by adding a `hostDirectives` property to a component's decorator. Such directives are called _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.
|
||||
In this example, the `MenuBehavior` directive is applied to the host element of `AdminMenu`. This works similarly to applying the `MenuBehavior` to the `<admin-menu>` element in a template.
|
||||
|
||||
```typescript
|
||||
```ts
|
||||
@Component({
|
||||
selector: 'admin-menu',
|
||||
templateUrl: './admin-menu.html',
|
||||
@@ -23,25 +19,20 @@ works similarly to applying the `MenuBehavior` to the `<admin-menu>` element in
|
||||
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.
|
||||
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.
|
||||
Host directives come with the following constraints:
|
||||
|
||||
**Directives used in `hostDirectives` may not specify `standalone: false`.**
|
||||
|
||||
**Angular ignores the `selector` of directives applied in the `hostDirectives` property.**
|
||||
- **Angular applies host directives statically at compile time.** You cannot dynamically add
|
||||
directives at runtime.
|
||||
- **Directives used in `hostDirectives` may not specify `standalone: false`.**
|
||||
- **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`:
|
||||
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
|
||||
```ts
|
||||
@Component({
|
||||
selector: 'admin-menu',
|
||||
templateUrl: './admin-menu.html',
|
||||
@@ -56,17 +47,15 @@ in your component's API by expanding the entry in `hostDirectives`:
|
||||
export class AdminMenu {}
|
||||
```
|
||||
|
||||
By explicitly specifying the inputs and outputs, consumers of the component with `hostDirective` can
|
||||
bind them in a template:
|
||||
By explicitly specifying the inputs and outputs, consumers of the component with `hostDirectives` can bind them in a template:
|
||||
|
||||
```angular-html
|
||||
<admin-menu menuId="top-menu" (menuClosed)="logMenuClosed()"></admin-menu>
|
||||
```
|
||||
|
||||
Furthermore, you can alias inputs and outputs from `hostDirective` to customize the API of your
|
||||
component:
|
||||
Furthermore, you can alias inputs and outputs from a host directive to customize the API of your component:
|
||||
|
||||
```typescript
|
||||
```ts
|
||||
@Component({
|
||||
selector: 'admin-menu',
|
||||
templateUrl: './admin-menu.html',
|
||||
@@ -87,26 +76,17 @@ export class AdminMenu {}
|
||||
|
||||
## 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.
|
||||
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`.
|
||||
The following example defines two directives, `Menu` and `Tooltip`, then composes their behavior in `MenuWithTooltip`. Finally, it applies `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`.
|
||||
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`.
|
||||
|
||||
```ts
|
||||
@Directive({
|
||||
/* ... */
|
||||
})
|
||||
@Directive({/* ... */})
|
||||
export class Menu {}
|
||||
|
||||
@Directive({
|
||||
/* ... */
|
||||
})
|
||||
@Directive({/* ... */})
|
||||
export class Tooltip {}
|
||||
|
||||
// MenuWithTooltip can compose behaviors from multiple other directives
|
||||
@@ -126,12 +106,11 @@ export class SpecializedMenuWithTooltip {}
|
||||
|
||||
### 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.
|
||||
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
|
||||
```ts
|
||||
@Component({
|
||||
selector: 'admin-menu',
|
||||
templateUrl: './admin-menu.html',
|
||||
@@ -149,13 +128,11 @@ The order of execution here is:
|
||||
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 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.
|
||||
This order of operations extends to nested chains of host directives, as shown in the following example.
|
||||
|
||||
```typescript
|
||||
```ts
|
||||
@Directive({...})
|
||||
export class Tooltip { }
|
||||
|
||||
@@ -184,15 +161,11 @@ In the example above, the order of execution is:
|
||||
|
||||
### Dependency injection
|
||||
|
||||
A component or directive that specifies `hostDirectives` can inject the instances of those host
|
||||
directives and vice versa.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
|
||||
### Host directive de-duplication
|
||||
|
||||
@@ -200,17 +173,14 @@ When the same directive appears more than once in the resolved host directive tr
|
||||
|
||||
#### Template match takes precedence
|
||||
|
||||
If a directive matches an element once through a **template selector** and also appears as a
|
||||
**host directive**, Angular keeps only the template match and discards all host directive matches.
|
||||
If a directive matches an element once through a **template selector** and also appears as a **host directive**, Angular keeps only the template match and discards all host directive matches.
|
||||
|
||||
The mental model is that a host directive match represents `Partial<YourDirective>` , a partial
|
||||
application where only the inputs and outputs explicitly listed in `hostDirectives` are exposed,
|
||||
while a template match represents the full directive with its complete public API.
|
||||
The mental model is that a host directive match represents `Partial<YourDirective>`, a partial application where only the inputs and outputs explicitly listed in `hostDirectives` are exposed, while a template match represents the full directive with its complete public API.
|
||||
|
||||
```ts
|
||||
@Directive({selector: '[hoverable]'})
|
||||
export class Hoverable {}
|
||||
|
||||
```ts
|
||||
@Component({
|
||||
selector: 'app-button',
|
||||
hostDirectives: [Hoverable],
|
||||
@@ -226,10 +196,7 @@ export class Button {}
|
||||
|
||||
#### Multiple host directive matches are merged
|
||||
|
||||
If the same directive appears **more than once as a host directive** , for example, when two
|
||||
directives both declare a common dependency in their `hostDirectives` , Angular merges all
|
||||
instances into a single directive instance. The input and output mappings from all instances are
|
||||
combined.
|
||||
If the same directive appears **more than once as a host directive**, for example, when two directives both declare a common dependency in their `hostDirectives`, Angular merges all instances into a single directive instance. The input and output mappings from all instances are combined.
|
||||
|
||||
This resolves the classic [diamond problem](https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem) in host directive composition:
|
||||
|
||||
@@ -237,11 +204,11 @@ This resolves the classic [diamond problem](https://en.wikipedia.org/wiki/Multip
|
||||
// A shared behavior that both triggers need
|
||||
@Directive({
|
||||
host: {
|
||||
'[attr.data-trigger-id]': 'triggerId',
|
||||
'[attr.data-trigger-id]': 'triggerId()',
|
||||
},
|
||||
})
|
||||
export class TriggerRef {
|
||||
readonly triggerId = `trigger-${crypto.randomUUID()}`;
|
||||
readonly triggerId = input(`trigger-${crypto.randomUUID()}`);
|
||||
}
|
||||
|
||||
// Two separate triggers, each declaring TriggerRef as a host directive
|
||||
@@ -267,14 +234,13 @@ export class DropdownTrigger {
|
||||
<button popoverTrigger dropdownTrigger>Actions</button>
|
||||
```
|
||||
|
||||
HELPFUL: Because Angular produces only one instance of the shared directive, both `PopoverTrigger`
|
||||
and `DropdownTrigger` receive the same `TriggerRef` instance when they inject it.
|
||||
HELPFUL: Because Angular produces only one instance of the shared directive, both `PopoverTrigger` and `DropdownTrigger` receive the same `TriggerRef` instance when they inject it.
|
||||
|
||||
#### Conflicting aliases
|
||||
|
||||
When Angular merges duplicate host directive matches it also merges their input and output mappings.
|
||||
If two instances of the same host directive expose the **same input or output under different
|
||||
aliases**, Angular throws an error at compile time ([NG8024](errors/NG8024))
|
||||
|
||||
If two instances of the same host directive expose the **same input or output under different aliases**, Angular throws an error at compile time ([NG8024](errors/NG8024)).
|
||||
|
||||
```ts
|
||||
@Directive({
|
||||
@@ -297,5 +263,11 @@ export class DropdownTrigger {}
|
||||
<button popoverTrigger dropdownTrigger></button>
|
||||
```
|
||||
|
||||
To resolve this, ensure that both paths expose the shared input or output under the same alias, or
|
||||
do not expose it at all.
|
||||
To resolve this, ensure that both paths expose the shared input or output under the same alias, or do not expose it at all.
|
||||
|
||||
## What's next
|
||||
|
||||
<docs-pill-row>
|
||||
<docs-pill href="guide/directives/structural-directives" title="Structural directives"/>
|
||||
<docs-pill href="guide/components/host-elements" title="Component host elements"/>
|
||||
</docs-pill-row>
|
||||
|
||||
@@ -14,37 +14,32 @@ Common examples include:
|
||||
- Reading from or writing to the host element's DOM, attributes, or classes.
|
||||
- Adding behavior to a component you don't own without changing its source.
|
||||
|
||||
If you need to render your own markup or manage a piece of UI with its own template, reach for a [component](guide/components) rather than a directive.
|
||||
If you need to render your own markup or manage a piece of UI with its own template, reach for a [component](guide/components), a specialized directive with its own template.
|
||||
|
||||
## A quick example
|
||||
|
||||
Suppose you want elements to highlight when the user hovers over them, changing their background color to yellow. Rather than repeat the same event-handling logic on every element, you can package that behavior in a directive and apply it wherever you need it.
|
||||
Suppose you want elements to highlight when the user hovers over them with a mouse, changing their background color to yellow. Rather than repeat the same event-handling logic on every element, you can package that behavior in a directive and apply it wherever you need it.
|
||||
|
||||
The following `appHighlight` directive sets the host element's background color when the pointer enters and clears it when the pointer leaves:
|
||||
The following `appHighlight` directive sets the host element's background color when the mouse enters and clears it when the mouse leaves:
|
||||
|
||||
```angular-ts
|
||||
import {Directive, ElementRef, inject} from '@angular/core';
|
||||
```ts
|
||||
import {Directive, signal} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[appHighlight]',
|
||||
host: {
|
||||
'(mouseenter)': 'onMouseEnter()',
|
||||
'(mouseleave)': 'onMouseLeave()',
|
||||
'(mouseenter)': 'isHovered.set(true)',
|
||||
'(mouseleave)': 'isHovered.set(false)',
|
||||
'[style.background-color]': 'isHovered() ? "yellow" : null',
|
||||
},
|
||||
})
|
||||
export class HighlightDirective {
|
||||
private el = inject(ElementRef);
|
||||
|
||||
onMouseEnter() {
|
||||
this.el.nativeElement.style.backgroundColor = 'yellow';
|
||||
}
|
||||
|
||||
onMouseLeave() {
|
||||
this.el.nativeElement.style.backgroundColor = '';
|
||||
}
|
||||
protected isHovered = signal(false);
|
||||
}
|
||||
```
|
||||
|
||||
The `host` metadata listens for mouse events to update the `isHovered` signal, and binds the host element's `background-color` style to the signal's value.
|
||||
|
||||
Apply the directive by adding its selector as an attribute on an element:
|
||||
|
||||
```angular-html
|
||||
@@ -55,10 +50,11 @@ Every element that carries the `appHighlight` attribute gains the same hover beh
|
||||
|
||||
## Types of directives
|
||||
|
||||
Angular has two primary types of directives:
|
||||
Angular has three primary types of directives:
|
||||
|
||||
| Directive type | Details |
|
||||
| :-------------------------------------------------------------- | :-------------------------------------------------------------------------------- |
|
||||
| [Components](guide/components) | Define reusable UI with their own template. |
|
||||
| [Attribute directives](guide/directives/attribute-directives) | Change the appearance or behavior of an element, component, or another directive. |
|
||||
| [Structural directives](guide/directives/structural-directives) | Change the DOM layout by adding and removing DOM elements. |
|
||||
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
|
||||
Structural directives are directives applied to an `<ng-template>` element that conditionally or repeatedly render the content of that `<ng-template>`.
|
||||
|
||||
For everyday conditional and repeated rendering, use Angular's built-in [control flow blocks](guide/templates/control-flow) (`@if`, `@for`, and `@switch`). Write a structural directive when you need reusable rendering behavior that control flow doesn't cover, such as gating content behind a permission check or providing a template with data from an external source.
|
||||
|
||||
## Example use case
|
||||
|
||||
In this guide you'll build a structural directive which fetches data from a given data source and renders its template when that data is available. This directive is called `SelectDirective`, after the SQL keyword `SELECT`, and match it with an attribute selector `[select]`.
|
||||
This guide uses a directive called `SelectDirective` as its running example. The directive fetches data from a given data source and renders its template when that data is available. It is named after the SQL keyword `SELECT` and matched with an attribute selector, `[select]`.
|
||||
|
||||
`SelectDirective` will have an input naming the data source to be used, which you will call `selectFrom`. The `select` prefix for this input is important for the [shorthand syntax](#structural-directive-shorthand). The directive will instantiate its `<ng-template>` with a template context providing the selected data.
|
||||
`SelectDirective` has an input naming the data source to use, called `selectFrom`. The `select` prefix for this input is important for the [shorthand syntax](#structural-directive-shorthand). The directive instantiates its `<ng-template>` with a template context providing the selected data.
|
||||
|
||||
The following is an example of using this directive directly on an `<ng-template>` would look like:
|
||||
Using this directive directly on an `<ng-template>` looks like this:
|
||||
|
||||
```angular-html
|
||||
<ng-template select let-data [selectFrom]="source">
|
||||
@@ -18,7 +20,7 @@ The following is an example of using this directive directly on an `<ng-template
|
||||
|
||||
The structural directive can wait for the data to become available and then render its `<ng-template>`.
|
||||
|
||||
HELPFUL: Note that Angular's `<ng-template>` element defines a template that doesn't render anything by default, if you just wrap elements in an `<ng-template>` without applying a structural directive those elements will not be rendered.
|
||||
HELPFUL: Angular's `<ng-template>` element defines a template that doesn't render anything by default. If you wrap elements in an `<ng-template>` without applying a structural directive, those elements are not rendered.
|
||||
|
||||
For more information, see the [ng-template API](api/core/ng-template) documentation.
|
||||
|
||||
@@ -60,20 +62,12 @@ You can only apply one structural directive per element when using the shorthand
|
||||
|
||||
## Creating a structural directive
|
||||
|
||||
This section guides you through creating the `SelectDirective`.
|
||||
A structural directive is a directive class that injects two dependencies:
|
||||
|
||||
<docs-workflow>
|
||||
<docs-step title="Generate the directive">
|
||||
Using the Angular CLI, run the following command, where `select` is the name of the directive:
|
||||
- [`TemplateRef`](api/core/TemplateRef) gives the directive access to the content of the `<ng-template>` it is applied to.
|
||||
- [`ViewContainerRef`](api/core/ViewContainerRef) represents the location in the DOM where the directive can render that template.
|
||||
|
||||
```shell
|
||||
ng generate directive select
|
||||
```
|
||||
|
||||
Angular creates the directive class and specifies the CSS selector, `[select]`, that identifies the directive in a template.
|
||||
</docs-step>
|
||||
<docs-step title="Make the directive structural">
|
||||
Import `TemplateRef`, `ViewContainerRef`, and `input`. Inject `TemplateRef` and `ViewContainerRef` in the directive as private properties.
|
||||
The directive controls rendering by creating, or not creating, embedded views from the template in the view container. The complete `SelectDirective` looks like this:
|
||||
|
||||
```ts
|
||||
import {Directive, TemplateRef, ViewContainerRef, inject, input} from '@angular/core';
|
||||
@@ -88,27 +82,9 @@ export interface DataSource<T> {
|
||||
export class SelectDirective {
|
||||
private templateRef = inject(TemplateRef);
|
||||
private viewContainerRef = inject(ViewContainerRef);
|
||||
}
|
||||
```
|
||||
|
||||
</docs-step>
|
||||
<docs-step title="Add the 'selectFrom' input">
|
||||
Add a `selectFrom` `input()` property.
|
||||
|
||||
```ts
|
||||
export class SelectDirective {
|
||||
// ...
|
||||
selectFrom = input.required<DataSource<unknown>>();
|
||||
}
|
||||
```
|
||||
|
||||
</docs-step>
|
||||
<docs-step title="Add the business logic">
|
||||
With `SelectDirective` now scaffolded as a structural directive with its input, you can now add the logic to fetch the data and render the template with it:
|
||||
|
||||
```ts
|
||||
export class SelectDirective {
|
||||
// ...
|
||||
async ngOnInit() {
|
||||
const data = await this.selectFrom().load();
|
||||
this.viewContainerRef.createEmbeddedView(this.templateRef, {
|
||||
@@ -120,10 +96,15 @@ export class SelectDirective {
|
||||
}
|
||||
```
|
||||
|
||||
</docs-step>
|
||||
</docs-workflow>
|
||||
The `selectFrom` input names the data source the directive reads from. It uses [`input.required()`](guide/components/inputs#required-inputs) because the directive can't do anything useful without a data source.
|
||||
|
||||
That's it - `SelectDirective` is up and running. A follow-up step might be to [add template type-checking support](#typing-the-directives-context).
|
||||
When Angular initializes the directive, it loads the data and then renders the template by calling `createEmbeddedView()`. The second argument is the template's _context object_: values the template can bind to with `let` declarations. Assigning the data to the `$implicit` key makes it the default value that `let-data` (or `let data` in shorthand) receives.
|
||||
|
||||
NOTE: This example renders its template once, when the directive initializes. It does not re-render when the bound data source changes.
|
||||
|
||||
Once the directive works, consider [adding template type-checking support](#typing-the-directives-context).
|
||||
|
||||
HELPFUL: The CLI command [`ng generate directive`](tools/cli/schematics) scaffolds a directive along with its test file.
|
||||
|
||||
## Structural directive syntax reference
|
||||
|
||||
@@ -167,8 +148,8 @@ The following table provides shorthand examples:
|
||||
| :-------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ |
|
||||
| `*myDir="let item of [1,2,3]"` | `<ng-template myDir let-item [myDirOf]="[1, 2, 3]">` |
|
||||
| `*myDir="let item of [1,2,3] as items; trackBy: myTrack; index as i"` | `<ng-template myDir let-item [myDirOf]="[1,2,3]" let-items="myDirOf" [myDirTrackBy]="myTrack" let-i="index">` |
|
||||
| `*ngComponentOutlet="componentClass";` | `<ng-template [ngComponentOutlet]="componentClass">` |
|
||||
| `*ngComponentOutlet="componentClass; inputs: myInputs";` | `<ng-template [ngComponentOutlet]="componentClass" [ngComponentOutletInputs]="myInputs">` |
|
||||
| `*ngComponentOutlet="componentClass"` | `<ng-template [ngComponentOutlet]="componentClass">` |
|
||||
| `*ngComponentOutlet="componentClass; inputs: myInputs"` | `<ng-template [ngComponentOutlet]="componentClass" [ngComponentOutletInputs]="myInputs">` |
|
||||
| `*myDir="exp as value"` | `<ng-template [myDir]="exp" let-value="myDir">` |
|
||||
|
||||
## Improving template type checking for custom directives
|
||||
@@ -251,3 +232,11 @@ export class SelectDirective<T> {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## What's next
|
||||
|
||||
<docs-pill-row>
|
||||
<docs-pill href="guide/directives/directive-composition-api" title="Directive composition API"/>
|
||||
<docs-pill href="guide/templates/ng-template" title="ng-template"/>
|
||||
<docs-pill href="guide/templates/control-flow" title="Control flow"/>
|
||||
</docs-pill-row>
|
||||
|
||||
Reference in New Issue
Block a user