Skip to content

Commit 30b6542

Browse files
committed
feat(core): added support for detecting lifecycle events based on interfaces
1 parent 2b6a653 commit 30b6542

File tree

12 files changed

+257
-60
lines changed

12 files changed

+257
-60
lines changed

modules/angular2/src/core/annotations_impl/annotations.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import {CONST, normalizeBlank, isPresent, CONST_EXPR} from 'angular2/src/facade/lang';
2-
import {ListWrapper, List} from 'angular2/src/facade/collection';
1+
import {CONST, CONST_EXPR} from 'angular2/src/facade/lang';
2+
import {List} from 'angular2/src/facade/collection';
33
import {Injectable} from 'angular2/src/di/annotations_impl';
44
import {DEFAULT} from 'angular2/change_detection';
55

@@ -778,15 +778,6 @@ export class Directive extends Injectable {
778778
this.compileChildren = compileChildren;
779779
this.hostInjector = hostInjector;
780780
}
781-
782-
/**
783-
* Returns true if a directive participates in a given `LifecycleEvent`.
784-
*
785-
* See {@link onChange}, {@link onDestroy}, {@link onAllChangesDone} for details.
786-
*/
787-
hasLifecycleHook(hook: LifecycleEvent): boolean {
788-
return isPresent(this.lifecycle) ? ListWrapper.contains(this.lifecycle, hook) : false;
789-
}
790781
}
791782

792783
/**
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import 'package:angular2/src/core/annotations_impl/annotations.dart';
2+
import 'package:angular2/src/core/compiler/interfaces.dart';
3+
import 'package:angular2/src/reflection/reflection.dart';
4+
5+
bool hasLifecycleHook(LifecycleEvent e, type, Directive annotation) {
6+
if (annotation.lifecycle != null) {
7+
return annotation.lifecycle.contains(e);
8+
} else {
9+
if (type is! Type) return false;
10+
11+
final List interfaces = reflector.interfaces(type);
12+
var interface;
13+
14+
if (e == onChange) {
15+
interface = OnChange;
16+
17+
} else if (e == onDestroy) {
18+
interface = OnDestroy;
19+
20+
} else if (e == onAllChangesDone) {
21+
interface = OnAllChangesDone;
22+
}
23+
24+
return interfaces.contains(interface);
25+
}
26+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import {Type, isPresent} from 'angular2/src/facade/lang';
2+
import {LifecycleEvent, Directive} from 'angular2/src/core/annotations_impl/annotations';
3+
4+
export function hasLifecycleHook(e: LifecycleEvent, type, annotation: Directive): boolean {
5+
if (isPresent(annotation.lifecycle)) {
6+
return annotation.lifecycle.indexOf(e) !== -1;
7+
} else {
8+
if (!(type instanceof Type)) return false;
9+
return e.name in(<any>type).prototype;
10+
}
11+
}

modules/angular2/src/core/compiler/element_injector.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
onDestroy,
2929
onAllChangesDone
3030
} from 'angular2/src/core/annotations_impl/annotations';
31+
import {hasLifecycleHook} from './directive_lifecycle_reflector';
3132
import {ChangeDetector, ChangeDetectorRef} from 'angular2/change_detection';
3233
import {QueryList} from './query_list';
3334
import {reflector} from 'angular2/src/reflection/reflection';
@@ -282,7 +283,6 @@ export class DirectiveBinding extends ResolvedBinding {
282283
var resolvedViewInjectables = ann instanceof Component && isPresent(ann.viewInjector) ?
283284
resolveBindings(ann.viewInjector) :
284285
[];
285-
286286
var metadata = new DirectiveMetadata({
287287
id: stringify(rb.key.token),
288288
type: ann instanceof
@@ -300,9 +300,11 @@ export class DirectiveBinding extends ResolvedBinding {
300300
null,
301301
properties: isPresent(ann.properties) ? MapWrapper.createFromStringMap(ann.properties) : null,
302302
readAttributes: DirectiveBinding._readAttributes(deps),
303-
callOnDestroy: ann.hasLifecycleHook(onDestroy),
304-
callOnChange: ann.hasLifecycleHook(onChange),
305-
callOnAllChangesDone: ann.hasLifecycleHook(onAllChangesDone),
303+
304+
callOnDestroy: hasLifecycleHook(onDestroy, rb.key.token, ann),
305+
callOnChange: hasLifecycleHook(onChange, rb.key.token, ann),
306+
callOnAllChangesDone: hasLifecycleHook(onAllChangesDone, rb.key.token, ann),
307+
306308
changeDetection: ann instanceof
307309
Component ? ann.changeDetection : null
308310
});

modules/angular2/src/core/compiler/interfaces.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export interface OnChange { onChange(changes: StringMap<string, any>): void; }
1212
export interface OnDestroy { onDestroy(): void; }
1313

1414
/**
15-
* Defines lifecycle method [onAllChangesDone ] called when the bindings of all its children have been changed.
15+
* Defines lifecycle method [onAllChangesDone ] called when the bindings of all its children have
16+
* been changed.
1617
*/
17-
export interface OnAllChangesDone { onAllChangesDone (): void; }
18+
export interface OnAllChangesDone { onAllChangesDone(): void; }

