fix(compiler): support lifecycle hooks in compiler_cli

This commit is contained in:
Tobias Bosch
2016-05-04 10:00:59 -07:00
parent bdce154282
commit 7150ace7c7
15 changed files with 143 additions and 102 deletions

View File

@ -1,21 +0,0 @@
library angular2.src.core.compiler.directive_lifecycle_reflector;
import 'package:angular2/src/core/reflection/reflection.dart';
import 'package:angular2/src/core/metadata/lifecycle_hooks.dart';
const INTERFACES = const {
LifecycleHooks.OnInit: OnInit,
LifecycleHooks.OnDestroy: OnDestroy,
LifecycleHooks.DoCheck: DoCheck,
LifecycleHooks.OnChanges: OnChanges,
LifecycleHooks.AfterContentInit: AfterContentInit,
LifecycleHooks.AfterContentChecked: AfterContentChecked,
LifecycleHooks.AfterViewInit: AfterViewInit,
LifecycleHooks.AfterViewChecked: AfterViewChecked,
};
bool hasLifecycleHook(LifecycleHooks interface, token) {
if (token is! Type) return false;
Type interfaceType = INTERFACES[interface];
return reflector.interfaces(token).contains(interfaceType);
}

View File

@ -1,31 +1,43 @@
import {
OnInit,
OnDestroy,
DoCheck,
OnChanges,
AfterContentInit,
AfterContentChecked,
AfterViewInit,
AfterViewChecked,
reflector
} from '@angular/core';
import {LifecycleHooks} from '../core_private'; import {LifecycleHooks} from '../core_private';
import {Type} from '../src/facade/lang'; import {Type} from '../src/facade/lang';
import {MapWrapper} from '../src/facade/collection';
const LIFECYCLE_INTERFACES: Map<any, Type> = MapWrapper.createFromPairs([
[LifecycleHooks.OnInit, OnInit],
[LifecycleHooks.OnDestroy, OnDestroy],
[LifecycleHooks.DoCheck, DoCheck],
[LifecycleHooks.OnChanges, OnChanges],
[LifecycleHooks.AfterContentInit, AfterContentInit],
[LifecycleHooks.AfterContentChecked, AfterContentChecked],
[LifecycleHooks.AfterViewInit, AfterViewInit],
[LifecycleHooks.AfterViewChecked, AfterViewChecked],
]);
export function hasLifecycleHook(lcInterface: LifecycleHooks, token): boolean { const LIFECYCLE_PROPS: Map<any, string> = MapWrapper.createFromPairs([
if (!(token instanceof Type)) return false; [LifecycleHooks.OnInit, 'ngOnInit'],
[LifecycleHooks.OnDestroy, 'ngOnDestroy'],
[LifecycleHooks.DoCheck, 'ngDoCheck'],
[LifecycleHooks.OnChanges, 'ngOnChanges'],
[LifecycleHooks.AfterContentInit, 'ngAfterContentInit'],
[LifecycleHooks.AfterContentChecked, 'ngAfterContentChecked'],
[LifecycleHooks.AfterViewInit, 'ngAfterViewInit'],
[LifecycleHooks.AfterViewChecked, 'ngAfterViewChecked'],
]);
var proto = (<any>token).prototype; export function hasLifecycleHook(hook: LifecycleHooks, token): boolean {
var lcInterface = LIFECYCLE_INTERFACES.get(hook);
switch (lcInterface) { var lcProp = LIFECYCLE_PROPS.get(hook);
case LifecycleHooks.AfterContentInit: return reflector.hasLifecycleHook(token, lcInterface, lcProp);
return !!proto.ngAfterContentInit;
case LifecycleHooks.AfterContentChecked:
return !!proto.ngAfterContentChecked;
case LifecycleHooks.AfterViewInit:
return !!proto.ngAfterViewInit;
case LifecycleHooks.AfterViewChecked:
return !!proto.ngAfterViewChecked;
case LifecycleHooks.OnChanges:
return !!proto.ngOnChanges;
case LifecycleHooks.DoCheck:
return !!proto.ngDoCheck;
case LifecycleHooks.OnDestroy:
return !!proto.ngOnDestroy;
case LifecycleHooks.OnInit:
return !!proto.ngOnInit;
default:
return false;
}
} }

