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(){} | ||||
| } | ||||
							
								
								
									
										100
									
								
								modules/angular2/test/core/compiler/directive_lifecycle_spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								modules/angular2/test/core/compiler/directive_lifecycle_spec.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user