From 40039d806861fd47029aff5dfe2e2ef273924e9f Mon Sep 17 00:00:00 2001 From: JoostK Date: Mon, 3 Feb 2020 19:30:50 +0100 Subject: [PATCH] fix(ivy): narrow `NgIf` context variables in template type checker (#35125) When the `NgIf` directive is used in a template, its context variables can be used to capture the bound value. This is typically used together with a pipe or function call, where the resulting value is captured in a context variable. There's two syntax forms available: 1. Binding to `NgIfContext.ngIf` using the `as` syntax: ```html {{user.name}} ``` 2. Binding to `NgIfContext.$implicit` using the `let` syntax: ```html {{user.name}} ``` Because of the semantics of `ngIf`, it is known that the captured context variable is non-nullable, however the template type checker would not consider them as such and still report errors when `strictNullTypes` is enabled. This commit updates `NgIf`'s context guard to make the types of the context variables non-nullable, avoiding the issue. Fixes #34572 PR Close #35125 --- packages/common/src/directives/ng_if.ts | 4 +- .../test/ngtsc/template_typecheck_spec.ts | 50 ++++++++++++++++++- tools/public_api_guard/common/common.d.ts | 2 +- 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/packages/common/src/directives/ng_if.ts b/packages/common/src/directives/ng_if.ts index 98fc3c328e..e9cb89227e 100644 --- a/packages/common/src/directives/ng_if.ts +++ b/packages/common/src/directives/ng_if.ts @@ -232,7 +232,9 @@ export class NgIf { * The presence of this method is a signal to the Ivy template type-check compiler that the * `NgIf` structural directive renders its template with a specific context type. */ - static ngTemplateContextGuard(dir: NgIf, ctx: any): ctx is NgIfContext { return true; } + static ngTemplateContextGuard(dir: NgIf, ctx: any): ctx is NgIfContext> { + return true; + } } /** diff --git a/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts b/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts index e299e67452..01f4fc0e18 100644 --- a/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts +++ b/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts @@ -70,7 +70,7 @@ export declare class NgIf { ngIfThen: TemplateRef> | null; constructor(_viewContainer: ViewContainerRef, templateRef: TemplateRef>); static ngTemplateGuard_ngIf: 'binding'; - static ngTemplateContextGuard(dir: NgIf, ctx: any): ctx is NgIfContext; + static ngTemplateContextGuard(dir: NgIf, ctx: any): ctx is NgIfContext>; static ɵdir: i0.ɵɵDirectiveDefWithMeta, '[ngIf]', never, {'ngIf': 'ngIf'}, {}, never>; } @@ -805,6 +805,54 @@ export declare class AnimationEvent { env.driveMain(); }); + it('should check usage of NgIf when using "let" to capture $implicit context variable', () => { + env.tsconfig({strictTemplates: true}); + env.write('test.ts', ` + import {CommonModule} from '@angular/common'; + import {Component, NgModule} from '@angular/core'; + + @Component({ + selector: 'test', + template: '
{{u.name}}
', + }) + class TestCmp { + user: {name: string}|null; + } + + @NgModule({ + declarations: [TestCmp], + imports: [CommonModule], + }) + class Module {} + `); + + env.driveMain(); + }); + + it('should check usage of NgIf when using "as" to capture `ngIf` context variable', () => { + env.tsconfig({strictTemplates: true}); + env.write('test.ts', ` + import {CommonModule} from '@angular/common'; + import {Component, NgModule} from '@angular/core'; + + @Component({ + selector: 'test', + template: '
{{u.name}}
', + }) + class TestCmp { + user: {name: string}|null; + } + + @NgModule({ + declarations: [TestCmp], + imports: [CommonModule], + }) + class Module {} + `); + + env.driveMain(); + }); + it('should check basic usage of NgFor', () => { env.write('test.ts', ` import {CommonModule} from '@angular/common'; diff --git a/tools/public_api_guard/common/common.d.ts b/tools/public_api_guard/common/common.d.ts index 84ade8d7aa..eb758d6045 100644 --- a/tools/public_api_guard/common/common.d.ts +++ b/tools/public_api_guard/common/common.d.ts @@ -236,7 +236,7 @@ export declare class NgIf { set ngIfThen(templateRef: TemplateRef> | null); constructor(_viewContainer: ViewContainerRef, templateRef: TemplateRef>); static ngTemplateGuard_ngIf: 'binding'; - static ngTemplateContextGuard(dir: NgIf, ctx: any): ctx is NgIfContext; + static ngTemplateContextGuard(dir: NgIf, ctx: any): ctx is NgIfContext>; } export declare class NgIfContext {