feat(core): added support for detecting lifecycle events based on interfaces
This commit is contained in:
parent
2b6a653050
commit
30b6542fc8
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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(<any>type).prototype;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
});
|
||||
|
|
|
@ -12,6 +12,7 @@ export interface OnChange { onChange(changes: StringMap<string, any>): 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; }
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
library reflection.reflection_capabilities;
|
||||
|
||||
import 'reflection.dart';
|
||||
import 'package:angular2/src/facade/lang.dart';
|
||||
import 'types.dart';
|
||||
import 'dart:mirrors';
|
||||
|
|
|
@ -97,9 +97,7 @@ export class ReflectionCapabilities {
|
|||
return [];
|
||||
}
|
||||
|
||||
interfaces(type): List<any> {
|
||||
throw new BaseException("JavaScript does not support interfaces");
|
||||
}
|
||||
interfaces(type): List<any> { throw new BaseException("JavaScript does not support interfaces"); }
|
||||
|
||||
getter(name: string): GetterFn { return new Function('o', 'return o.' + name + ';'); }
|
||||
|
||||
|
|
|
@ -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<Type, any>;
|
||||
|
@ -18,7 +24,7 @@ export class Reflector {
|
|||
this.reflectionCapabilities = reflectionCapabilities;
|
||||
}
|
||||
|
||||
registerType(type: Type, typeInfo: Map<Type, any>): void {
|
||||
registerType(type: Type, typeInfo: StringMap<string, any>): void {
|
||||
MapWrapper.set(this._typeInfo, type, typeInfo);
|
||||
}
|
||||
|
||||
|
@ -29,32 +35,32 @@ export class Reflector {
|
|||
registerMethods(methods: Map<string, MethodFn>): 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<any> {
|
||||
if (MapWrapper.contains(this._typeInfo, typeOfFunc)) {
|
||||
return MapWrapper.get(this._typeInfo, typeOfFunc)["parameters"];
|
||||
parameters(typeOrFunc): List<any> {
|
||||
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<any> {
|
||||
if (MapWrapper.contains(this._typeInfo, typeOfFunc)) {
|
||||
return MapWrapper.get(this._typeInfo, typeOfFunc)["annotations"];
|
||||
annotations(typeOrFunc): List<any> {
|
||||
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<any> {
|
||||
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<any, any>, config: Map<string, Function>): void {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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(){}
|
||||
}
|
|
@ -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(_) {}
|
||||
}
|
|
@ -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", () => {
|
||||
|
|
Loading…
Reference in New Issue