feat(change_detection): added a directive lifecycle hook that is called after children are checked
This commit is contained in:
@ -900,3 +900,23 @@ export const onDestroy = "onDestroy";
|
||||
* @publicModule angular2/annotations
|
||||
*/
|
||||
export const onChange = "onChange";
|
||||
|
||||
/**
|
||||
* Notify a directive when the bindings of all its children have been changed.
|
||||
*
|
||||
* ## Example:
|
||||
*
|
||||
* ```
|
||||
* @Decorator({
|
||||
* selector: '[class-set]',
|
||||
* })
|
||||
* class ClassSet {
|
||||
*
|
||||
* onAllChangesDone() {
|
||||
* }
|
||||
*
|
||||
* }
|
||||
* ```
|
||||
* @publicModule angular2/annotations
|
||||
*/
|
||||
export const onAllChangesDone = "onAllChangesDone";
|
||||
|
@ -7,7 +7,7 @@ import {EventEmitter, PropertySetter, Attribute} from 'angular2/src/core/annotat
|
||||
import * as viewModule from 'angular2/src/core/compiler/view';
|
||||
import {ViewContainer} from 'angular2/src/core/compiler/view_container';
|
||||
import {NgElement} from 'angular2/src/core/dom/element';
|
||||
import {Directive, onChange, onDestroy} from 'angular2/src/core/annotations/annotations';
|
||||
import {Directive, onChange, onDestroy, onAllChangesDone} from 'angular2/src/core/annotations/annotations';
|
||||
import {BindingPropagationConfig} from 'angular2/change_detection';
|
||||
import * as pclModule from 'angular2/src/core/compiler/private_component_location';
|
||||
import {setterFactory} from './property_setter_factory';
|
||||
@ -131,11 +131,13 @@ export class DirectiveDependency extends Dependency {
|
||||
export class DirectiveBinding extends Binding {
|
||||
callOnDestroy:boolean;
|
||||
callOnChange:boolean;
|
||||
callOnAllChangesDone:boolean;
|
||||
|
||||
constructor(key:Key, factory:Function, dependencies:List, providedAsPromise:boolean, annotation:Directive) {
|
||||
super(key, factory, dependencies, providedAsPromise);
|
||||
this.callOnDestroy = isPresent(annotation) && annotation.hasLifecycleHook(onDestroy);
|
||||
this.callOnChange = isPresent(annotation) && annotation.hasLifecycleHook(onChange);
|
||||
this.callOnAllChangesDone = isPresent(annotation) && annotation.hasLifecycleHook(onAllChangesDone);
|
||||
}
|
||||
|
||||
static createFromBinding(b:Binding, annotation:Directive):Binding {
|
||||
@ -216,6 +218,8 @@ export class ProtoElementInjector {
|
||||
distanceToParent:number;
|
||||
attributes:Map;
|
||||
|
||||
numberOfDirectives:number;
|
||||
|
||||
/** Whether the element is exported as $implicit. */
|
||||
exportElement:boolean;
|
||||
|
||||
@ -244,6 +248,7 @@ export class ProtoElementInjector {
|
||||
this._binding8 = null; this._keyId8 = null;
|
||||
this._binding9 = null; this._keyId9 = null;
|
||||
|
||||
this.numberOfDirectives = bindings.length;
|
||||
var length = bindings.length;
|
||||
|
||||
if (length > 0) {this._binding0 = this._createBinding(bindings[0]); this._keyId0 = this._binding0.key.id;}
|
||||
@ -282,6 +287,20 @@ export class ProtoElementInjector {
|
||||
return isPresent(this._binding0);
|
||||
}
|
||||
|
||||
getDirectiveBindingAtIndex(index:int) {
|
||||
if (index == 0) return this._binding0;
|
||||
if (index == 1) return this._binding1;
|
||||
if (index == 2) return this._binding2;
|
||||
if (index == 3) return this._binding3;
|
||||
if (index == 4) return this._binding4;
|
||||
if (index == 5) return this._binding5;
|
||||
if (index == 6) return this._binding6;
|
||||
if (index == 7) return this._binding7;
|
||||
if (index == 8) return this._binding8;
|
||||
if (index == 9) return this._binding9;
|
||||
throw new OutOfBoundsAccess(index);
|
||||
}
|
||||
|
||||
hasEventEmitter(eventName: string) {
|
||||
var p = this;
|
||||
if (isPresent(p._binding0) && DirectiveBinding._hasEventEmitter(eventName, p._binding0)) return true;
|
||||
@ -648,18 +667,7 @@ export class ElementInjector extends TreeNode {
|
||||
}
|
||||
|
||||
getDirectiveBindingAtIndex(index:int) {
|
||||
var p = this._proto;
|
||||
if (index == 0) return p._binding0;
|
||||
if (index == 1) return p._binding1;
|
||||
if (index == 2) return p._binding2;
|
||||
if (index == 3) return p._binding3;
|
||||
if (index == 4) return p._binding4;
|
||||
if (index == 5) return p._binding5;
|
||||
if (index == 6) return p._binding6;
|
||||
if (index == 7) return p._binding7;
|
||||
if (index == 8) return p._binding8;
|
||||
if (index == 9) return p._binding9;
|
||||
throw new OutOfBoundsAccess(index);
|
||||
return this._proto.getDirectiveBindingAtIndex(index);
|
||||
}
|
||||
|
||||
hasInstances() {
|
||||
|
70
modules/angular2/src/core/compiler/view.js
vendored
70
modules/angular2/src/core/compiler/view.js
vendored
@ -236,6 +236,11 @@ export class View {
|
||||
}
|
||||
}
|
||||
|
||||
onAllChangesDone(directiveMemento) {
|
||||
var dir = directiveMemento.directive(this.elementInjectors);
|
||||
dir.onAllChangesDone();
|
||||
}
|
||||
|
||||
_invokeMementos(records:List) {
|
||||
for(var i = 0; i < records.length; ++i) {
|
||||
this._invokeMementoFor(records[i]);
|
||||
@ -303,6 +308,9 @@ export class ProtoView {
|
||||
parentProtoView:ProtoView;
|
||||
_variableBindings:List;
|
||||
|
||||
_directiveMementosMap:Map;
|
||||
_directiveMementos:List;
|
||||
|
||||
constructor(
|
||||
template,
|
||||
protoChangeDetector:ProtoChangeDetector,
|
||||
@ -324,7 +332,9 @@ export class ProtoView {
|
||||
this.stylePromises = [];
|
||||
this.eventHandlers = [];
|
||||
this.bindingRecords = [];
|
||||
this._directiveMementosMap = MapWrapper.create();
|
||||
this._variableBindings = null;
|
||||
this._directiveMementos = null;
|
||||
}
|
||||
|
||||
// TODO(rado): hostElementInjector should be moved to hydrate phase.
|
||||
@ -357,6 +367,27 @@ export class ProtoView {
|
||||
return this._variableBindings;
|
||||
}
|
||||
|
||||
// this work should be done the constructor of ProtoView once we separate
|
||||
// ProtoView and ProtoViewBuilder
|
||||
_getDirectiveMementos() {
|
||||
if (isPresent(this._directiveMementos)) {
|
||||
return this._directiveMementos;
|
||||
}
|
||||
|
||||
this._directiveMementos = [];
|
||||
|
||||
for (var injectorIndex = 0; injectorIndex < this.elementBinders.length; ++injectorIndex) {
|
||||
var pei = this.elementBinders[injectorIndex].protoElementInjector;
|
||||
if (isPresent(pei)) {
|
||||
for (var directiveIndex = 0; directiveIndex < pei.numberOfDirectives; ++directiveIndex) {
|
||||
ListWrapper.push(this._directiveMementos, this._getDirectiveMemento(injectorIndex, directiveIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this._directiveMementos;
|
||||
}
|
||||
|
||||
_instantiate(hostElementInjector: ElementInjector, eventManager: EventManager): View {
|
||||
var rootElementClone = this.instantiateInPlace ? this.element : DOM.importIntoDoc(this.element);
|
||||
var elementsWithBindingsDynamic;
|
||||
@ -385,7 +416,9 @@ export class ProtoView {
|
||||
}
|
||||
|
||||
var view = new View(this, viewNodes, this.protoLocals);
|
||||
var changeDetector = this.protoChangeDetector.instantiate(view, this.bindingRecords, this._getVariableBindings());
|
||||
var changeDetector = this.protoChangeDetector.instantiate(view, this.bindingRecords,
|
||||
this._getVariableBindings(), this._getDirectiveMementos());
|
||||
|
||||
var binders = this.elementBinders;
|
||||
var elementInjectors = ListWrapper.createFixedSize(binders.length);
|
||||
var eventHandlers = ListWrapper.createFixedSize(binders.length);
|
||||
@ -610,15 +643,29 @@ export class ProtoView {
|
||||
setterName:string,
|
||||
setter:SetterFn) {
|
||||
|
||||
var elementIndex = this.elementBinders.length-1;
|
||||
var bindingMemento = new DirectiveBindingMemento(
|
||||
this.elementBinders.length-1,
|
||||
elementIndex,
|
||||
directiveIndex,
|
||||
setterName,
|
||||
setter
|
||||
);
|
||||
var directiveMemento = DirectiveMemento.get(bindingMemento);
|
||||
var directiveMemento = this._getDirectiveMemento(elementIndex, directiveIndex);
|
||||
ListWrapper.push(this.bindingRecords, new BindingRecord(expression, bindingMemento, directiveMemento));
|
||||
}
|
||||
|
||||
_getDirectiveMemento(elementInjectorIndex:number, directiveIndex:number) {
|
||||
var id = elementInjectorIndex * 100 + directiveIndex;
|
||||
var protoElementInjector = this.elementBinders[elementInjectorIndex].protoElementInjector;
|
||||
|
||||
if (!MapWrapper.contains(this._directiveMementosMap, id)) {
|
||||
var binding = protoElementInjector.getDirectiveBindingAtIndex(directiveIndex);
|
||||
MapWrapper.set(this._directiveMementosMap, id,
|
||||
new DirectiveMemento(elementInjectorIndex, directiveIndex, binding.callOnAllChangesDone));
|
||||
}
|
||||
|
||||
return MapWrapper.get(this._directiveMementosMap, id);
|
||||
}
|
||||
|
||||
// Create a rootView as if the compiler encountered <rootcmp></rootcmp>,
|
||||
// and the component template is already compiled into protoView.
|
||||
@ -688,26 +735,15 @@ export class DirectiveBindingMemento {
|
||||
}
|
||||
}
|
||||
|
||||
var _directiveMementos = MapWrapper.create();
|
||||
|
||||
class DirectiveMemento {
|
||||
_elementInjectorIndex:number;
|
||||
_directiveIndex:number;
|
||||
notifyOnAllChangesDone:boolean;
|
||||
|
||||
constructor(elementInjectorIndex:number, directiveIndex:number) {
|
||||
constructor(elementInjectorIndex:number, directiveIndex:number, notifyOnAllChangesDone:boolean) {
|
||||
this._elementInjectorIndex = elementInjectorIndex;
|
||||
this._directiveIndex = directiveIndex;
|
||||
}
|
||||
|
||||
static get(memento:DirectiveBindingMemento) {
|
||||
var elementInjectorIndex = memento._elementInjectorIndex;
|
||||
var directiveIndex = memento._directiveIndex;
|
||||
var id = elementInjectorIndex * 100 + directiveIndex;
|
||||
|
||||
if (!MapWrapper.contains(_directiveMementos, id)) {
|
||||
MapWrapper.set(_directiveMementos, id, new DirectiveMemento(elementInjectorIndex, directiveIndex));
|
||||
}
|
||||
return MapWrapper.get(_directiveMementos, id);
|
||||
this.notifyOnAllChangesDone = notifyOnAllChangesDone;
|
||||
}
|
||||
|
||||
directive(elementInjectors:List<ElementInjector>) {
|
||||
|
Reference in New Issue
Block a user