docs: add doc for template type guards to stuctural-directives.md (#34549)
add new feature recommendation to existing content include references from aot-compiler.md and template-typecheck.md PR Close #34549
This commit is contained in:
parent
cb6ddfc215
commit
7addc9f1a9
|
@ -542,6 +542,7 @@ It does not, however, rewrite the `.d.ts` file, so TypeScript doesn't recognize
|
|||
|
||||
|
||||
{@a binding-expression-validation}
|
||||
|
||||
## Phase 3: Template type checking
|
||||
|
||||
One of the Angular compiler's most helpful features is the ability to type-check expressions within templates, and catch any errors before they cause crashes at runtime.
|
||||
|
@ -559,7 +560,7 @@ As a result, templates that previously compiled under View Engine can fail type
|
|||
This stricter type checking is not enabled by default in version 9, but can be enabled by setting the `strictTemplates` configuration option.
|
||||
We do expect to make strict type checking the default in the future.
|
||||
|
||||
<!-- For more information about type-checking options, and about improvements to template type checking in version 9 and above, see [Template type checking](guide/template-type-checking). -->
|
||||
For more information about type-checking options, and about improvements to template type checking in version 9 and above, see [Template type checking](guide/template-typecheck).
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -618,15 +619,7 @@ For example, to avoid `Object is possibly 'undefined'` error in the template abo
|
|||
|
||||
Using `*ngIf` allows the TypeScript compiler to infer that the `person` used in the binding expression will never be `undefined`.
|
||||
|
||||
#### Custom `ngIf` like directives
|
||||
|
||||
Directives that behave like `*ngIf` can declare that they want the same treatment by including a static member marker that is a signal to the template compiler to treat them like `*ngIf`. This static member for `*ngIf` is:
|
||||
|
||||
```typescript
|
||||
public static ngIfUseIfTypeGuard: void;
|
||||
```
|
||||
|
||||
This declares that the input property `ngIf` of the `NgIf` directive should be treated as a guard to the use of its template, implying that the template will only be instantiated if the `ngIf` input property is true.
|
||||
For more information about input type narrowing, see [Input setter coercion](guide/template-typecheck#input-setter-coercion) and [Improving template type checking for custom directives](guide/structural-directives#directive-type-checks).
|
||||
|
||||
|
||||
### Non-null type assertion operator
|
||||
|
|
|
@ -832,6 +832,89 @@ When the `condition` is truthy, the top (A) paragraph is removed and the bottom
|
|||
<img src='generated/images/guide/structural-directives/unless-anim.gif' alt="UnlessDirective in action">
|
||||
</div>
|
||||
|
||||
{@a directive-type-checks}
|
||||
|
||||
## Improving template type checking for custom directives
|
||||
|
||||
You can improve template type checking for custom directives by adding template guard properties to your directive definition.
|
||||
These properties help the Angular template type checker find mistakes in the template at compile time, which can avoid runtime errors those mistakes can cause.
|
||||
|
||||
Use the type-guard properties to inform the template type checker of an expected type, thus improving compile-time type-checking for that template.
|
||||
|
||||
* A property `ngTemplateGuard_(someInputProperty)` lets you specify a more accurate type for an input expression within the template.
|
||||
* The `ngTemplateContextGuard` static property declares the type of the template context.
|
||||
|
||||
This section provides example of both kinds of type-guard property.
|
||||
|
||||
<div class="alert is-helpful>
|
||||
|
||||
For more information, see [Template type checking guide](guide/template-typecheck "Template type-checking guide").
|
||||
|
||||
</div>
|
||||
|
||||
{@a narrowing-input-types}
|
||||
|
||||
### Make in-template type requirements more specific with template guards
|
||||
|
||||
A structural directive in a template controls whether that template is rendered at run time, based on its input expression.
|
||||
To help the compiler catch template type errors, you should specify as closely as possible the required type of a directive's input expression when it occurs inside the template.
|
||||
|
||||
A type guard function *narrows* the expected type of an input expression to a subset of types that might be passed to the directive within the template at run time.
|
||||
You can provide such a function to help the type-checker infer the proper type for the expression at compile time.
|
||||
|
||||
For example, the `NgIf` implementation uses type-narrowing to ensure that the
|
||||
template is only instantiated if the input expression to `*ngIf` is truthy.
|
||||
To provide the specific type requirement, the `NgIf` directive defines a [static property `ngTemplateGuard_ngIf: 'binding'`](api/common/NgIf#static-properties).
|
||||
The `binding` value is a special case for a common kind of type-narrowing where the input expression is evaluated in order to satisfy the type requirement.
|
||||
|
||||
To provide a more specific type for an input expression to a directive within the template, add a `ngTemplateGuard_xx` property to the directive, where the suffix to the static property name is the `@Input` field name.
|
||||
The value of the property can be either a general type-narrowing function based on its return type, or the string `"binding"` as in the case of `NgIf`.
|
||||
|
||||
For example, consider the following structural directive that takes the result of a template expression as an input.
|
||||
|
||||
<code-example language="ts" header="IfLoadedDirective">
|
||||
export type Loaded<T> = { type: 'loaded', data: T };
|
||||
export type Loading = { type: 'loading' };
|
||||
export type LoadingState<T> = Loaded<T> | Loading;
|
||||
export class IfLoadedDirective<T> {
|
||||
@Input('ifLoaded') set state(state: LoadingState<T>) {}
|
||||
static ngTemplateGuard_state<T>(dir: IfLoadedDirective<T>, expr: LoadingState<T>): expr is Loaded<T> { return true; };
|
||||
export interface Person {
|
||||
name: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `<div *ifLoaded="state">{{ state.data }}</div>`,
|
||||
})
|
||||
export class AppComponent {
|
||||
state: LoadingState<Person>;
|
||||
}
|
||||
</code-example>
|
||||
|
||||
In this example, the `LoadingState<T>` type permits either of two states, `Loaded<T>` or `Loading`. The expression used as the directive’s `state` input is of the umbrella type `LoadingState`, as it’s unknown what the loading state is at that point.
|
||||
|
||||
The `IfLoadedDirective` definition declares the static field `ngTemplateGuard_state`, which expresses the narrowing behavior.
|
||||
Within the `AppComponent` template, the `*ifLoaded` structural directive should render this template only when `state` is actually `Loaded<Person>`.
|
||||
The type guard allows the type checker to infer that the acceptable type of `state` within the template is a `Loaded<T>`, and further infer that `T` must be an instance of `Person`.
|
||||
|
||||
{@a narrowing-context-type}
|
||||
|
||||
### Typing the directive's context
|
||||
|
||||
If your structural directive provides a context to the instantiated template, you can properly type it inside the template by providing a static `ngTemplateContextGuard` function.
|
||||
The following snippet shows an example of such a function.
|
||||
|
||||
<code-example language="ts" header="myDirective.ts">
|
||||
@Directive({…})
|
||||
export class ExampleDirective {
|
||||
// Make sure the template checker knows the type of the context with which the
|
||||
// template of this directive will be rendered
|
||||
static ngTemplateContextGuard(dir: ExampleDirective, ctx: unknown): ctx is ExampleContext { return true; };
|
||||
|
||||
// …
|
||||
}
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
{@a summary}
|
||||
|
|
|
@ -167,6 +167,8 @@ Here, during type checking of the template for `AppComponent`, the `[user]="sele
|
|||
Therefore, Angular assigns the `selectedUser` property to `UserDetailComponent.user`, which would result in an error if their types were incompatible.
|
||||
TypeScript checks the assignment according to its type system, obeying flags such as `strictNullChecks` as they are configured in the application.
|
||||
|
||||
You can avoid run-time type errors by providing more specific in-template type requirements to the template type checker. Make the input type requirements for your own directives as specific as possible by providing template-guard functions in the directive definition. See [Improving template type checking for custom directives](guide/structural-directives#directive-type-checks), and [Input setter coercion](#input-setter-coercion) in this guide.
|
||||
|
||||
|
||||
### Strict null checks
|
||||
|
||||
|
@ -201,7 +203,7 @@ There are two potential workarounds to the above issues:
|
|||
|
||||
As a library author, you can take several measures to provide an optimal experience for your users.
|
||||
First, enabling `strictNullChecks` and including `null` in an input's type, as appropriate, communicates to your consumers whether they can provide a nullable value or not.
|
||||
Additionally, it is possible to provide type hints that are specific to the template type checker, see the [Input setter coercion](guide/template-typecheck#input-setter-coercion) section of this guide.
|
||||
Additionally, it is possible to provide type hints that are specific to the template type checker. See [Improving template type checking for custom directives](guide/structural-directives#directive-type-checks), and [Input setter coercion](#input-setter-coercion) below.
|
||||
|
||||
|
||||
{@a input-setter-coercion}
|
||||
|
|
Loading…
Reference in New Issue