fix(ivy): repeat template guards to narrow types in event handlers (#35193)
In Ivy's template type checker, event bindings are checked in a closure to allow for accurate type inference of the `$event` parameter. Because of the closure, any narrowing effects of template guards will no longer be in effect when checking the event binding, as TypeScript assumes that the guard outside of the closure may no longer be true once the closure is invoked. For more information on TypeScript's Control Flow Analysis, please refer to https://github.com/microsoft/TypeScript/issues/9998. In Angular templates, it is known that an event binding can only be executed when the view it occurs in is currently rendered, hence the corresponding template guard is known to hold during the invocation of an event handler closure. As such, it is desirable that any narrowing effects from template guards are still in effect within the event handler closure. This commit tweaks the generated Type-Check Block (TCB) to repeat all template guards within an event handler closure. This achieves the narrowing effect of the guards even within the closure. Fixes #35073 PR Close #35193
This commit is contained in:
@ -240,6 +240,59 @@ export declare class AnimationEvent {
|
||||
expect(diags[2].messageText).toEqual(`Property 'focused' does not exist on type 'TestCmp'.`);
|
||||
});
|
||||
|
||||
// https://github.com/angular/angular/issues/35073
|
||||
it('ngIf should narrow on output types', () => {
|
||||
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="person" (click)="handleEvent(person.name)"></div>',
|
||||
})
|
||||
class TestCmp {
|
||||
person?: { name: string; };
|
||||
handleEvent(name: string) {}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule],
|
||||
declarations: [TestCmp],
|
||||
})
|
||||
class Module {}
|
||||
`);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(0);
|
||||
});
|
||||
|
||||
it('ngIf should narrow on output types across multiple guards', () => {
|
||||
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="person"><div *ngIf="person.name" (click)="handleEvent(person.name)"></div></div>',
|
||||
})
|
||||
class TestCmp {
|
||||
person?: { name?: string; };
|
||||
handleEvent(name: string) {}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule],
|
||||
declarations: [TestCmp],
|
||||
})
|
||||
class Module {}
|
||||
`);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(0);
|
||||
});
|
||||
|
||||
describe('strictInputTypes', () => {
|
||||
beforeEach(() => {
|
||||
env.write('test.ts', `
|
||||
|
Reference in New Issue
Block a user