modules/angular2/src/reflection/reflection_capabilities.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
library reflection.reflection_capabilities;
22

3-
import 'reflection.dart';
43
import 'package:angular2/src/facade/lang.dart';
54
import 'types.dart';
65
import 'dart:mirrors';

modules/angular2/src/reflection/reflection_capabilities.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,7 @@ export class ReflectionCapabilities {
9797
return [];
9898
}
9999

100-
interfaces(type): List<any> {
101-
throw new BaseException("JavaScript does not support interfaces");
102-
}
100+
interfaces(type): List<any> { throw new BaseException("JavaScript does not support interfaces"); }
103101

104102
getter(name: string): GetterFn { return new Function('o', 'return o.' + name + ';'); }
105103

modules/angular2/src/reflection/reflector.ts

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import {Type, isPresent, stringify, BaseException} from 'angular2/src/facade/lang';
2-
import {List, ListWrapper, Map, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
2+
import {
3+
List,
4+
ListWrapper,
5+
Map,
6+
MapWrapper,
7+
StringMap,
8+
StringMapWrapper
9+
} from 'angular2/src/facade/collection';
310
import {SetterFn, GetterFn, MethodFn} from './types';
4-
export {SetterFn, GetterFn, MethodFn} from './types';
511

612
export class Reflector {
713
_typeInfo: Map<Type, any>;
@@ -18,7 +24,7 @@ export class Reflector {
1824
this.reflectionCapabilities = reflectionCapabilities;
1925
}
2026

21-
registerType(type: Type, typeInfo: Map<Type, any>): void {
27+
registerType(type: Type, typeInfo: StringMap<string, any>): void {
2228
MapWrapper.set(this._typeInfo, type, typeInfo);
2329
}
2430

@@ -29,32 +35,32 @@ export class Reflector {
2935
registerMethods(methods: Map<string, MethodFn>): void { _mergeMaps(this._methods, methods); }
3036

3137
factory(type: Type): Function {
32-
if (MapWrapper.contains(this._typeInfo, type)) {
33-
return MapWrapper.get(this._typeInfo, type)["factory"];
38+
if (this._containsTypeInfo(type)) {
39+
return this._getTypeInfoField(type, "factory", null);
3440
} else {
3541
return this.reflectionCapabilities.factory(type);
3642
}
3743
}
3844

39-
parameters(typeOfFunc): List<any> {
40-
if (MapWrapper.contains(this._typeInfo, typeOfFunc)) {
41-
return MapWrapper.get(this._typeInfo, typeOfFunc)["parameters"];
45+
parameters(typeOrFunc): List<any> {
46+
if (MapWrapper.contains(this._typeInfo, typeOrFunc)) {
47+
return this._getTypeInfoField(typeOrFunc, "parameters", []);
4248
} else {
43-
return this.reflectionCapabilities.parameters(typeOfFunc);
49+
return this.reflectionCapabilities.parameters(typeOrFunc);
4450
}
4551
}
4652

47-
annotations(typeOfFunc): List<any> {
48-
if (MapWrapper.contains(this._typeInfo, typeOfFunc)) {
49-
return MapWrapper.get(this._typeInfo, typeOfFunc)["annotations"];
53+
annotations(typeOrFunc): List<any> {
54+
if (MapWrapper.contains(this._typeInfo, typeOrFunc)) {
55+
return this._getTypeInfoField(typeOrFunc, "annotations", []);
5056
} else {
51-
return this.reflectionCapabilities.annotations(typeOfFunc);
57+
return this.reflectionCapabilities.annotations(typeOrFunc);
5258
}
5359
}
5460

5561
interfaces(type): List<any> {
5662
if (MapWrapper.contains(this._typeInfo, type)) {
57-
return MapWrapper.get(this._typeInfo, type)["interfaces"];
63+
return this._getTypeInfoField(type, "interfaces", []);
5864
} else {
5965
return this.reflectionCapabilities.interfaces(type);
6066
}
@@ -83,6 +89,13 @@ export class Reflector {
8389
return this.reflectionCapabilities.method(name);
8490
}
8591
}
92+
93+
_getTypeInfoField(typeOrFunc, key, defaultValue) {
94+
var res = MapWrapper.get(this._typeInfo, typeOrFunc)[key];
95+
return isPresent(res) ? res : defaultValue;
96+
}
97+
98+
_containsTypeInfo(typeOrFunc) { return MapWrapper.contains(this._typeInfo, typeOrFunc); }
8699
}
87100

88101
function _mergeMaps(target: Map<any, any>, config: Map<string, Function>): void {

modules/angular2/test/core/annotations/annotations_spec.ts

Lines changed: 0 additions & 23 deletions
This file was deleted.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
library angular2.test.core.compiler.directive_lifecycle_spec;
2+
3+
import 'package:angular2/test_lib.dart';
4+
import 'package:angular2/angular2.dart';
5+
import 'package:angular2/src/core/compiler/element_injector.dart';
6+
7+
8+
main() {
9+
describe('Create DirectiveMetadata', () {
10+
describe('lifecycle', () {
11+
metadata(type, annotation) => DirectiveBinding.createFromType(type, annotation).metadata;
12+
13+
describe("onChange", () {
14+
it("should be true when the directive implements OnChange", () {
15+
expect(metadata(DirectiveImplementingOnChange, new Directive()).callOnChange).toBe(true);
16+
});
17+
18+
it("should be true when the lifecycle includes onChange", () {
19+
expect(metadata(DirectiveNoHooks, new Directive(lifecycle: [onChange])).callOnChange).toBe(true);
20+
});
21+
22+
it("should be false otherwise", () {
23+
expect(metadata(DirectiveNoHooks, new Directive()).callOnChange).toBe(false);
24+
});
25+
26+
it("should be false when empty lifecycle", () {
27+
expect(metadata(DirectiveImplementingOnChange, new Directive(lifecycle: [])).callOnChange).toBe(false);
28+
});
29+
});
30+
31+
describe("onDestroy", () {
32+
it("should be true when the directive implements OnDestroy", () {
33+
expect(metadata(DirectiveImplementingOnDestroy, new Directive()).callOnDestroy).toBe(true);
34+
});
35+
36+
it("should be true when the lifecycle includes onDestroy", () {
37+
expect(metadata(DirectiveNoHooks, new Directive(lifecycle: [onDestroy])).callOnDestroy).toBe(true);
38+
});
39+
40+
it("should be false otherwise", () {
41+
expect(metadata(DirectiveNoHooks, new Directive()).callOnDestroy).toBe(false);
42+
});
43+
});
44+
45+
describe("onAllChangesDone", () {
46+
it("should be true when the directive implements OnAllChangesDone", () {
47+
expect(metadata(DirectiveImplementingOnAllChangesDone, new Directive()).callOnAllChangesDone).toBe(true);
48+
});
49+
50+
it("should be true when the lifecycle includes onAllChangesDone", () {
51+
expect(metadata(DirectiveNoHooks, new Directive(lifecycle: [onAllChangesDone])).callOnAllChangesDone).toBe(true);
52+
});
53+
54+
it("should be false otherwise", () {
55+
expect(metadata(DirectiveNoHooks, new Directive()).callOnAllChangesDone).toBe(false);
56+
});
57+
});
58+
});
59+
});
60+
}
61+
62+
class DirectiveNoHooks {
63+
}
64+
65+
class DirectiveImplementingOnChange implements OnChange {
66+
onChange(_){}
67+
}
68+
69+
class DirectiveImplementingOnDestroy implements OnDestroy {
70+
onDestroy(){}
71+
}
72+
73+
class DirectiveImplementingOnAllChangesDone implements OnAllChangesDone {
74+
onAllChangesDone(){}
75+
}

0 commit comments

Comments
 (0)