diff --git a/modules/angular2/src/core/annotations_impl/annotations.ts b/modules/angular2/src/core/annotations_impl/annotations.ts index b280fbb64b..841b190857 100644 --- a/modules/angular2/src/core/annotations_impl/annotations.ts +++ b/modules/angular2/src/core/annotations_impl/annotations.ts @@ -1,5 +1,5 @@ -import {CONST, normalizeBlank, isPresent, CONST_EXPR} from 'angular2/src/facade/lang'; -import {ListWrapper, List} from 'angular2/src/facade/collection'; +import {CONST, CONST_EXPR} from 'angular2/src/facade/lang'; +import {List} from 'angular2/src/facade/collection'; import {Injectable} from 'angular2/src/di/annotations_impl'; import {DEFAULT} from 'angular2/change_detection'; @@ -778,15 +778,6 @@ export class Directive extends Injectable { this.compileChildren = compileChildren; this.hostInjector = hostInjector; } - - /** - * Returns true if a directive participates in a given `LifecycleEvent`. - * - * See {@link onChange}, {@link onDestroy}, {@link onAllChangesDone} for details. - */ - hasLifecycleHook(hook: LifecycleEvent): boolean { - return isPresent(this.lifecycle) ? ListWrapper.contains(this.lifecycle, hook) : false; - } } /** diff --git a/modules/angular2/src/core/compiler/directive_lifecycle_reflector.dart b/modules/angular2/src/core/compiler/directive_lifecycle_reflector.dart new file mode 100644 index 0000000000..42b7d16857 --- /dev/null +++ b/modules/angular2/src/core/compiler/directive_lifecycle_reflector.dart @@ -0,0 +1,26 @@ +import 'package:angular2/src/core/annotations_impl/annotations.dart'; +import 'package:angular2/src/core/compiler/interfaces.dart'; +import 'package:angular2/src/reflection/reflection.dart'; + +bool hasLifecycleHook(LifecycleEvent e, type, Directive annotation) { + if (annotation.lifecycle != null) { + return annotation.lifecycle.contains(e); + } else { + if (type is! Type) return false; + + final List interfaces = reflector.interfaces(type); + var interface; + + if (e == onChange) { + interface = OnChange; + + } else if (e == onDestroy) { + interface = OnDestroy; + + } else if (e == onAllChangesDone) { + interface = OnAllChangesDone; + } + + return interfaces.contains(interface); + } +} \ No newline at end of file diff --git a/modules/angular2/src/core/compiler/directive_lifecycle_reflector.ts b/modules/angular2/src/core/compiler/directive_lifecycle_reflector.ts new file mode 100644 index 0000000000..f58af0c12a --- /dev/null +++ b/modules/angular2/src/core/compiler/directive_lifecycle_reflector.ts @@ -0,0 +1,11 @@ +import {Type, isPresent} from 'angular2/src/facade/lang'; +import {LifecycleEvent, Directive} from 'angular2/src/core/annotations_impl/annotations'; + +export function hasLifecycleHook(e: LifecycleEvent, type, annotation: Directive): boolean { + if (isPresent(annotation.lifecycle)) { + return annotation.lifecycle.indexOf(e) !== -1; + } else { + if (!(type instanceof Type)) return false; + return e.name in(type).prototype; + } +} \ No newline at end of file diff --git a/modules/angular2/src/core/compiler/element_injector.ts b/modules/angular2/src/core/compiler/element_injector.ts index 4d41a9ef99..0acf9fd699 100644 --- a/modules/angular2/src/core/compiler/element_injector.ts +++ b/modules/angular2/src/core/compiler/element_injector.ts @@ -28,6 +28,7 @@ import { onDestroy, onAllChangesDone } from 'angular2/src/core/annotations_impl/annotations'; +import {hasLifecycleHook} from './directive_lifecycle_reflector'; import {ChangeDetector, ChangeDetectorRef} from 'angular2/change_detection'; import {QueryList} from './query_list'; import {reflector} from 'angular2/src/reflection/reflection'; @@ -282,7 +283,6 @@ export class DirectiveBinding extends ResolvedBinding { var resolvedViewInjectables = ann instanceof Component && isPresent(ann.viewInjector) ? resolveBindings(ann.viewInjector) : []; - var metadata = new DirectiveMetadata({ id: stringify(rb.key.token), type: ann instanceof @@ -300,9 +300,11 @@ export class DirectiveBinding extends ResolvedBinding { null, properties: isPresent(ann.properties) ? MapWrapper.createFromStringMap(ann.properties) : null, readAttributes: DirectiveBinding._readAttributes(deps), - callOnDestroy: ann.hasLifecycleHook(onDestroy), - callOnChange: ann.hasLifecycleHook(onChange), - callOnAllChangesDone: ann.hasLifecycleHook(onAllChangesDone), + + callOnDestroy: hasLifecycleHook(onDestroy, rb.key.token, ann), + callOnChange: hasLifecycleHook(onChange, rb.key.token, ann), + callOnAllChangesDone: hasLifecycleHook(onAllChangesDone, rb.key.token, ann), + changeDetection: ann instanceof Component ? ann.changeDetection : null }); diff --git a/modules/angular2/src/core/compiler/interfaces.ts b/modules/angular2/src/core/compiler/interfaces.ts index a43bc89b91..0c08334353 100644 --- a/modules/angular2/src/core/compiler/interfaces.ts +++ b/modules/angular2/src/core/compiler/interfaces.ts @@ -12,6 +12,7 @@ export interface OnChange { onChange(changes: StringMap): void; } export interface OnDestroy { onDestroy(): void; } /** - * Defines lifecycle method [onAllChangesDone ] called when the bindings of all its children have been changed. + * Defines lifecycle method [onAllChangesDone ] called when the bindings of all its children have + * been changed. */ -export interface OnAllChangesDone { onAllChangesDone (): void; } +export interface OnAllChangesDone { onAllChangesDone(): void; } diff --git a/modules/angular2/src/reflection/reflection_capabilities.dart b/modules/angular2/src/reflection/reflection_capabilities.dart index e450e96265..0edb79c017 100644 --- a/modules/angular2/src/reflection/reflection_capabilities.dart +++ b/modules/angular2/src/reflection/reflection_capabilities.dart @@ -1,6 +1,5 @@ library reflection.reflection_capabilities; -import 'reflection.dart'; import 'package:angular2/src/facade/lang.dart'; import 'types.dart'; import 'dart:mirrors'; diff --git a/modules/angular2/src/reflection/reflection_capabilities.ts b/modules/angular2/src/reflection/reflection_capabilities.ts index c6d3aa5a05..913edfba41 100644 --- a/modules/angular2/src/reflection/reflection_capabilities.ts +++ b/modules/angular2/src/reflection/reflection_capabilities.ts @@ -97,9 +97,7 @@ export class ReflectionCapabilities { return []; } - interfaces(type): List { - throw new BaseException("JavaScript does not support interfaces"); - } + interfaces(type): List { throw new BaseException("JavaScript does not support interfaces"); } getter(name: string): GetterFn { return new Function('o', 'return o.' + name + ';'); } diff --git a/modules/angular2/src/reflection/reflector.ts b/modules/angular2/src/reflection/reflector.ts index c8f9bc32ab..94e7376212 100644 --- a/modules/angular2/src/reflection/reflector.ts +++ b/modules/angular2/src/reflection/reflector.ts @@ -1,7 +1,13 @@ import {Type, isPresent, stringify, BaseException} from 'angular2/src/facade/lang'; -import {List, ListWrapper, Map, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; +import { + List, + ListWrapper, + Map, + MapWrapper, + StringMap, + StringMapWrapper +} from 'angular2/src/facade/collection'; import {SetterFn, GetterFn, MethodFn} from './types'; -export {SetterFn, GetterFn, MethodFn} from './types'; export class Reflector { _typeInfo: Map; @@ -18,7 +24,7 @@ export class Reflector { this.reflectionCapabilities = reflectionCapabilities; } - registerType(type: Type, typeInfo: Map): void { + registerType(type: Type, typeInfo: StringMap): void { MapWrapper.set(this._typeInfo, type, typeInfo); } @@ -29,32 +35,32 @@ export class Reflector { registerMethods(methods: Map): void { _mergeMaps(this._methods, methods); } factory(type: Type): Function { - if (MapWrapper.contains(this._typeInfo, type)) { - return MapWrapper.get(this._typeInfo, type)["factory"]; + if (this._containsTypeInfo(type)) { + return this._getTypeInfoField(type, "factory", null); } else { return this.reflectionCapabilities.factory(type); } } - parameters(typeOfFunc): List { - if (MapWrapper.contains(this._typeInfo, typeOfFunc)) { - return MapWrapper.get(this._typeInfo, typeOfFunc)["parameters"]; + parameters(typeOrFunc): List { + if (MapWrapper.contains(this._typeInfo, typeOrFunc)) { + return this._getTypeInfoField(typeOrFunc, "parameters", []); } else { - return this.reflectionCapabilities.parameters(typeOfFunc); + return this.reflectionCapabilities.parameters(typeOrFunc); } } - annotations(typeOfFunc): List { - if (MapWrapper.contains(this._typeInfo, typeOfFunc)) { - return MapWrapper.get(this._typeInfo, typeOfFunc)["annotations"]; + annotations(typeOrFunc): List { + if (MapWrapper.contains(this._typeInfo, typeOrFunc)) { + return this._getTypeInfoField(typeOrFunc, "annotations", []); } else { - return this.reflectionCapabilities.annotations(typeOfFunc); + return this.reflectionCapabilities.annotations(typeOrFunc); } } interfaces(type): List { if (MapWrapper.contains(this._typeInfo, type)) { - return MapWrapper.get(this._typeInfo, type)["interfaces"]; + return this._getTypeInfoField(type, "interfaces", []); } else { return this.reflectionCapabilities.interfaces(type); } @@ -83,6 +89,13 @@ export class Reflector { return this.reflectionCapabilities.method(name); } } + + _getTypeInfoField(typeOrFunc, key, defaultValue) { + var res = MapWrapper.get(this._typeInfo, typeOrFunc)[key]; + return isPresent(res) ? res : defaultValue; + } + + _containsTypeInfo(typeOrFunc) { return MapWrapper.contains(this._typeInfo, typeOrFunc); } } function _mergeMaps(target: Map, config: Map): void { diff --git a/modules/angular2/test/core/annotations/annotations_spec.ts b/modules/angular2/test/core/annotations/annotations_spec.ts deleted file mode 100644 index 6b0f0fe6e9..0000000000 --- a/modules/angular2/test/core/annotations/annotations_spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {ddescribe, describe, it, iit, expect, beforeEach} from 'angular2/test_lib'; -import {Directive, onChange} from 'angular2/src/core/annotations_impl/annotations'; - -export function main() { - describe("Directive", () => { - describe("lifecycle", () => { - it("should be false when no lifecycle specified", () => { - var d = new Directive(); - expect(d.hasLifecycleHook(onChange)).toBe(false); - }); - - it("should be false when the lifecycle does not contain the hook", () => { - var d = new Directive({lifecycle: []}); - expect(d.hasLifecycleHook(onChange)).toBe(false); - }); - - it("should be true otherwise", () => { - var d = new Directive({lifecycle: [onChange]}); - expect(d.hasLifecycleHook(onChange)).toBe(true); - }); - }); - }); -} diff --git a/modules/angular2/test/core/compiler/directive_lifecycle_spec.dart b/modules/angular2/test/core/compiler/directive_lifecycle_spec.dart new file mode 100644 index 0000000000..598f0985de --- /dev/null +++ b/modules/angular2/test/core/compiler/directive_lifecycle_spec.dart @@ -0,0 +1,75 @@ +library angular2.test.core.compiler.directive_lifecycle_spec; + +import 'package:angular2/test_lib.dart'; +import 'package:angular2/angular2.dart'; +import 'package:angular2/src/core/compiler/element_injector.dart'; + + +main() { + describe('Create DirectiveMetadata', () { + describe('lifecycle', () { + metadata(type, annotation) => DirectiveBinding.createFromType(type, annotation).metadata; + + describe("onChange", () { + it("should be true when the directive implements OnChange", () { + expect(metadata(DirectiveImplementingOnChange, new Directive()).callOnChange).toBe(true); + }); + + it("should be true when the lifecycle includes onChange", () { + expect(metadata(DirectiveNoHooks, new Directive(lifecycle: [onChange])).callOnChange).toBe(true); + }); + + it("should be false otherwise", () { + expect(metadata(DirectiveNoHooks, new Directive()).callOnChange).toBe(false); + }); + + it("should be false when empty lifecycle", () { + expect(metadata(DirectiveImplementingOnChange, new Directive(lifecycle: [])).callOnChange).toBe(false); + }); + }); + + describe("onDestroy", () { + it("should be true when the directive implements OnDestroy", () { + expect(metadata(DirectiveImplementingOnDestroy, new Directive()).callOnDestroy).toBe(true); + }); + + it("should be true when the lifecycle includes onDestroy", () { + expect(metadata(DirectiveNoHooks, new Directive(lifecycle: [onDestroy])).callOnDestroy).toBe(true); + }); + + it("should be false otherwise", () { + expect(metadata(DirectiveNoHooks, new Directive()).callOnDestroy).toBe(false); + }); + }); + + describe("onAllChangesDone", () { + it("should be true when the directive implements OnAllChangesDone", () { + expect(metadata(DirectiveImplementingOnAllChangesDone, new Directive()).callOnAllChangesDone).toBe(true); + }); + + it("should be true when the lifecycle includes onAllChangesDone", () { + expect(metadata(DirectiveNoHooks, new Directive(lifecycle: [onAllChangesDone])).callOnAllChangesDone).toBe(true); + }); + + it("should be false otherwise", () { + expect(metadata(DirectiveNoHooks, new Directive()).callOnAllChangesDone).toBe(false); + }); + }); + }); + }); +} + +class DirectiveNoHooks { +} + +class DirectiveImplementingOnChange implements OnChange { + onChange(_){} +} + +class DirectiveImplementingOnDestroy implements OnDestroy { + onDestroy(){} +} + +class DirectiveImplementingOnAllChangesDone implements OnAllChangesDone { + onAllChangesDone(){} +} \ No newline at end of file diff --git a/modules/angular2/test/core/compiler/directive_lifecycle_spec.ts b/modules/angular2/test/core/compiler/directive_lifecycle_spec.ts new file mode 100644 index 0000000000..eb14d08680 --- /dev/null +++ b/modules/angular2/test/core/compiler/directive_lifecycle_spec.ts @@ -0,0 +1,100 @@ +import { + AsyncTestCompleter, + beforeEach, + xdescribe, + ddescribe, + describe, + el, + expect, + iit, + inject, + IS_DARTIUM, + it, + SpyObject, + proxy +} from 'angular2/test_lib'; + +import { + Directive, + onChange, + onDestroy, + onAllChangesDone +} from 'angular2/src/core/annotations_impl/annotations'; +import {DirectiveBinding} from 'angular2/src/core/compiler/element_injector'; + +export function main() { + describe('Create DirectiveMetadata', () => { + describe('lifecycle', () => { + function metadata(type, annotation) { + return DirectiveBinding.createFromType(type, annotation).metadata; + } + + describe("onChange", () => { + it("should be true when the directive has the onChange method", () => { + expect(metadata(DirectiveWithOnChangeMethod, new Directive({})).callOnChange).toBe(true); + }); + + it("should be true when the lifecycle includes onChange", () => { + expect(metadata(DirectiveNoHooks, new Directive({lifecycle: [onChange]})).callOnChange) + .toBe(true); + }); + + it("should be false otherwise", + () => { expect(metadata(DirectiveNoHooks, new Directive()).callOnChange).toBe(false); }); + + it("should be false when empty lifecycle", () => { + expect(metadata(DirectiveWithOnChangeMethod, new Directive({lifecycle: []})).callOnChange) + .toBe(false); + }); + }); + + describe("onDestroy", () => { + it("should be true when the directive has the onDestroy method", () => { + expect(metadata(DirectiveWithOnDestroyMethod, new Directive({})).callOnDestroy) + .toBe(true); + }); + + it("should be true when the lifecycle includes onDestroy", () => { + expect(metadata(DirectiveNoHooks, new Directive({lifecycle: [onDestroy]})).callOnDestroy) + .toBe(true); + }); + + it("should be false otherwise", () => { + expect(metadata(DirectiveNoHooks, new Directive()).callOnDestroy).toBe(false); + }); + }); + + describe("onAllChangesDone", () => { + it("should be true when the directive has the onAllChangesDone method", () => { + expect( + metadata(DirectiveWithOnAllChangesDoneMethod, new Directive({})).callOnAllChangesDone) + .toBe(true); + }); + + it("should be true when the lifecycle includes onAllChangesDone", () => { + expect(metadata(DirectiveNoHooks, new Directive({lifecycle: [onAllChangesDone]})) + .callOnAllChangesDone) + .toBe(true); + }); + + it("should be false otherwise", () => { + expect(metadata(DirectiveNoHooks, new Directive()).callOnAllChangesDone).toBe(false); + }); + }); + }); + }); +} + +class DirectiveNoHooks {} + +class DirectiveWithOnChangeMethod { + onChange(_) {} +} + +class DirectiveWithOnDestroyMethod { + onDestroy(_) {} +} + +class DirectiveWithOnAllChangesDoneMethod { + onAllChangesDone(_) {} +} \ No newline at end of file diff --git a/modules/angular2/test/reflection/reflector_spec.ts b/modules/angular2/test/reflection/reflector_spec.ts index 6664362762..91a1540b48 100644 --- a/modules/angular2/test/reflection/reflector_spec.ts +++ b/modules/angular2/test/reflection/reflector_spec.ts @@ -42,8 +42,7 @@ class TestObj { class Interface {} -class ClassImplementingInterface implements Interface { -} +class ClassImplementingInterface implements Interface {} export function main() { describe('Reflector', () => { @@ -85,6 +84,11 @@ export function main() { reflector.registerType(TestObj, {"parameters": [1, 2]}); expect(reflector.parameters(TestObj)).toEqual([1, 2]); }); + + it("should return an empty list when no paramters field in the stored type info", () => { + reflector.registerType(TestObj, {}); + expect(reflector.parameters(TestObj)).toEqual([]); + }); }); describe("annotations", () => {