From 7addc9f1a9b6a54465a95d3c98f0dab605e3bd53 Mon Sep 17 00:00:00 2001 From: Judy Bogart Date: Mon, 23 Dec 2019 13:06:42 -0800 Subject: [PATCH] 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 --- aio/content/guide/aot-compiler.md | 13 +--- aio/content/guide/structural-directives.md | 83 ++++++++++++++++++++++ aio/content/guide/template-typecheck.md | 4 +- 3 files changed, 89 insertions(+), 11 deletions(-) diff --git a/aio/content/guide/aot-compiler.md b/aio/content/guide/aot-compiler.md index 013718101b..246e6842c0 100644 --- a/aio/content/guide/aot-compiler.md +++ b/aio/content/guide/aot-compiler.md @@ -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-typecheck). @@ -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 diff --git a/aio/content/guide/structural-directives.md b/aio/content/guide/structural-directives.md index b428a1f6b0..5a269d4bb7 100644 --- a/aio/content/guide/structural-directives.md +++ b/aio/content/guide/structural-directives.md @@ -832,6 +832,89 @@ When the `condition` is truthy, the top (A) paragraph is removed and the bottom UnlessDirective in action +{@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. + +
+ +{@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. + + +export type Loaded = { type: 'loaded', data: T }; +export type Loading = { type: 'loading' }; +export type LoadingState = Loaded | Loading; +export class IfLoadedDirective { + @Input('ifLoaded') set state(state: LoadingState) {} + static ngTemplateGuard_state(dir: IfLoadedDirective, expr: LoadingState): expr is Loaded { return true; }; +export interface Person { + name: string; +} + +@Component({ + template: `
{{ state.data }}
`, +}) +export class AppComponent { + state: LoadingState; +} +
+ +In this example, the `LoadingState` type permits either of two states, `Loaded` 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`. +The type guard allows the type checker to infer that the acceptable type of `state` within the template is a `Loaded`, 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. + + +@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; }; + + // … +} + + {@a summary} diff --git a/aio/content/guide/template-typecheck.md b/aio/content/guide/template-typecheck.md index 9eb02023c0..b00834bfe8 100644 --- a/aio/content/guide/template-typecheck.md +++ b/aio/content/guide/template-typecheck.md @@ -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}