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:
Judy Bogart 2019-12-23 13:06:42 -08:00 committed by Misko Hevery
parent cb6ddfc215
commit 7addc9f1a9
3 changed files with 89 additions and 11 deletions

View File

@ -542,6 +542,7 @@ It does not, however, rewrite the `.d.ts` file, so TypeScript doesn't recognize
{@a binding-expression-validation} {@a binding-expression-validation}
## Phase 3: Template type checking ## 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. 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. 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. 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> </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`. Using `*ngIf` allows the TypeScript compiler to infer that the `person` used in the binding expression will never be `undefined`.
#### Custom `ngIf` like directives 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).
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.
### Non-null type assertion operator ### Non-null type assertion operator

View File

@ -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"> <img src='generated/images/guide/structural-directives/unless-anim.gif' alt="UnlessDirective in action">
</div> </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 directives `state` input is of the umbrella type `LoadingState`, as its 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} {@a summary}

View File

@ -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. 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. 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 ### 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. 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. 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} {@a input-setter-coercion}