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:
parent
e544742156
commit
e7d9cb3e4c
|
@ -151,6 +151,8 @@ export class NgIf {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ngIfTypeGuard: <T>(v: T|null|undefined|false) => v is T;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -81,6 +81,141 @@ describe('ng type checker', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('type narrowing', () => {
|
||||||
|
const a = (files: MockFiles, options: object = {}) => {
|
||||||
|
accept(files, {fullTemplateTypeCheck: true, ...options});
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should narrow an *ngIf like directive', () => {
|
||||||
|
a({
|
||||||
|
'src/app.component.ts': '',
|
||||||
|
'src/lib.ts': '',
|
||||||
|
'src/app.module.ts': `
|
||||||
|
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';
|
||||||
|
|
||||||
|
export interface Person {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'comp',
|
||||||
|
template: '<div *myIf="person"> {{person.name}} </div>'
|
||||||
|
})
|
||||||
|
export class MainComp {
|
||||||
|
person?: Person;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MyIfContext {
|
||||||
|
public $implicit: any = null;
|
||||||
|
public myIf: any = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({selector: '[myIf]'})
|
||||||
|
export class MyIf {
|
||||||
|
constructor(templateRef: TemplateRef<MyIfContext>) {}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set myIf(condition: any) {}
|
||||||
|
|
||||||
|
static myIfTypeGuard: <T>(v: T | null | undefined | false) => v is T;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [MainComp, MyIf],
|
||||||
|
})
|
||||||
|
export class MainModule {}`
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should narrow a renamed *ngIf like directive', () => {
|
||||||
|
a({
|
||||||
|
'src/app.component.ts': '',
|
||||||
|
'src/lib.ts': '',
|
||||||
|
'src/app.module.ts': `
|
||||||
|
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';
|
||||||
|
|
||||||
|
export interface Person {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'comp',
|
||||||
|
template: '<div *my-if="person"> {{person.name}} </div>'
|
||||||
|
})
|
||||||
|
export class MainComp {
|
||||||
|
person?: Person;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MyIfContext {
|
||||||
|
public $implicit: any = null;
|
||||||
|
public myIf: any = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({selector: '[my-if]'})
|
||||||
|
export class MyIf {
|
||||||
|
constructor(templateRef: TemplateRef<MyIfContext>) {}
|
||||||
|
|
||||||
|
@Input('my-if')
|
||||||
|
set myIf(condition: any) {}
|
||||||
|
|
||||||
|
static myIfTypeGuard: <T>(v: T | null | undefined | false) => v is T;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [MainComp, MyIf],
|
||||||
|
})
|
||||||
|
export class MainModule {}`
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should narrow a type in a nested *ngIf like directive', () => {
|
||||||
|
a({
|
||||||
|
'src/app.component.ts': '',
|
||||||
|
'src/lib.ts': '',
|
||||||
|
'src/app.module.ts': `
|
||||||
|
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';
|
||||||
|
|
||||||
|
export interface Address {
|
||||||
|
street: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Person {
|
||||||
|
name: string;
|
||||||
|
address?: Address;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'comp',
|
||||||
|
template: '<div *myIf="person"> {{person.name}} <span *myIf="person.address">{{person.address.street}}</span></div>'
|
||||||
|
})
|
||||||
|
export class MainComp {
|
||||||
|
person?: Person;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MyIfContext {
|
||||||
|
public $implicit: any = null;
|
||||||
|
public myIf: any = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({selector: '[myIf]'})
|
||||||
|
export class MyIf {
|
||||||
|
constructor(templateRef: TemplateRef<MyIfContext>) {}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set myIf(condition: any) {}
|
||||||
|
|
||||||
|
static myIfTypeGuard: <T>(v: T | null | undefined | false) => v is T;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [MainComp, MyIf],
|
||||||
|
})
|
||||||
|
export class MainModule {}`
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('regressions ', () => {
|
describe('regressions ', () => {
|
||||||
const a = (files: MockFiles, options: object = {}) => {
|
const a = (files: MockFiles, options: object = {}) => {
|
||||||
accept(files, {fullTemplateTypeCheck: true, ...options});
|
accept(files, {fullTemplateTypeCheck: true, ...options});
|
||||||
|
|
|
@ -1038,6 +1038,25 @@ describe('Collector', () => {
|
||||||
expect(metadata).toBeUndefined();
|
expect(metadata).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should collect type guards', () => {
|
||||||
|
const metadata = collectSource(`
|
||||||
|
import {Directive, Input, TemplateRef} from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({selector: '[myIf]'})
|
||||||
|
export class MyIf {
|
||||||
|
|
||||||
|
constructor(private templateRef: TemplateRef) {}
|
||||||
|
|
||||||
|
@Input() myIf: any;
|
||||||
|
|
||||||
|
static typeGuard: <T>(v: T | null | undefined): v is T;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect((metadata.metadata.MyIf as any).statics.typeGuard)
|
||||||
|
.not.toBeUndefined('typeGuard was not collected');
|
||||||
|
});
|
||||||
|
|
||||||
it('should be able to collect an invalid access expression', () => {
|
it('should be able to collect an invalid access expression', () => {
|
||||||
const source = createSource(`
|
const source = createSource(`
|
||||||
import {Component} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
|
|
|
@ -271,7 +271,7 @@ export class AotCompiler {
|
||||||
const {template: parsedTemplate, pipes: usedPipes} =
|
const {template: parsedTemplate, pipes: usedPipes} =
|
||||||
this._parseTemplate(compMeta, moduleMeta, directives);
|
this._parseTemplate(compMeta, moduleMeta, directives);
|
||||||
ctx.statements.push(...this._typeCheckCompiler.compileComponent(
|
ctx.statements.push(...this._typeCheckCompiler.compileComponent(
|
||||||
componentId, compMeta, parsedTemplate, usedPipes, externalReferenceVars));
|
componentId, compMeta, parsedTemplate, usedPipes, externalReferenceVars, ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
emitMessageBundle(analyzeResult: NgAnalyzedModules, locale: string|null): MessageBundle {
|
emitMessageBundle(analyzeResult: NgAnalyzedModules, locale: string|null): MessageBundle {
|
||||||
|
|
|
@ -29,6 +29,7 @@ const IGNORE = {
|
||||||
const USE_VALUE = 'useValue';
|
const USE_VALUE = 'useValue';
|
||||||
const PROVIDE = 'provide';
|
const PROVIDE = 'provide';
|
||||||
const REFERENCE_SET = new Set([USE_VALUE, 'useFactory', 'data']);
|
const REFERENCE_SET = new Set([USE_VALUE, 'useFactory', 'data']);
|
||||||
|
const TYPEGUARD_POSTFIX = 'TypeGuard';
|
||||||
|
|
||||||
function shouldIgnore(value: any): boolean {
|
function shouldIgnore(value: any): boolean {
|
||||||
return value && value.__symbolic == 'ignore';
|
return value && value.__symbolic == 'ignore';
|
||||||
|
@ -43,6 +44,7 @@ export class StaticReflector implements CompileReflector {
|
||||||
private propertyCache = new Map<StaticSymbol, {[key: string]: any[]}>();
|
private propertyCache = new Map<StaticSymbol, {[key: string]: any[]}>();
|
||||||
private parameterCache = new Map<StaticSymbol, any[]>();
|
private parameterCache = new Map<StaticSymbol, any[]>();
|
||||||
private methodCache = new Map<StaticSymbol, {[key: string]: boolean}>();
|
private methodCache = new Map<StaticSymbol, {[key: string]: boolean}>();
|
||||||
|
private staticCache = new Map<StaticSymbol, string[]>();
|
||||||
private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
|
private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
|
||||||
private injectionToken: StaticSymbol;
|
private injectionToken: StaticSymbol;
|
||||||
private opaqueToken: StaticSymbol;
|
private opaqueToken: StaticSymbol;
|
||||||
|
@ -251,6 +253,18 @@ export class StaticReflector implements CompileReflector {
|
||||||
return methodNames;
|
return methodNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _staticMembers(type: StaticSymbol): string[] {
|
||||||
|
let staticMembers = this.staticCache.get(type);
|
||||||
|
if (!staticMembers) {
|
||||||
|
const classMetadata = this.getTypeMetadata(type);
|
||||||
|
const staticMemberData = classMetadata['statics'] || {};
|
||||||
|
staticMembers = Object.keys(staticMemberData);
|
||||||
|
this.staticCache.set(type, staticMembers);
|
||||||
|
}
|
||||||
|
return staticMembers;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private findParentType(type: StaticSymbol, classMetadata: any): StaticSymbol|undefined {
|
private findParentType(type: StaticSymbol, classMetadata: any): StaticSymbol|undefined {
|
||||||
const parentType = this.trySimplify(type, classMetadata['extends']);
|
const parentType = this.trySimplify(type, classMetadata['extends']);
|
||||||
if (parentType instanceof StaticSymbol) {
|
if (parentType instanceof StaticSymbol) {
|
||||||
|
@ -273,6 +287,21 @@ export class StaticReflector implements CompileReflector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guards(type: any): {[key: string]: StaticSymbol} {
|
||||||
|
if (!(type instanceof StaticSymbol)) {
|
||||||
|
this.reportError(
|
||||||
|
new Error(`guards received ${JSON.stringify(type)} which is not a StaticSymbol`), type);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const staticMembers = this._staticMembers(type);
|
||||||
|
const result: {[key: string]: StaticSymbol} = {};
|
||||||
|
for (let name of staticMembers) {
|
||||||
|
result[name.substr(0, name.length - TYPEGUARD_POSTFIX.length)] =
|
||||||
|
this.getStaticSymbol(type.filePath, type.name, [name]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private _registerDecoratorOrConstructor(type: StaticSymbol, ctor: any): void {
|
private _registerDecoratorOrConstructor(type: StaticSymbol, ctor: any): void {
|
||||||
this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => new ctor(...args));
|
this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => new ctor(...args));
|
||||||
}
|
}
|
||||||
|
|
|
@ -254,6 +254,7 @@ export interface CompileDirectiveSummary extends CompileTypeSummary {
|
||||||
providers: CompileProviderMetadata[];
|
providers: CompileProviderMetadata[];
|
||||||
viewProviders: CompileProviderMetadata[];
|
viewProviders: CompileProviderMetadata[];
|
||||||
queries: CompileQueryMetadata[];
|
queries: CompileQueryMetadata[];
|
||||||
|
guards: {[key: string]: any};
|
||||||
viewQueries: CompileQueryMetadata[];
|
viewQueries: CompileQueryMetadata[];
|
||||||
entryComponents: CompileEntryComponentMetadata[];
|
entryComponents: CompileEntryComponentMetadata[];
|
||||||
changeDetection: ChangeDetectionStrategy|null;
|
changeDetection: ChangeDetectionStrategy|null;
|
||||||
|
@ -268,8 +269,8 @@ export interface CompileDirectiveSummary extends CompileTypeSummary {
|
||||||
*/
|
*/
|
||||||
export class CompileDirectiveMetadata {
|
export class CompileDirectiveMetadata {
|
||||||
static create({isHost, type, isComponent, selector, exportAs, changeDetection, inputs, outputs,
|
static create({isHost, type, isComponent, selector, exportAs, changeDetection, inputs, outputs,
|
||||||
host, providers, viewProviders, queries, viewQueries, entryComponents, template,
|
host, providers, viewProviders, queries, guards, viewQueries, entryComponents,
|
||||||
componentViewType, rendererType, componentFactory}: {
|
template, componentViewType, rendererType, componentFactory}: {
|
||||||
isHost: boolean,
|
isHost: boolean,
|
||||||
type: CompileTypeMetadata,
|
type: CompileTypeMetadata,
|
||||||
isComponent: boolean,
|
isComponent: boolean,
|
||||||
|
@ -282,6 +283,7 @@ export class CompileDirectiveMetadata {
|
||||||
providers: CompileProviderMetadata[],
|
providers: CompileProviderMetadata[],
|
||||||
viewProviders: CompileProviderMetadata[],
|
viewProviders: CompileProviderMetadata[],
|
||||||
queries: CompileQueryMetadata[],
|
queries: CompileQueryMetadata[],
|
||||||
|
guards: {[key: string]: any};
|
||||||
viewQueries: CompileQueryMetadata[],
|
viewQueries: CompileQueryMetadata[],
|
||||||
entryComponents: CompileEntryComponentMetadata[],
|
entryComponents: CompileEntryComponentMetadata[],
|
||||||
template: CompileTemplateMetadata,
|
template: CompileTemplateMetadata,
|
||||||
|
@ -336,6 +338,7 @@ export class CompileDirectiveMetadata {
|
||||||
providers,
|
providers,
|
||||||
viewProviders,
|
viewProviders,
|
||||||
queries,
|
queries,
|
||||||
|
guards,
|
||||||
viewQueries,
|
viewQueries,
|
||||||
entryComponents,
|
entryComponents,
|
||||||
template,
|
template,
|
||||||
|
@ -358,6 +361,7 @@ export class CompileDirectiveMetadata {
|
||||||
providers: CompileProviderMetadata[];
|
providers: CompileProviderMetadata[];
|
||||||
viewProviders: CompileProviderMetadata[];
|
viewProviders: CompileProviderMetadata[];
|
||||||
queries: CompileQueryMetadata[];
|
queries: CompileQueryMetadata[];
|
||||||
|
guards: {[key: string]: any};
|
||||||
viewQueries: CompileQueryMetadata[];
|
viewQueries: CompileQueryMetadata[];
|
||||||
entryComponents: CompileEntryComponentMetadata[];
|
entryComponents: CompileEntryComponentMetadata[];
|
||||||
|
|
||||||
|
@ -367,10 +371,27 @@ export class CompileDirectiveMetadata {
|
||||||
rendererType: StaticSymbol|object|null;
|
rendererType: StaticSymbol|object|null;
|
||||||
componentFactory: StaticSymbol|object|null;
|
componentFactory: StaticSymbol|object|null;
|
||||||
|
|
||||||
constructor({isHost, type, isComponent, selector, exportAs,
|
constructor({isHost,
|
||||||
changeDetection, inputs, outputs, hostListeners, hostProperties,
|
type,
|
||||||
hostAttributes, providers, viewProviders, queries, viewQueries,
|
isComponent,
|
||||||
entryComponents, template, componentViewType, rendererType, componentFactory}: {
|
selector,
|
||||||
|
exportAs,
|
||||||
|
changeDetection,
|
||||||
|
inputs,
|
||||||
|
outputs,
|
||||||
|
hostListeners,
|
||||||
|
hostProperties,
|
||||||
|
hostAttributes,
|
||||||
|
providers,
|
||||||
|
viewProviders,
|
||||||
|
queries,
|
||||||
|
guards,
|
||||||
|
viewQueries,
|
||||||
|
entryComponents,
|
||||||
|
template,
|
||||||
|
componentViewType,
|
||||||
|
rendererType,
|
||||||
|
componentFactory}: {
|
||||||
isHost: boolean,
|
isHost: boolean,
|
||||||
type: CompileTypeMetadata,
|
type: CompileTypeMetadata,
|
||||||
isComponent: boolean,
|
isComponent: boolean,
|
||||||
|
@ -385,6 +406,7 @@ export class CompileDirectiveMetadata {
|
||||||
providers: CompileProviderMetadata[],
|
providers: CompileProviderMetadata[],
|
||||||
viewProviders: CompileProviderMetadata[],
|
viewProviders: CompileProviderMetadata[],
|
||||||
queries: CompileQueryMetadata[],
|
queries: CompileQueryMetadata[],
|
||||||
|
guards: {[key: string]: any},
|
||||||
viewQueries: CompileQueryMetadata[],
|
viewQueries: CompileQueryMetadata[],
|
||||||
entryComponents: CompileEntryComponentMetadata[],
|
entryComponents: CompileEntryComponentMetadata[],
|
||||||
template: CompileTemplateMetadata|null,
|
template: CompileTemplateMetadata|null,
|
||||||
|
@ -406,6 +428,7 @@ export class CompileDirectiveMetadata {
|
||||||
this.providers = _normalizeArray(providers);
|
this.providers = _normalizeArray(providers);
|
||||||
this.viewProviders = _normalizeArray(viewProviders);
|
this.viewProviders = _normalizeArray(viewProviders);
|
||||||
this.queries = _normalizeArray(queries);
|
this.queries = _normalizeArray(queries);
|
||||||
|
this.guards = guards;
|
||||||
this.viewQueries = _normalizeArray(viewQueries);
|
this.viewQueries = _normalizeArray(viewQueries);
|
||||||
this.entryComponents = _normalizeArray(entryComponents);
|
this.entryComponents = _normalizeArray(entryComponents);
|
||||||
this.template = template;
|
this.template = template;
|
||||||
|
@ -430,6 +453,7 @@ export class CompileDirectiveMetadata {
|
||||||
providers: this.providers,
|
providers: this.providers,
|
||||||
viewProviders: this.viewProviders,
|
viewProviders: this.viewProviders,
|
||||||
queries: this.queries,
|
queries: this.queries,
|
||||||
|
guards: this.guards,
|
||||||
viewQueries: this.viewQueries,
|
viewQueries: this.viewQueries,
|
||||||
entryComponents: this.entryComponents,
|
entryComponents: this.entryComponents,
|
||||||
changeDetection: this.changeDetection,
|
changeDetection: this.changeDetection,
|
||||||
|
|
|
@ -17,6 +17,7 @@ export abstract class CompileReflector {
|
||||||
abstract annotations(typeOrFunc: /*Type*/ any): any[];
|
abstract annotations(typeOrFunc: /*Type*/ any): any[];
|
||||||
abstract propMetadata(typeOrFunc: /*Type*/ any): {[key: string]: any[]};
|
abstract propMetadata(typeOrFunc: /*Type*/ any): {[key: string]: any[]};
|
||||||
abstract hasLifecycleHook(type: any, lcProperty: string): boolean;
|
abstract hasLifecycleHook(type: any, lcProperty: string): boolean;
|
||||||
|
abstract guards(typeOrFunc: /* Type */ any): {[key: string]: any};
|
||||||
abstract componentModuleUrl(type: /*Type*/ any, cmpMetadata: Component): string;
|
abstract componentModuleUrl(type: /*Type*/ any, cmpMetadata: Component): string;
|
||||||
abstract resolveExternalReference(ref: o.ExternalReference): any;
|
abstract resolveExternalReference(ref: o.ExternalReference): any;
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,6 +90,14 @@ export class ConvertPropertyBindingResult {
|
||||||
constructor(public stmts: o.Statement[], public currValExpr: o.Expression) {}
|
constructor(public stmts: o.Statement[], public currValExpr: o.Expression) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum BindingForm {
|
||||||
|
// The general form of binding expression, supports all expressions.
|
||||||
|
General,
|
||||||
|
|
||||||
|
// Try to generate a simple binding (no temporaries or statements)
|
||||||
|
// otherise generate a general binding
|
||||||
|
TrySimple,
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Converts the given expression AST into an executable output AST, assuming the expression
|
* Converts the given expression AST into an executable output AST, assuming the expression
|
||||||
* is used in property binding. The expression has to be preprocessed via
|
* is used in property binding. The expression has to be preprocessed via
|
||||||
|
@ -97,7 +105,8 @@ export class ConvertPropertyBindingResult {
|
||||||
*/
|
*/
|
||||||
export function convertPropertyBinding(
|
export function convertPropertyBinding(
|
||||||
localResolver: LocalResolver | null, implicitReceiver: o.Expression,
|
localResolver: LocalResolver | null, implicitReceiver: o.Expression,
|
||||||
expressionWithoutBuiltins: cdAst.AST, bindingId: string): ConvertPropertyBindingResult {
|
expressionWithoutBuiltins: cdAst.AST, bindingId: string,
|
||||||
|
form: BindingForm): ConvertPropertyBindingResult {
|
||||||
if (!localResolver) {
|
if (!localResolver) {
|
||||||
localResolver = new DefaultLocalResolver();
|
localResolver = new DefaultLocalResolver();
|
||||||
}
|
}
|
||||||
|
@ -110,6 +119,8 @@ export function convertPropertyBinding(
|
||||||
for (let i = 0; i < visitor.temporaryCount; i++) {
|
for (let i = 0; i < visitor.temporaryCount; i++) {
|
||||||
stmts.push(temporaryDeclaration(bindingId, i));
|
stmts.push(temporaryDeclaration(bindingId, i));
|
||||||
}
|
}
|
||||||
|
} else if (form == BindingForm.TrySimple) {
|
||||||
|
return new ConvertPropertyBindingResult([], outputExpr);
|
||||||
}
|
}
|
||||||
|
|
||||||
stmts.push(currValExpr.set(outputExpr).toDeclStmt(null, [o.StmtModifier.Final]));
|
stmts.push(currValExpr.set(outputExpr).toDeclStmt(null, [o.StmtModifier.Final]));
|
||||||
|
|
|
@ -51,6 +51,7 @@ export interface Directive {
|
||||||
providers?: Provider[];
|
providers?: Provider[];
|
||||||
exportAs?: string;
|
exportAs?: string;
|
||||||
queries?: {[key: string]: any};
|
queries?: {[key: string]: any};
|
||||||
|
guards?: {[key: string]: any};
|
||||||
}
|
}
|
||||||
export const createDirective =
|
export const createDirective =
|
||||||
makeMetadataFactory<Directive>('Directive', (dir: Directive = {}) => dir);
|
makeMetadataFactory<Directive>('Directive', (dir: Directive = {}) => dir);
|
||||||
|
|
|
@ -44,7 +44,8 @@ export class DirectiveResolver {
|
||||||
const metadata = findLast(typeMetadata, isDirectiveMetadata);
|
const metadata = findLast(typeMetadata, isDirectiveMetadata);
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
const propertyMetadata = this._reflector.propMetadata(type);
|
const propertyMetadata = this._reflector.propMetadata(type);
|
||||||
return this._mergeWithPropertyMetadata(metadata, propertyMetadata, type);
|
const guards = this._reflector.guards(type);
|
||||||
|
return this._mergeWithPropertyMetadata(metadata, propertyMetadata, guards, type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,12 +57,12 @@ export class DirectiveResolver {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _mergeWithPropertyMetadata(
|
private _mergeWithPropertyMetadata(
|
||||||
dm: Directive, propertyMetadata: {[key: string]: any[]}, directiveType: Type): Directive {
|
dm: Directive, propertyMetadata: {[key: string]: any[]}, guards: {[key: string]: any},
|
||||||
|
directiveType: Type): Directive {
|
||||||
const inputs: string[] = [];
|
const inputs: string[] = [];
|
||||||
const outputs: string[] = [];
|
const outputs: string[] = [];
|
||||||
const host: {[key: string]: string} = {};
|
const host: {[key: string]: string} = {};
|
||||||
const queries: {[key: string]: any} = {};
|
const queries: {[key: string]: any} = {};
|
||||||
|
|
||||||
Object.keys(propertyMetadata).forEach((propName: string) => {
|
Object.keys(propertyMetadata).forEach((propName: string) => {
|
||||||
const input = findLast(propertyMetadata[propName], (a) => createInput.isTypeOf(a));
|
const input = findLast(propertyMetadata[propName], (a) => createInput.isTypeOf(a));
|
||||||
if (input) {
|
if (input) {
|
||||||
|
@ -105,18 +106,20 @@ export class DirectiveResolver {
|
||||||
queries[propName] = query;
|
queries[propName] = query;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return this._merge(dm, inputs, outputs, host, queries, directiveType);
|
return this._merge(dm, inputs, outputs, host, queries, guards, directiveType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _extractPublicName(def: string) { return splitAtColon(def, [null !, def])[1].trim(); }
|
private _extractPublicName(def: string) { return splitAtColon(def, [null !, def])[1].trim(); }
|
||||||
|
|
||||||
private _dedupeBindings(bindings: string[]): string[] {
|
private _dedupeBindings(bindings: string[]): string[] {
|
||||||
const names = new Set<string>();
|
const names = new Set<string>();
|
||||||
|
const publicNames = new Set<string>();
|
||||||
const reversedResult: string[] = [];
|
const reversedResult: string[] = [];
|
||||||
// go last to first to allow later entries to overwrite previous entries
|
// go last to first to allow later entries to overwrite previous entries
|
||||||
for (let i = bindings.length - 1; i >= 0; i--) {
|
for (let i = bindings.length - 1; i >= 0; i--) {
|
||||||
const binding = bindings[i];
|
const binding = bindings[i];
|
||||||
const name = this._extractPublicName(binding);
|
const name = this._extractPublicName(binding);
|
||||||
|
publicNames.add(name);
|
||||||
if (!names.has(name)) {
|
if (!names.has(name)) {
|
||||||
names.add(name);
|
names.add(name);
|
||||||
reversedResult.push(binding);
|
reversedResult.push(binding);
|
||||||
|
@ -127,14 +130,13 @@ export class DirectiveResolver {
|
||||||
|
|
||||||
private _merge(
|
private _merge(
|
||||||
directive: Directive, inputs: string[], outputs: string[], host: {[key: string]: string},
|
directive: Directive, inputs: string[], outputs: string[], host: {[key: string]: string},
|
||||||
queries: {[key: string]: any}, directiveType: Type): Directive {
|
queries: {[key: string]: any}, guards: {[key: string]: any}, directiveType: Type): Directive {
|
||||||
const mergedInputs =
|
const mergedInputs =
|
||||||
this._dedupeBindings(directive.inputs ? directive.inputs.concat(inputs) : inputs);
|
this._dedupeBindings(directive.inputs ? directive.inputs.concat(inputs) : inputs);
|
||||||
const mergedOutputs =
|
const mergedOutputs =
|
||||||
this._dedupeBindings(directive.outputs ? directive.outputs.concat(outputs) : outputs);
|
this._dedupeBindings(directive.outputs ? directive.outputs.concat(outputs) : outputs);
|
||||||
const mergedHost = directive.host ? {...directive.host, ...host} : host;
|
const mergedHost = directive.host ? {...directive.host, ...host} : host;
|
||||||
const mergedQueries = directive.queries ? {...directive.queries, ...queries} : queries;
|
const mergedQueries = directive.queries ? {...directive.queries, ...queries} : queries;
|
||||||
|
|
||||||
if (createComponent.isTypeOf(directive)) {
|
if (createComponent.isTypeOf(directive)) {
|
||||||
const comp = directive as Component;
|
const comp = directive as Component;
|
||||||
return createComponent({
|
return createComponent({
|
||||||
|
@ -166,7 +168,7 @@ export class DirectiveResolver {
|
||||||
host: mergedHost,
|
host: mergedHost,
|
||||||
exportAs: directive.exportAs,
|
exportAs: directive.exportAs,
|
||||||
queries: mergedQueries,
|
queries: mergedQueries,
|
||||||
providers: directive.providers
|
providers: directive.providers, guards
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -208,6 +208,7 @@ export class CompileMetadataResolver {
|
||||||
providers: [],
|
providers: [],
|
||||||
viewProviders: [],
|
viewProviders: [],
|
||||||
queries: [],
|
queries: [],
|
||||||
|
guards: {},
|
||||||
viewQueries: [],
|
viewQueries: [],
|
||||||
componentViewType: hostViewType,
|
componentViewType: hostViewType,
|
||||||
rendererType:
|
rendererType:
|
||||||
|
@ -240,6 +241,7 @@ export class CompileMetadataResolver {
|
||||||
providers: metadata.providers,
|
providers: metadata.providers,
|
||||||
viewProviders: metadata.viewProviders,
|
viewProviders: metadata.viewProviders,
|
||||||
queries: metadata.queries,
|
queries: metadata.queries,
|
||||||
|
guards: metadata.guards,
|
||||||
viewQueries: metadata.viewQueries,
|
viewQueries: metadata.viewQueries,
|
||||||
entryComponents: metadata.entryComponents,
|
entryComponents: metadata.entryComponents,
|
||||||
componentViewType: metadata.componentViewType,
|
componentViewType: metadata.componentViewType,
|
||||||
|
@ -383,6 +385,7 @@ export class CompileMetadataResolver {
|
||||||
providers: providers || [],
|
providers: providers || [],
|
||||||
viewProviders: viewProviders || [],
|
viewProviders: viewProviders || [],
|
||||||
queries: queries || [],
|
queries: queries || [],
|
||||||
|
guards: dirMeta.guards || {},
|
||||||
viewQueries: viewQueries || [],
|
viewQueries: viewQueries || [],
|
||||||
entryComponents: entryComponentMetadata,
|
entryComponents: entryComponentMetadata,
|
||||||
componentViewType: nonNormalizedTemplateMetadata ? this.getComponentViewClass(directiveType) :
|
componentViewType: nonNormalizedTemplateMetadata ? this.getComponentViewClass(directiveType) :
|
||||||
|
|
|
@ -10,13 +10,15 @@ import {AotCompilerOptions} from '../aot/compiler_options';
|
||||||
import {StaticReflector} from '../aot/static_reflector';
|
import {StaticReflector} from '../aot/static_reflector';
|
||||||
import {StaticSymbol} from '../aot/static_symbol';
|
import {StaticSymbol} from '../aot/static_symbol';
|
||||||
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompilePipeSummary} from '../compile_metadata';
|
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompilePipeSummary} from '../compile_metadata';
|
||||||
import {BuiltinConverter, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
|
import {BindingForm, BuiltinConverter, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
|
||||||
import {AST, ASTWithSource, Interpolation} from '../expression_parser/ast';
|
import {AST, ASTWithSource, Interpolation} from '../expression_parser/ast';
|
||||||
import {Identifiers} from '../identifiers';
|
import {Identifiers} from '../identifiers';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
import {convertValueToOutputAst} from '../output/value_util';
|
import {convertValueToOutputAst} from '../output/value_util';
|
||||||
import {ParseSourceSpan} from '../parse_util';
|
import {ParseSourceSpan} from '../parse_util';
|
||||||
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAst, ProviderAstType, QueryMatch, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
|
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAst, ProviderAstType, QueryMatch, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
|
||||||
|
import {OutputContext} from '../util';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates code that is used to type check templates.
|
* Generates code that is used to type check templates.
|
||||||
|
@ -34,27 +36,33 @@ export class TypeCheckCompiler {
|
||||||
*/
|
*/
|
||||||
compileComponent(
|
compileComponent(
|
||||||
componentId: string, component: CompileDirectiveMetadata, template: TemplateAst[],
|
componentId: string, component: CompileDirectiveMetadata, template: TemplateAst[],
|
||||||
usedPipes: CompilePipeSummary[],
|
usedPipes: CompilePipeSummary[], externalReferenceVars: Map<StaticSymbol, string>,
|
||||||
externalReferenceVars: Map<StaticSymbol, string>): o.Statement[] {
|
ctx: OutputContext): o.Statement[] {
|
||||||
const pipes = new Map<string, StaticSymbol>();
|
const pipes = new Map<string, StaticSymbol>();
|
||||||
usedPipes.forEach(p => pipes.set(p.name, p.type.reference));
|
usedPipes.forEach(p => pipes.set(p.name, p.type.reference));
|
||||||
let embeddedViewCount = 0;
|
let embeddedViewCount = 0;
|
||||||
const viewBuilderFactory = (parent: ViewBuilder | null): ViewBuilder => {
|
const viewBuilderFactory =
|
||||||
const embeddedViewIndex = embeddedViewCount++;
|
(parent: ViewBuilder | null, guards: GuardExpression[]): ViewBuilder => {
|
||||||
return new ViewBuilder(
|
const embeddedViewIndex = embeddedViewCount++;
|
||||||
this.options, this.reflector, externalReferenceVars, parent, component.type.reference,
|
return new ViewBuilder(
|
||||||
component.isHost, embeddedViewIndex, pipes, viewBuilderFactory);
|
this.options, this.reflector, externalReferenceVars, parent, component.type.reference,
|
||||||
};
|
component.isHost, embeddedViewIndex, pipes, guards, ctx, viewBuilderFactory);
|
||||||
|
};
|
||||||
|
|
||||||
const visitor = viewBuilderFactory(null);
|
const visitor = viewBuilderFactory(null, []);
|
||||||
visitor.visitAll([], template);
|
visitor.visitAll([], template);
|
||||||
|
|
||||||
return visitor.build(componentId);
|
return visitor.build(componentId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GuardExpression {
|
||||||
|
guard: StaticSymbol;
|
||||||
|
expression: Expression;
|
||||||
|
}
|
||||||
|
|
||||||
interface ViewBuilderFactory {
|
interface ViewBuilderFactory {
|
||||||
(parent: ViewBuilder): ViewBuilder;
|
(parent: ViewBuilder, guards: GuardExpression[]): ViewBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: This is used as key in Map and should therefore be
|
// Note: This is used as key in Map and should therefore be
|
||||||
|
@ -94,6 +102,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
private externalReferenceVars: Map<StaticSymbol, string>, private parent: ViewBuilder|null,
|
private externalReferenceVars: Map<StaticSymbol, string>, private parent: ViewBuilder|null,
|
||||||
private component: StaticSymbol, private isHostComponent: boolean,
|
private component: StaticSymbol, private isHostComponent: boolean,
|
||||||
private embeddedViewIndex: number, private pipes: Map<string, StaticSymbol>,
|
private embeddedViewIndex: number, private pipes: Map<string, StaticSymbol>,
|
||||||
|
private guards: GuardExpression[], private ctx: OutputContext,
|
||||||
private viewBuilderFactory: ViewBuilderFactory) {}
|
private viewBuilderFactory: ViewBuilderFactory) {}
|
||||||
|
|
||||||
private getOutputVar(type: o.BuiltinTypeName|StaticSymbol): string {
|
private getOutputVar(type: o.BuiltinTypeName|StaticSymbol): string {
|
||||||
|
@ -112,6 +121,20 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
return varName;
|
return varName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getTypeGuardExpressions(ast: EmbeddedTemplateAst): GuardExpression[] {
|
||||||
|
const result = [...this.guards];
|
||||||
|
for (let directive of ast.directives) {
|
||||||
|
for (let input of directive.inputs) {
|
||||||
|
const guard = directive.directive.guards[input.directiveName];
|
||||||
|
if (guard) {
|
||||||
|
result.push(
|
||||||
|
{guard, expression: {context: this.component, value: input.value} as Expression});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
visitAll(variables: VariableAst[], astNodes: TemplateAst[]) {
|
visitAll(variables: VariableAst[], astNodes: TemplateAst[]) {
|
||||||
this.variables = variables;
|
this.variables = variables;
|
||||||
templateVisitAll(this, astNodes);
|
templateVisitAll(this, astNodes);
|
||||||
|
@ -119,7 +142,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
|
|
||||||
build(componentId: string, targetStatements: o.Statement[] = []): o.Statement[] {
|
build(componentId: string, targetStatements: o.Statement[] = []): o.Statement[] {
|
||||||
this.children.forEach((child) => child.build(componentId, targetStatements));
|
this.children.forEach((child) => child.build(componentId, targetStatements));
|
||||||
const viewStmts: o.Statement[] =
|
let viewStmts: o.Statement[] =
|
||||||
[o.variable(DYNAMIC_VAR_NAME).set(o.NULL_EXPR).toDeclStmt(o.DYNAMIC_TYPE)];
|
[o.variable(DYNAMIC_VAR_NAME).set(o.NULL_EXPR).toDeclStmt(o.DYNAMIC_TYPE)];
|
||||||
let bindingCount = 0;
|
let bindingCount = 0;
|
||||||
this.updates.forEach((expression) => {
|
this.updates.forEach((expression) => {
|
||||||
|
@ -127,7 +150,8 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
const bindingId = `${bindingCount++}`;
|
const bindingId = `${bindingCount++}`;
|
||||||
const nameResolver = context === this.component ? this : defaultResolver;
|
const nameResolver = context === this.component ? this : defaultResolver;
|
||||||
const {stmts, currValExpr} = convertPropertyBinding(
|
const {stmts, currValExpr} = convertPropertyBinding(
|
||||||
nameResolver, o.variable(this.getOutputVar(context)), value, bindingId);
|
nameResolver, o.variable(this.getOutputVar(context)), value, bindingId,
|
||||||
|
BindingForm.General);
|
||||||
stmts.push(new o.ExpressionStatement(currValExpr));
|
stmts.push(new o.ExpressionStatement(currValExpr));
|
||||||
viewStmts.push(...stmts.map(
|
viewStmts.push(...stmts.map(
|
||||||
(stmt: o.Statement) => o.applySourceSpanToStatementIfNeeded(stmt, sourceSpan)));
|
(stmt: o.Statement) => o.applySourceSpanToStatementIfNeeded(stmt, sourceSpan)));
|
||||||
|
@ -142,6 +166,27 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
(stmt: o.Statement) => o.applySourceSpanToStatementIfNeeded(stmt, sourceSpan)));
|
(stmt: o.Statement) => o.applySourceSpanToStatementIfNeeded(stmt, sourceSpan)));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (this.guards.length) {
|
||||||
|
let guardExpression: o.Expression|undefined = undefined;
|
||||||
|
for (const guard of this.guards) {
|
||||||
|
const {context, value} = this.preprocessUpdateExpression(guard.expression);
|
||||||
|
const bindingId = `${bindingCount++}`;
|
||||||
|
const nameResolver = context === this.component ? this : defaultResolver;
|
||||||
|
// We only support support simple expressions and ignore others as they
|
||||||
|
// are unlikely to affect type narrowing.
|
||||||
|
const {stmts, currValExpr} = convertPropertyBinding(
|
||||||
|
nameResolver, o.variable(this.getOutputVar(context)), value, bindingId,
|
||||||
|
BindingForm.TrySimple);
|
||||||
|
if (stmts.length == 0) {
|
||||||
|
const callGuard = this.ctx.importExpr(guard.guard).callFn([currValExpr]);
|
||||||
|
guardExpression = guardExpression ? guardExpression.and(callGuard) : callGuard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (guardExpression) {
|
||||||
|
viewStmts = [new o.IfStmt(guardExpression, viewStmts)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const viewName = `_View_${componentId}_${this.embeddedViewIndex}`;
|
const viewName = `_View_${componentId}_${this.embeddedViewIndex}`;
|
||||||
const viewFactory = new o.DeclareFunctionStmt(viewName, [], viewStmts);
|
const viewFactory = new o.DeclareFunctionStmt(viewName, [], viewStmts);
|
||||||
targetStatements.push(viewFactory);
|
targetStatements.push(viewFactory);
|
||||||
|
@ -163,7 +208,12 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
// for the context in any embedded view.
|
// for the context in any embedded view.
|
||||||
// We keep this behaivor behind a flag for now.
|
// We keep this behaivor behind a flag for now.
|
||||||
if (this.options.fullTemplateTypeCheck) {
|
if (this.options.fullTemplateTypeCheck) {
|
||||||
const childVisitor = this.viewBuilderFactory(this);
|
// Find any applicable type guards. For example, NgIf has a type guard on ngIf
|
||||||
|
// (see NgIf.ngIfTypeGuard) that can be used to indicate that a template is only
|
||||||
|
// stamped out if ngIf is truthy so any bindings in the template can assume that,
|
||||||
|
// if a nullable type is used for ngIf, that expression is not null or undefined.
|
||||||
|
const guards = this.getTypeGuardExpressions(ast);
|
||||||
|
const childVisitor = this.viewBuilderFactory(this, guards);
|
||||||
this.children.push(childVisitor);
|
this.children.push(childVisitor);
|
||||||
childVisitor.visitAll(ast.variables, ast.children);
|
childVisitor.visitAll(ast.variables, ast.children);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import {CompileDirectiveMetadata, CompilePipeSummary, rendererTypeName, tokenReference, viewClassName} from '../compile_metadata';
|
import {CompileDirectiveMetadata, CompilePipeSummary, rendererTypeName, tokenReference, viewClassName} from '../compile_metadata';
|
||||||
import {CompileReflector} from '../compile_reflector';
|
import {CompileReflector} from '../compile_reflector';
|
||||||
import {BuiltinConverter, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
|
import {BindingForm, BuiltinConverter, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
|
||||||
import {ArgumentType, BindingFlags, ChangeDetectionStrategy, NodeFlags, QueryBindingType, QueryValueType, ViewFlags} from '../core';
|
import {ArgumentType, BindingFlags, ChangeDetectionStrategy, NodeFlags, QueryBindingType, QueryValueType, ViewFlags} from '../core';
|
||||||
import {AST, ASTWithSource, Interpolation} from '../expression_parser/ast';
|
import {AST, ASTWithSource, Interpolation} from '../expression_parser/ast';
|
||||||
import {Identifiers} from '../identifiers';
|
import {Identifiers} from '../identifiers';
|
||||||
|
@ -859,7 +859,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
const bindingId = `${updateBindingCount++}`;
|
const bindingId = `${updateBindingCount++}`;
|
||||||
const nameResolver = context === COMP_VAR ? self : null;
|
const nameResolver = context === COMP_VAR ? self : null;
|
||||||
const {stmts, currValExpr} =
|
const {stmts, currValExpr} =
|
||||||
convertPropertyBinding(nameResolver, context, value, bindingId);
|
convertPropertyBinding(nameResolver, context, value, bindingId, BindingForm.General);
|
||||||
updateStmts.push(...stmts.map(
|
updateStmts.push(...stmts.map(
|
||||||
(stmt: o.Statement) => o.applySourceSpanToStatementIfNeeded(stmt, sourceSpan)));
|
(stmt: o.Statement) => o.applySourceSpanToStatementIfNeeded(stmt, sourceSpan)));
|
||||||
return o.applySourceSpanToExpressionIfNeeded(currValExpr, sourceSpan);
|
return o.applySourceSpanToExpressionIfNeeded(currValExpr, sourceSpan);
|
||||||
|
|
|
@ -126,6 +126,7 @@ export function main() {
|
||||||
outputs: [],
|
outputs: [],
|
||||||
host: {},
|
host: {},
|
||||||
queries: {},
|
queries: {},
|
||||||
|
guards: {},
|
||||||
exportAs: undefined,
|
exportAs: undefined,
|
||||||
providers: undefined
|
providers: undefined
|
||||||
}));
|
}));
|
||||||
|
@ -154,6 +155,7 @@ export function main() {
|
||||||
outputs: [],
|
outputs: [],
|
||||||
host: {},
|
host: {},
|
||||||
queries: {},
|
queries: {},
|
||||||
|
guards: {},
|
||||||
exportAs: undefined,
|
exportAs: undefined,
|
||||||
providers: undefined
|
providers: undefined
|
||||||
}));
|
}));
|
||||||
|
@ -164,6 +166,7 @@ export function main() {
|
||||||
outputs: [],
|
outputs: [],
|
||||||
host: {},
|
host: {},
|
||||||
queries: {},
|
queries: {},
|
||||||
|
guards: {},
|
||||||
exportAs: undefined,
|
exportAs: undefined,
|
||||||
providers: undefined
|
providers: undefined
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -38,28 +38,29 @@ function createTypeMeta({reference, diDeps}: {reference: any, diDeps?: any[]}):
|
||||||
return {reference: reference, diDeps: diDeps || [], lifecycleHooks: []};
|
return {reference: reference, diDeps: diDeps || [], lifecycleHooks: []};
|
||||||
}
|
}
|
||||||
|
|
||||||
function compileDirectiveMetadataCreate({isHost, type, isComponent, selector, exportAs,
|
function compileDirectiveMetadataCreate(
|
||||||
changeDetection, inputs, outputs, host, providers,
|
{isHost, type, isComponent, selector, exportAs, changeDetection, inputs, outputs, host,
|
||||||
viewProviders, queries, viewQueries, entryComponents,
|
providers, viewProviders, queries, guards, viewQueries, entryComponents, template,
|
||||||
template, componentViewType, rendererType}: {
|
componentViewType, rendererType}: {
|
||||||
isHost?: boolean,
|
isHost?: boolean,
|
||||||
type?: CompileTypeMetadata,
|
type?: CompileTypeMetadata,
|
||||||
isComponent?: boolean,
|
isComponent?: boolean,
|
||||||
selector?: string | null,
|
selector?: string | null,
|
||||||
exportAs?: string | null,
|
exportAs?: string | null,
|
||||||
changeDetection?: ChangeDetectionStrategy | null,
|
changeDetection?: ChangeDetectionStrategy | null,
|
||||||
inputs?: string[],
|
inputs?: string[],
|
||||||
outputs?: string[],
|
outputs?: string[],
|
||||||
host?: {[key: string]: string},
|
host?: {[key: string]: string},
|
||||||
providers?: CompileProviderMetadata[] | null,
|
providers?: CompileProviderMetadata[] | null,
|
||||||
viewProviders?: CompileProviderMetadata[] | null,
|
viewProviders?: CompileProviderMetadata[] | null,
|
||||||
queries?: CompileQueryMetadata[] | null,
|
queries?: CompileQueryMetadata[] | null,
|
||||||
viewQueries?: CompileQueryMetadata[],
|
guards?: {[key: string]: any},
|
||||||
entryComponents?: CompileEntryComponentMetadata[],
|
viewQueries?: CompileQueryMetadata[],
|
||||||
template?: CompileTemplateMetadata,
|
entryComponents?: CompileEntryComponentMetadata[],
|
||||||
componentViewType?: StaticSymbol | ProxyClass | null,
|
template?: CompileTemplateMetadata,
|
||||||
rendererType?: StaticSymbol | RendererType2 | null,
|
componentViewType?: StaticSymbol | ProxyClass | null,
|
||||||
}) {
|
rendererType?: StaticSymbol | RendererType2 | null,
|
||||||
|
}) {
|
||||||
return CompileDirectiveMetadata.create({
|
return CompileDirectiveMetadata.create({
|
||||||
isHost: !!isHost,
|
isHost: !!isHost,
|
||||||
type: noUndefined(type) !,
|
type: noUndefined(type) !,
|
||||||
|
@ -73,6 +74,7 @@ function compileDirectiveMetadataCreate({isHost, type, isComponent, selector, ex
|
||||||
providers: providers || [],
|
providers: providers || [],
|
||||||
viewProviders: viewProviders || [],
|
viewProviders: viewProviders || [],
|
||||||
queries: queries || [],
|
queries: queries || [],
|
||||||
|
guards: guards || {},
|
||||||
viewQueries: viewQueries || [],
|
viewQueries: viewQueries || [],
|
||||||
entryComponents: entryComponents || [],
|
entryComponents: entryComponents || [],
|
||||||
template: noUndefined(template) !,
|
template: noUndefined(template) !,
|
||||||
|
@ -390,6 +392,7 @@ export function main() {
|
||||||
providers: [],
|
providers: [],
|
||||||
viewProviders: [],
|
viewProviders: [],
|
||||||
queries: [],
|
queries: [],
|
||||||
|
guards: {},
|
||||||
viewQueries: [],
|
viewQueries: [],
|
||||||
entryComponents: [],
|
entryComponents: [],
|
||||||
componentViewType: null,
|
componentViewType: null,
|
||||||
|
|
|
@ -13,6 +13,7 @@ export interface PlatformReflectionCapabilities {
|
||||||
isReflectionEnabled(): boolean;
|
isReflectionEnabled(): boolean;
|
||||||
factory(type: Type<any>): Function;
|
factory(type: Type<any>): Function;
|
||||||
hasLifecycleHook(type: any, lcProperty: string): boolean;
|
hasLifecycleHook(type: any, lcProperty: string): boolean;
|
||||||
|
guards(type: any): {[key: string]: any};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a list of annotations/types for constructor parameters
|
* Return a list of annotations/types for constructor parameters
|
||||||
|
|
|
@ -207,6 +207,8 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
||||||
return type instanceof Type && lcProperty in type.prototype;
|
return type instanceof Type && lcProperty in type.prototype;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guards(type: any): {[key: string]: any} { return {}; }
|
||||||
|
|
||||||
getter(name: string): GetterFn { return <GetterFn>new Function('o', 'return o.' + name + ';'); }
|
getter(name: string): GetterFn { return <GetterFn>new Function('o', 'return o.' + name + ';'); }
|
||||||
|
|
||||||
setter(name: string): SetterFn {
|
setter(name: string): SetterFn {
|
||||||
|
|
|
@ -42,6 +42,7 @@ export class JitReflector implements CompileReflector {
|
||||||
hasLifecycleHook(type: any, lcProperty: string): boolean {
|
hasLifecycleHook(type: any, lcProperty: string): boolean {
|
||||||
return this.reflectionCapabilities.hasLifecycleHook(type, lcProperty);
|
return this.reflectionCapabilities.hasLifecycleHook(type, lcProperty);
|
||||||
}
|
}
|
||||||
|
guards(type: any): {[key: string]: any} { return this.reflectionCapabilities.guards(type); }
|
||||||
resolveExternalReference(ref: ExternalReference): any {
|
resolveExternalReference(ref: ExternalReference): any {
|
||||||
return builtinExternalReferences.get(ref) || ref.runtime;
|
return builtinExternalReferences.get(ref) || ref.runtime;
|
||||||
}
|
}
|
||||||
|
|
|
@ -276,6 +276,7 @@ export declare class NgIf {
|
||||||
ngIfElse: TemplateRef<NgIfContext>;
|
ngIfElse: TemplateRef<NgIfContext>;
|
||||||
ngIfThen: TemplateRef<NgIfContext>;
|
ngIfThen: TemplateRef<NgIfContext>;
|
||||||
constructor(_viewContainer: ViewContainerRef, templateRef: TemplateRef<NgIfContext>);
|
constructor(_viewContainer: ViewContainerRef, templateRef: TemplateRef<NgIfContext>);
|
||||||
|
static ngIfTypeGuard: <T>(v: T | null | undefined | false) => v is T;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @stable */
|
/** @stable */
|
||||||
|
|
Loading…
Reference in New Issue