View File

@ -159,7 +159,7 @@ export class CompileView implements NameResolver {
proxyParams.push(new o.FnParam(paramName)); proxyParams.push(new o.FnParam(paramName));
proxyReturnEntries.push(o.variable(paramName)); proxyReturnEntries.push(o.variable(paramName));
} }
createPureProxy(o.fn(proxyParams, [new o.ReturnStatement(o.literalArr(proxyReturnEntries))]), createPureProxy(o.fn(proxyParams, [new o.ReturnStatement(o.literalArr(proxyReturnEntries))], new o.ArrayType(o.DYNAMIC_TYPE)),
values.length, proxyExpr, this); values.length, proxyExpr, this);
return proxyExpr.callFn(values); return proxyExpr.callFn(values);
} }
@ -178,7 +178,7 @@ export class CompileView implements NameResolver {
proxyReturnEntries.push([entries[i][0], o.variable(paramName)]); proxyReturnEntries.push([entries[i][0], o.variable(paramName)]);
values.push(<o.Expression>entries[i][1]); values.push(<o.Expression>entries[i][1]);
} }
createPureProxy(o.fn(proxyParams, [new o.ReturnStatement(o.literalMap(proxyReturnEntries))]), createPureProxy(o.fn(proxyParams, [new o.ReturnStatement(o.literalMap(proxyReturnEntries))], new o.MapType(o.DYNAMIC_TYPE)),
entries.length, proxyExpr, this); entries.length, proxyExpr, this);
return proxyExpr.callFn(values); return proxyExpr.callFn(values);
} }

View File

@ -83,12 +83,12 @@ Run the compiler from source:
# Build angular2 and the compiler # Build angular2 and the compiler
./node_modules/.bin/tsc -p modules ./node_modules/.bin/tsc -p modules
# Run it on the test project # Run it on the test project
$ ./integrationtest.sh $ ./modules/@angular/compiler_cli./integrationtest.sh
``` ```
Release: Release:
``` ```
$ ./integrationtest.sh $ ./modules/@angular/compiler_cli./integrationtest.sh
$ cp modules/@angular/compiler_cli/README.md modules/@angular/compiler_cli/package.json dist/all/@angular/compiler_cli $ cp modules/@angular/compiler_cli/README.md modules/@angular/compiler_cli/package.json dist/all/@angular/compiler_cli
# npm login as angular # npm login as angular
$ npm publish dist/all/@angular/compiler_cli --access=public $ npm publish dist/all/@angular/compiler_cli --access=public

View File

@ -1,3 +1,4 @@
<div>{{ctxProp}}</div> <div>{{ctxProp}}</div>
<form><input type="button" [(ngModel)]="ctxProp"/></form> <form><input type="button" [(ngModel)]="ctxProp"/></form>
<my-comp *ngIf="ctxBool"></my-comp> <my-comp *ngIf="ctxBool"></my-comp>
<div *ngFor="let x of ctxArr" [attr.value]="x"></div>

View File

@ -1,5 +1,5 @@
import {Component, Inject} from '@angular/core'; import {Component, Inject} from '@angular/core';
import {FORM_DIRECTIVES, NgIf} from '@angular/common'; import {FORM_DIRECTIVES, NgIf, NgFor} from '@angular/common';
import {MyComp} from './a/multiple_components'; import {MyComp} from './a/multiple_components';
@Component({ @Component({
@ -7,10 +7,11 @@ import {MyComp} from './a/multiple_components';
templateUrl: './basic.html', templateUrl: './basic.html',
styles: ['.red { color: red }'], styles: ['.red { color: red }'],
styleUrls: ['./basic.css'], styleUrls: ['./basic.css'],
directives: [MyComp, FORM_DIRECTIVES, NgIf] directives: [MyComp, FORM_DIRECTIVES, NgIf, NgFor]
}) })
export class Basic { export class Basic {
ctxProp: string; ctxProp: string;
ctxBool: boolean; ctxBool: boolean;
ctxArr: any[] = [];
constructor() { this.ctxProp = 'initialValue'; } constructor() { this.ctxProp = 'initialValue'; }
} }

