feat(compiler): narrow types of expressions used in *ngIf (#20702)

Structural directives can now specify a type guard that describes
what types can be inferred for an input expression inside the
directive's template.

NgIf was modified to declare an input guard on ngIf.

After this change, `fullTemplateTypeCheck` will infer that
usage of `ngIf` expression inside it's template is truthy.

For example, if a component has a property `person?: Person`
and a template of `<div *ngIf="person"> {{person.name}} </div>`
the compiler will no longer report that `person` might be null or
undefined.

The template compiler will generate code similar to,

```
  if (NgIf.ngIfTypeGuard(instance.person)) {
    instance.person.name
  }
```

to validate the template's use of the interpolation expression.
Calling the type guard in this fashion allows TypeScript to infer
that `person` is non-null.

Fixes: #19756?

PR Close #20702
This commit is contained in:
Chuck Jazdzewski
2017-11-29 16:29:05 -08:00
committed by Jason Aden
parent e544742156
commit e7d9cb3e4c
19 changed files with 341 additions and 53 deletions

View File

@ -254,6 +254,7 @@ export interface CompileDirectiveSummary extends CompileTypeSummary {
providers: CompileProviderMetadata[];
viewProviders: CompileProviderMetadata[];
queries: CompileQueryMetadata[];
guards: {[key: string]: any};
viewQueries: CompileQueryMetadata[];
entryComponents: CompileEntryComponentMetadata[];
changeDetection: ChangeDetectionStrategy|null;
@ -268,8 +269,8 @@ export interface CompileDirectiveSummary extends CompileTypeSummary {
*/
export class CompileDirectiveMetadata {
static create({isHost, type, isComponent, selector, exportAs, changeDetection, inputs, outputs,
host, providers, viewProviders, queries, viewQueries, entryComponents, template,
componentViewType, rendererType, componentFactory}: {
host, providers, viewProviders, queries, guards, viewQueries, entryComponents,
template, componentViewType, rendererType, componentFactory}: {
isHost: boolean,
type: CompileTypeMetadata,
isComponent: boolean,
@ -282,6 +283,7 @@ export class CompileDirectiveMetadata {
providers: CompileProviderMetadata[],
viewProviders: CompileProviderMetadata[],
queries: CompileQueryMetadata[],
guards: {[key: string]: any};
viewQueries: CompileQueryMetadata[],
entryComponents: CompileEntryComponentMetadata[],
template: CompileTemplateMetadata,
@ -336,6 +338,7 @@ export class CompileDirectiveMetadata {
providers,
viewProviders,
queries,
guards,
viewQueries,
entryComponents,
template,
@ -358,6 +361,7 @@ export class CompileDirectiveMetadata {
providers: CompileProviderMetadata[];
viewProviders: CompileProviderMetadata[];
queries: CompileQueryMetadata[];
guards: {[key: string]: any};
viewQueries: CompileQueryMetadata[];
entryComponents: CompileEntryComponentMetadata[];
@ -367,10 +371,27 @@ export class CompileDirectiveMetadata {
rendererType: StaticSymbol|object|null;
componentFactory: StaticSymbol|object|null;
constructor({isHost, type, isComponent, selector, exportAs,
changeDetection, inputs, outputs, hostListeners, hostProperties,
hostAttributes, providers, viewProviders, queries, viewQueries,
entryComponents, template, componentViewType, rendererType, componentFactory}: {
constructor({isHost,
type,
isComponent,
selector,
exportAs,
changeDetection,
inputs,
outputs,
hostListeners,
hostProperties,
hostAttributes,
providers,
viewProviders,
queries,
guards,
viewQueries,
entryComponents,
template,
componentViewType,
rendererType,
componentFactory}: {
isHost: boolean,
type: CompileTypeMetadata,
isComponent: boolean,
@ -385,6 +406,7 @@ export class CompileDirectiveMetadata {
providers: CompileProviderMetadata[],
viewProviders: CompileProviderMetadata[],
queries: CompileQueryMetadata[],
guards: {[key: string]: any},
viewQueries: CompileQueryMetadata[],
entryComponents: CompileEntryComponentMetadata[],
template: CompileTemplateMetadata|null,
@ -406,6 +428,7 @@ export class CompileDirectiveMetadata {
this.providers = _normalizeArray(providers);
this.viewProviders = _normalizeArray(viewProviders);
this.queries = _normalizeArray(queries);
this.guards = guards;
this.viewQueries = _normalizeArray(viewQueries);
this.entryComponents = _normalizeArray(entryComponents);
this.template = template;
@ -430,6 +453,7 @@ export class CompileDirectiveMetadata {
providers: this.providers,
viewProviders: this.viewProviders,
queries: this.queries,
guards: this.guards,
viewQueries: this.viewQueries,
entryComponents: this.entryComponents,
changeDetection: this.changeDetection,