Skip to content

Commit

Permalink
fix(common): reflect input type in NgIf context (#33997)
Browse files Browse the repository at this point in the history
Fixes the content of `NgIf` being typed to any.

Fixes #31556.

PR Close #33997
  • Loading branch information
crisbeto authored and mhevery committed Dec 2, 2019
1 parent b640d38 commit 7504543
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 25 deletions.
34 changes: 21 additions & 13 deletions packages/common/src/directives/ng_if.ts
Expand Up @@ -149,22 +149,22 @@ import {Directive, EmbeddedViewRef, Input, TemplateRef, ViewContainerRef, ɵstri
* @publicApi * @publicApi
*/ */
@Directive({selector: '[ngIf]'}) @Directive({selector: '[ngIf]'})
export class NgIf { export class NgIf<T> {
private _context: NgIfContext = new NgIfContext(); private _context: NgIfContext<T> = new NgIfContext<T>();
private _thenTemplateRef: TemplateRef<NgIfContext>|null = null; private _thenTemplateRef: TemplateRef<NgIfContext<T>>|null = null;
private _elseTemplateRef: TemplateRef<NgIfContext>|null = null; private _elseTemplateRef: TemplateRef<NgIfContext<T>>|null = null;
private _thenViewRef: EmbeddedViewRef<NgIfContext>|null = null; private _thenViewRef: EmbeddedViewRef<NgIfContext<T>>|null = null;
private _elseViewRef: EmbeddedViewRef<NgIfContext>|null = null; private _elseViewRef: EmbeddedViewRef<NgIfContext<T>>|null = null;


constructor(private _viewContainer: ViewContainerRef, templateRef: TemplateRef<NgIfContext>) { constructor(private _viewContainer: ViewContainerRef, templateRef: TemplateRef<NgIfContext<T>>) {
this._thenTemplateRef = templateRef; this._thenTemplateRef = templateRef;
} }


/** /**
* The Boolean expression to evaluate as the condition for showing a template. * The Boolean expression to evaluate as the condition for showing a template.
*/ */
@Input() @Input()
set ngIf(condition: any) { set ngIf(condition: T) {
this._context.$implicit = this._context.ngIf = condition; this._context.$implicit = this._context.ngIf = condition;
this._updateView(); this._updateView();
} }
Expand All @@ -173,7 +173,7 @@ export class NgIf {
* A template to show if the condition expression evaluates to true. * A template to show if the condition expression evaluates to true.
*/ */
@Input() @Input()
set ngIfThen(templateRef: TemplateRef<NgIfContext>|null) { set ngIfThen(templateRef: TemplateRef<NgIfContext<T>>|null) {
assertTemplate('ngIfThen', templateRef); assertTemplate('ngIfThen', templateRef);
this._thenTemplateRef = templateRef; this._thenTemplateRef = templateRef;
this._thenViewRef = null; // clear previous view if any. this._thenViewRef = null; // clear previous view if any.
Expand All @@ -184,7 +184,7 @@ export class NgIf {
* A template to show if the condition expression evaluates to false. * A template to show if the condition expression evaluates to false.
*/ */
@Input() @Input()
set ngIfElse(templateRef: TemplateRef<NgIfContext>|null) { set ngIfElse(templateRef: TemplateRef<NgIfContext<T>>|null) {
assertTemplate('ngIfElse', templateRef); assertTemplate('ngIfElse', templateRef);
this._elseTemplateRef = templateRef; this._elseTemplateRef = templateRef;
this._elseViewRef = null; // clear previous view if any. this._elseViewRef = null; // clear previous view if any.
Expand Down Expand Up @@ -225,14 +225,22 @@ export class NgIf {
* narrow its type, which allows the strictNullChecks feature of TypeScript to work with `NgIf`. * narrow its type, which allows the strictNullChecks feature of TypeScript to work with `NgIf`.
*/ */
static ngTemplateGuard_ngIf: 'binding'; static ngTemplateGuard_ngIf: 'binding';

/**
* Asserts the correct type of the context for the template that `NgIf` will render.
*
* 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<T>(dir: NgIf<T>, ctx: any): ctx is NgIfContext<T> { return true; }
} }


/** /**
* @publicApi * @publicApi
*/ */
export class NgIfContext { export class NgIfContext<T> {
public $implicit: any = null; public $implicit: T = null !;
public ngIf: any = null; public ngIf: T = null !;
} }


function assertTemplate(property: string, templateRef: TemplateRef<any>| null): void { function assertTemplate(property: string, templateRef: TemplateRef<any>| null): void {
Expand Down
42 changes: 39 additions & 3 deletions packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts
Expand Up @@ -64,10 +64,19 @@ export declare class NgForOf<T, U extends i0.NgIterable<T>> implements DoCheck {
static ɵdir: i0.ɵɵDirectiveDefWithMeta<NgForOf<any>, '[ngFor][ngForOf]', never, {'ngForOf': 'ngForOf'}, {}, never>; static ɵdir: i0.ɵɵDirectiveDefWithMeta<NgForOf<any>, '[ngFor][ngForOf]', never, {'ngForOf': 'ngForOf'}, {}, never>;
} }
export declare class NgIf { export declare class NgIf<T> {
ngIf: any; ngIf: T;
ngIfElse: TemplateRef<NgIfContext<T>> | null;
ngIfThen: TemplateRef<NgIfContext<T>> | null;
constructor(_viewContainer: ViewContainerRef, templateRef: TemplateRef<NgIfContext<T>>);
static ngTemplateGuard_ngIf: 'binding'; static ngTemplateGuard_ngIf: 'binding';
static ɵdir: i0.ɵɵDirectiveDefWithMeta<NgForOf<any>, '[ngIf]', never, {'ngIf': 'ngIf'}, {}, never>; static ngTemplateContextGuard<T>(dir: NgIf<T>, ctx: any): ctx is NgIfContext<T>;
static ɵdir: i0.ɵɵDirectiveDefWithMeta<NgIf<any>, '[ngIf]', never, {'ngIf': 'ngIf'}, {}, never>;
}
export declare class NgIfContext<T> {
$implicit: T;
ngIf: T;
} }
export declare class CommonModule { export declare class CommonModule {
Expand Down Expand Up @@ -815,6 +824,33 @@ export declare class AnimationEvent {
expect(diags.length).toBe(0); expect(diags.length).toBe(0);
}); });


it('should infer the context of NgIf', () => {
env.tsconfig({fullTemplateTypeCheck: true, strictTemplates: true});
env.write('test.ts', `
import {CommonModule} from '@angular/common';
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'test',
template: '<div *ngIf="getUser(); let user">{{user.nonExistingProp}}</div>',
})
class TestCmp {
getUser(): {name: string} {
return {name: 'frodo'};
}
}
@NgModule({
declarations: [TestCmp],
imports: [CommonModule],
})
class Module {}
`);

const diags = env.driveDiagnostics();
expect(diags.length).toBe(1);
expect(diags[0].messageText)
.toBe(`Property 'nonExistingProp' does not exist on type '{ name: string; }'.`);
});

it('should report an error with an unknown local ref target', () => { it('should report an error with an unknown local ref target', () => {
env.write('test.ts', ` env.write('test.ts', `
import {Component, NgModule} from '@angular/core'; import {Component, NgModule} from '@angular/core';
Expand Down
2 changes: 1 addition & 1 deletion packages/core/test/render3/common_with_def.ts
Expand Up @@ -12,7 +12,7 @@ import {IterableDiffers, NgIterable, TemplateRef, ViewContainerRef} from '@angul
import {DirectiveType, ɵɵNgOnChangesFeature, ɵɵdefineDirective, ɵɵdirectiveInject} from '../../src/render3/index'; import {DirectiveType, ɵɵNgOnChangesFeature, ɵɵdefineDirective, ɵɵdirectiveInject} from '../../src/render3/index';


export const NgForOf: DirectiveType<NgForOfDef<any, NgIterable<any>>> = NgForOfDef as any; export const NgForOf: DirectiveType<NgForOfDef<any, NgIterable<any>>> = NgForOfDef as any;
export const NgIf: DirectiveType<NgIfDef> = NgIfDef as any; export const NgIf: DirectiveType<NgIfDef<any>> = NgIfDef as any;
export const NgTemplateOutlet: DirectiveType<NgTemplateOutletDef> = NgTemplateOutletDef as any; export const NgTemplateOutlet: DirectiveType<NgTemplateOutletDef> = NgTemplateOutletDef as any;


NgForOf.ɵdir = ɵɵdefineDirective({ NgForOf.ɵdir = ɵɵdefineDirective({
Expand Down
17 changes: 9 additions & 8 deletions tools/public_api_guard/common/common.d.ts
Expand Up @@ -235,17 +235,18 @@ export declare class NgForOfContext<T, U extends NgIterable<T>> {
constructor($implicit: T, ngForOf: U, index: number, count: number); constructor($implicit: T, ngForOf: U, index: number, count: number);
} }


export declare class NgIf { export declare class NgIf<T> {
ngIf: any; ngIf: T;
ngIfElse: TemplateRef<NgIfContext> | null; ngIfElse: TemplateRef<NgIfContext<T>> | null;
ngIfThen: TemplateRef<NgIfContext> | null; ngIfThen: TemplateRef<NgIfContext<T>> | null;
constructor(_viewContainer: ViewContainerRef, templateRef: TemplateRef<NgIfContext>); 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>;
} }


export declare class NgIfContext { export declare class NgIfContext<T> {
$implicit: any; $implicit: T;
ngIf: any; ngIf: T;
} }


export declare class NgLocaleLocalization extends NgLocalization { export declare class NgLocaleLocalization extends NgLocalization {
Expand Down

0 comments on commit 7504543

Please sign in to comment.