View File

@ -28,7 +28,14 @@ describe("template codegen output", () => {
expect(fs.readFileSync(dtsOutput, {encoding: 'utf-8'})).toContain('Basic'); expect(fs.readFileSync(dtsOutput, {encoding: 'utf-8'})).toContain('Basic');
}); });
it("should be able to create the basic component and trigger an ngIf", () => { it("should be able to create the basic component", () => {
const appInjector = ReflectiveInjector.resolveAndCreate(BROWSER_APP_STATIC_PROVIDERS,
browserPlatform().injector);
var comp = BasicNgFactory.create(appInjector);
expect(comp.instance).toBeTruthy();
});
it("should support ngIf", () => {
const appInjector = ReflectiveInjector.resolveAndCreate(BROWSER_APP_STATIC_PROVIDERS, const appInjector = ReflectiveInjector.resolveAndCreate(BROWSER_APP_STATIC_PROVIDERS,
browserPlatform().injector); browserPlatform().injector);
var comp = BasicNgFactory.create(appInjector); var comp = BasicNgFactory.create(appInjector);
@ -40,4 +47,19 @@ describe("template codegen output", () => {
expect(debugElement.children.length).toBe(3); expect(debugElement.children.length).toBe(3);
expect(debugElement.children[2].injector.get(MyComp)).toBeTruthy(); expect(debugElement.children[2].injector.get(MyComp)).toBeTruthy();
}); });
it("should support ngFor", () => {
const appInjector = ReflectiveInjector.resolveAndCreate(BROWSER_APP_STATIC_PROVIDERS,
browserPlatform().injector);
var comp = BasicNgFactory.create(appInjector);
var debugElement = <DebugElement>getDebugNode(comp.location.nativeElement);
expect(debugElement.children.length).toBe(2);
// test NgFor
comp.instance.ctxArr = [1, 2];
comp.changeDetectorRef.detectChanges();
expect(debugElement.children.length).toBe(4);
expect(debugElement.children[2].attributes['value']).toBe('1');
expect(debugElement.children[3].attributes['value']).toBe('2');
});
}); });

View File

