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 <span *ngIf="(user$ | async) as user">{{user.name}}</span> ``` 2. Binding to `NgIfContext.$implicit` using the `let` syntax: ```html <span *ngIf="user$ | async; let user">{{user.name}}</span> ``` 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
This commit is contained in:
parent
208ef7bd62
commit
40039d8068
@ -232,7 +232,9 @@ export class NgIf<T = unknown> {
|
|||||||
* The presence of this method is a signal to the Ivy template type-check compiler that the
|
* 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.
|
* `NgIf` structural directive renders its template with a specific context type.
|
||||||
*/
|
*/
|
||||||
static ngTemplateContextGuard<T>(dir: NgIf<T>, ctx: any): ctx is NgIfContext<T> { return true; }
|
static ngTemplateContextGuard<T>(dir: NgIf<T>, ctx: any): ctx is NgIfContext<NonNullable<T>> {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -70,7 +70,7 @@ export declare class NgIf<T = unknown> {
|
|||||||
ngIfThen: TemplateRef<NgIfContext<T>> | null;
|
ngIfThen: TemplateRef<NgIfContext<T>> | null;
|
||||||
constructor(_viewContainer: ViewContainerRef, templateRef: TemplateRef<NgIfContext<T>>);
|
constructor(_viewContainer: ViewContainerRef, templateRef: TemplateRef<NgIfContext<T>>);
|
||||||
static ngTemplateGuard_ngIf: 'binding';
|
static ngTemplateGuard_ngIf: 'binding';
|
||||||
static ngTemplateContextGuard<T>(dir: NgIf<T>, ctx: any): ctx is NgIfContext<T>;
|
static ngTemplateContextGuard<T>(dir: NgIf<T>, ctx: any): ctx is NgIfContext<NonNullable<T>>;
|
||||||
static ɵdir: i0.ɵɵDirectiveDefWithMeta<NgIf<any>, '[ngIf]', never, {'ngIf': 'ngIf'}, {}, never>;
|
static ɵdir: i0.ɵɵDirectiveDefWithMeta<NgIf<any>, '[ngIf]', never, {'ngIf': 'ngIf'}, {}, never>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -805,6 +805,54 @@ export declare class AnimationEvent {
|
|||||||
env.driveMain();
|
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: '<div *ngIf="user; let u">{{u.name}}</div>',
|
||||||
|
})
|
||||||
|
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: '<div *ngIf="user as u">{{u.name}}</div>',
|
||||||
|
})
|
||||||
|
class TestCmp {
|
||||||
|
user: {name: string}|null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [TestCmp],
|
||||||
|
imports: [CommonModule],
|
||||||
|
})
|
||||||
|
class Module {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
env.driveMain();
|
||||||
|
});
|
||||||
|
|
||||||
it('should check basic usage of NgFor', () => {
|
it('should check basic usage of NgFor', () => {
|
||||||
env.write('test.ts', `
|
env.write('test.ts', `
|
||||||
import {CommonModule} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
|
2
tools/public_api_guard/common/common.d.ts
vendored
2
tools/public_api_guard/common/common.d.ts
vendored
@ -236,7 +236,7 @@ export declare class NgIf<T = unknown> {
|
|||||||
set ngIfThen(templateRef: TemplateRef<NgIfContext<T>> | null);
|
set ngIfThen(templateRef: TemplateRef<NgIfContext<T>> | null);
|
||||||
constructor(_viewContainer: ViewContainerRef, templateRef: TemplateRef<NgIfContext<T>>);
|
constructor(_viewContainer: ViewContainerRef, templateRef: TemplateRef<NgIfContext<T>>);
|
||||||
static ngTemplateGuard_ngIf: 'binding';
|
static ngTemplateGuard_ngIf: 'binding';
|
||||||
static ngTemplateContextGuard<T>(dir: NgIf<T>, ctx: any): ctx is NgIfContext<T>;
|
static ngTemplateContextGuard<T>(dir: NgIf<T>, ctx: any): ctx is NgIfContext<NonNullable<T>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare class NgIfContext<T = unknown> {
|
export declare class NgIfContext<T = unknown> {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user