@ -2,7 +2,7 @@ import {reflector} from '@angular/core';
import {ReflectionCapabilities} from './core_private'; import {ReflectionCapabilities} from './core_private';
import {StaticReflector} from './static_reflector'; import {StaticReflector} from './static_reflector';
export class StaticAndDynamicReflectionCapabilities { export class StaticAndDynamicReflectionCapabilities {
static install(staticDelegate: StaticReflector) { static install(staticDelegate: StaticReflector) {
reflector.updateCapabilities(new StaticAndDynamicReflectionCapabilities(staticDelegate)); reflector.updateCapabilities(new StaticAndDynamicReflectionCapabilities(staticDelegate));
} }
@ -11,36 +11,30 @@ export class StaticAndDynamicReflectionCapabilities {
constructor(private staticDelegate: StaticReflector) {} constructor(private staticDelegate: StaticReflector) {}
isReflectionEnabled(): boolean { isReflectionEnabled(): boolean { return true; }
return true; factory(type: any): Function { return this.dynamicDelegate.factory(type); }
} interfaces(type: any): any[] { return this.dynamicDelegate.interfaces(type); }
factory(type: any): Function { hasLifecycleHook(type: any, lcInterface: /*Type*/ any, lcProperty: string): boolean {
return this.dynamicDelegate.factory(type); return isStaticType(type) ?
} this.staticDelegate.hasLifecycleHook(type, lcInterface, lcProperty) :
interfaces(type: any): any[] { this.dynamicDelegate.hasLifecycleHook(type, lcInterface, lcProperty);
return this.dynamicDelegate.interfaces(type);
} }
parameters(type: any): any[][] { parameters(type: any): any[][] {
return isStaticType(type) ? this.staticDelegate.parameters(type) : this.dynamicDelegate.parameters(type); return isStaticType(type) ? this.staticDelegate.parameters(type) :
this.dynamicDelegate.parameters(type);
} }
annotations(type: any): any[] { annotations(type: any): any[] {
return isStaticType(type) ? this.staticDelegate.annotations(type) : this.dynamicDelegate.annotations(type); return isStaticType(type) ? this.staticDelegate.annotations(type) :
this.dynamicDelegate.annotations(type);
} }
propMetadata(typeOrFunc: any): {[key: string]: any[]} { propMetadata(typeOrFunc: any): {[key: string]: any[]} {
return isStaticType(typeOrFunc) ? this.staticDelegate.propMetadata(typeOrFunc) : this.dynamicDelegate.propMetadata(typeOrFunc); return isStaticType(typeOrFunc) ? this.staticDelegate.propMetadata(typeOrFunc) :
} this.dynamicDelegate.propMetadata(typeOrFunc);
getter(name: string) {
return this.dynamicDelegate.getter(name);
}
setter(name: string) {
return this.dynamicDelegate.setter(name);
}
method(name: string) {
return this.dynamicDelegate.method(name);
}
importUri(type: any): string {
return this.staticDelegate.importUri(type);
} }
getter(name: string) { return this.dynamicDelegate.getter(name); }
setter(name: string) { return this.dynamicDelegate.setter(name); }
method(name: string) { return this.dynamicDelegate.method(name); }
importUri(type: any): string { return this.staticDelegate.importUri(type); }
} }
function isStaticType(type: any): boolean { function isStaticType(type: any): boolean {

View File

@ -114,7 +114,7 @@ export class StaticReflector implements ReflectorReader {
public parameters(type: StaticSymbol): any[] { public parameters(type: StaticSymbol): any[] {
if (!(type instanceof StaticSymbol)) { if (!(type instanceof StaticSymbol)) {
throw new Error(`parameters recieved ${JSON.stringify(type)} which is not a StaticSymbol`); throw new Error(`parameters received ${JSON.stringify(type)} which is not a StaticSymbol`);
} }
try { try {
let parameters = this.parameterCache.get(type); let parameters = this.parameterCache.get(type);
@ -124,8 +124,8 @@ export class StaticReflector implements ReflectorReader {
let ctorData = members ? members['__ctor__'] : null; let ctorData = members ? members['__ctor__'] : null;
if (ctorData) { if (ctorData) {
let ctor = (<any[]>ctorData).find(a => a['__symbolic'] == 'constructor'); let ctor = (<any[]>ctorData).find(a => a['__symbolic'] == 'constructor');
let parameterTypes = <any[]>this.simplify(type, ctor['parameters']); let parameterTypes = <any[]>this.simplify(type, ctor['parameters'] || []);
let parameterDecorators = <any[]>this.simplify(type, ctor['parameterDecorators']); let parameterDecorators = <any[]>this.simplify(type, ctor['parameterDecorators'] || []);
parameters = []; parameters = [];
parameterTypes.forEach((paramType, index) => { parameterTypes.forEach((paramType, index) => {
@ -152,6 +152,17 @@ export class StaticReflector implements ReflectorReader {
} }
} }
hasLifecycleHook(type: any, lcInterface: /*Type*/ any, lcProperty: string): boolean {
if (!(type instanceof StaticSymbol)) {
throw new Error(
`hasLifecycleHook received ${JSON.stringify(type)} which is not a StaticSymbol`);
}
let classMetadata = this.getTypeMetadata(type);
let members = classMetadata ? classMetadata['members'] : null;
let member:any[] = members ? members[lcProperty] : null;
return member ? member.some(a => a['__symbolic'] == 'method') : false;
}
private registerDecoratorOrConstructor(type: StaticSymbol, ctor: any): void { private registerDecoratorOrConstructor(type: StaticSymbol, ctor: any): void {
this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => { this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => {
let argValues: any[] = []; let argValues: any[] = [];

View File

@ -75,7 +75,7 @@ export var LIFECYCLE_HOOKS_VALUES = [
* bootstrap(App).catch(err => console.error(err)); * bootstrap(App).catch(err => console.error(err));
* ``` * ```
*/ */
export interface OnChanges { ngOnChanges(changes: {[key: string]: SimpleChange}); } export abstract class OnChanges { abstract ngOnChanges(changes: {[key: string]: SimpleChange}); }
/** /**
* Implement this interface to execute custom initialization logic after your directive's * Implement this interface to execute custom initialization logic after your directive's
@ -118,7 +118,7 @@ export interface OnChanges { ngOnChanges(changes: {[key: string]: SimpleChange})
* bootstrap(App).catch(err => console.error(err)); * bootstrap(App).catch(err => console.error(err));
* ``` * ```
*/ */
export interface OnInit { ngOnInit(); } export abstract class OnInit { abstract ngOnInit(); }
/** /**
* Implement this interface to override the default change detection algorithm for your directive. * Implement this interface to override the default change detection algorithm for your directive.
@ -185,7 +185,7 @@ export interface OnInit { ngOnInit(); }
* } * }
* ``` * ```
*/ */
export interface DoCheck { ngDoCheck(); } export abstract class DoCheck { abstract ngDoCheck(); }
/** /**
* Implement this interface to get notified when your directive is destroyed. * Implement this interface to get notified when your directive is destroyed.
@ -276,7 +276,7 @@ export interface DoCheck { ngDoCheck(); }
* every 50ms, until it reaches 0. * every 50ms, until it reaches 0.
* *
*/ */
export interface OnDestroy { ngOnDestroy(); } export abstract class OnDestroy { abstract ngOnDestroy(); }
/** /**
* Implement this interface to get notified when your directive's content has been fully * Implement this interface to get notified when your directive's content has been fully
@ -329,7 +329,7 @@ export interface OnDestroy { ngOnDestroy(); }
* bootstrap(App).catch(err => console.error(err)); * bootstrap(App).catch(err => console.error(err));
* ``` * ```
*/ */
export interface AfterContentInit { ngAfterContentInit(); } export abstract class AfterContentInit { abstract ngAfterContentInit(); }
/** /**
* Implement this interface to get notified after every check of your directive's content. * Implement this interface to get notified after every check of your directive's content.
@ -377,7 +377,7 @@ export interface AfterContentInit { ngAfterContentInit(); }
* bootstrap(App).catch(err => console.error(err)); * bootstrap(App).catch(err => console.error(err));
* ``` * ```
*/ */
export interface AfterContentChecked { ngAfterContentChecked(); } export abstract class AfterContentChecked { abstract ngAfterContentChecked(); }
/** /**
* Implement this interface to get notified when your component's view has been fully initialized. * Implement this interface to get notified when your component's view has been fully initialized.
@ -424,7 +424,7 @@ export interface AfterContentChecked { ngAfterContentChecked(); }
* bootstrap(App).catch(err => console.error(err)); * bootstrap(App).catch(err => console.error(err));
* ``` * ```
*/ */
export interface AfterViewInit { ngAfterViewInit(); } export abstract class AfterViewInit { abstract ngAfterViewInit(); }
/** /**
* Implement this interface to get notified after every check of your component's view. * Implement this interface to get notified after every check of your component's view.
@ -474,4 +474,4 @@ export interface AfterViewInit { ngAfterViewInit(); }
* bootstrap(App).catch(err => console.error(err)); * bootstrap(App).catch(err => console.error(err));
* ``` * ```
*/ */
export interface AfterViewChecked { ngAfterViewChecked(); } export abstract class AfterViewChecked { abstract ngAfterViewChecked(); }

View File

@ -5,6 +5,7 @@ export interface PlatformReflectionCapabilities {
isReflectionEnabled(): boolean; isReflectionEnabled(): boolean;
factory(type: Type): Function; factory(type: Type): Function;
interfaces(type: Type): any[]; interfaces(type: Type): any[];
hasLifecycleHook(type: any, lcInterface: /*Type*/ any, lcProperty: string): boolean;
parameters(type: any): any[][]; parameters(type: any): any[][];
annotations(type: any): any[]; annotations(type: any): any[];
propMetadata(typeOrFunc: any): {[key: string]: any[]}; propMetadata(typeOrFunc: any): {[key: string]: any[]};

View File

@ -291,6 +291,11 @@ class ReflectionCapabilities implements PlatformReflectionCapabilities {
return name.endsWith("=") ? name.substring(0, name.length - 1) : name; return name.endsWith("=") ? name.substring(0, name.length - 1) : name;
} }
bool hasLifecycleHook(dynamic type, Type lcInterface, String lcProperty) {
if (type is! Type) return false;
return this.interfaces(type).contains(lcInterface);
}
List interfaces(type) { List interfaces(type) {
final clazz = reflectType(type); final clazz = reflectType(type);
_assertDeclaresLifecycleHooks(clazz); _assertDeclaresLifecycleHooks(clazz);

View File

@ -197,8 +197,16 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
return {}; return {};
} }
interfaces(type: Type): any[] { // Note: JavaScript does not support to query for interfaces during runtime.
throw new BaseException("JavaScript does not support interfaces"); // However, we can't throw here as the reflector will always call this method
// when asked for a lifecycle interface as this is what we check in Dart.
interfaces(type: Type): any[] { return []; }
hasLifecycleHook(type: any, lcInterface: Type, lcProperty: string): boolean {
if (!(type instanceof Type)) return false;
var proto = (<any>type).prototype;
return !!proto[lcProperty];
} }
getter(name: string): GetterFn { return <GetterFn>new Function('o', 'return o.' + name + ';'); } getter(name: string): GetterFn { return <GetterFn>new Function('o', 'return o.' + name + ';'); }

View File

@ -45,9 +45,7 @@ export class Reflector extends ReflectorReader {
this.reflectionCapabilities = reflectionCapabilities; this.reflectionCapabilities = reflectionCapabilities;
} }
updateCapabilities(caps: PlatformReflectionCapabilities) { updateCapabilities(caps: PlatformReflectionCapabilities) { this.reflectionCapabilities = caps; }
this.reflectionCapabilities = caps;
}
isReflectionEnabled(): boolean { return this.reflectionCapabilities.isReflectionEnabled(); } isReflectionEnabled(): boolean { return this.reflectionCapabilities.isReflectionEnabled(); }
@ -120,7 +118,7 @@ export class Reflector extends ReflectorReader {
} }
} }
interfaces(type: Type): any[] { interfaces(type: /*Type*/ any): any[] {
if (this._injectableInfo.has(type)) { if (this._injectableInfo.has(type)) {
var res = this._getReflectionInfo(type).interfaces; var res = this._getReflectionInfo(type).interfaces;
return isPresent(res) ? res : []; return isPresent(res) ? res : [];
@ -129,6 +127,15 @@ export class Reflector extends ReflectorReader {
} }
} }
hasLifecycleHook(type: any, lcInterface: /*Type*/ any, lcProperty: string): boolean {
var interfaces = this.interfaces(type);
if (interfaces.indexOf(lcInterface) !== -1) {
return true;
} else {
return this.reflectionCapabilities.hasLifecycleHook(type, lcInterface, lcProperty);
}
}
getter(name: string): GetterFn { getter(name: string): GetterFn {
if (this._getters.has(name)) { if (this._getters.has(name)) {
return this._getters.get(name); return this._getters.get(name);

View File

@ -297,18 +297,18 @@ var CORE: string[] = [
'resolveForwardRef:js', 'resolveForwardRef:js',
'PLATFORM_COMMON_PROVIDERS', 'PLATFORM_COMMON_PROVIDERS',
'PLATFORM_INITIALIZER', 'PLATFORM_INITIALIZER',
'AfterContentChecked:dart', 'AfterContentChecked',
'AfterContentInit:dart', 'AfterContentInit',
'AfterViewChecked:dart', 'AfterViewChecked',
'AfterViewInit:dart', 'AfterViewInit',
'DoCheck:dart', 'DoCheck',
'IterableDifferFactory:dart', 'IterableDifferFactory:dart',
'IterableDiffer:dart', 'IterableDiffer:dart',
'KeyValueDifferFactory:dart', 'KeyValueDifferFactory:dart',
'KeyValueDiffer:dart', 'KeyValueDiffer:dart',
'OnChanges:dart', 'OnChanges',
'OnDestroy:dart', 'OnDestroy',
'OnInit:dart', 'OnInit',
'PipeTransform:dart', 'PipeTransform:dart',
'reflector', 'reflector',
'Stream:dart', 'Stream:dart',