feat(change_detection): added a directive lifecycle hook that is called after children are checked
This commit is contained in:
+86
-22
@@ -44,7 +44,8 @@ export function main() {
|
||||
var dispatcher = new TestDispatcher();
|
||||
|
||||
var variableBindings = convertLocalsToVariableBindings(locals);
|
||||
var cd = pcd.instantiate(dispatcher, [new BindingRecord(ast(exp), memo, memo)], variableBindings);
|
||||
var records = [new BindingRecord(ast(exp), memo, new FakeDirectiveMemento(memo, false))];
|
||||
var cd = pcd.instantiate(dispatcher, records, variableBindings, []);
|
||||
cd.hydrate(context, locals);
|
||||
|
||||
return {"changeDetector" : cd, "dispatcher" : dispatcher};
|
||||
@@ -56,6 +57,10 @@ export function main() {
|
||||
return res["dispatcher"].log;
|
||||
}
|
||||
|
||||
function instantiate(protoChangeDetector, dispatcher, bindings) {
|
||||
return protoChangeDetector.instantiate(dispatcher, bindings, null, []);
|
||||
}
|
||||
|
||||
describe(`${name} change detection`, () => {
|
||||
it('should do simple watching', () => {
|
||||
var person = new Person("misko");
|
||||
@@ -193,7 +198,7 @@ export function main() {
|
||||
var ast = parser.parseInterpolation("B{{a}}A", "location");
|
||||
|
||||
var dispatcher = new TestDispatcher();
|
||||
var cd = pcd.instantiate(dispatcher, [new BindingRecord(ast, "memo", "memo")], null);
|
||||
var cd = instantiate(pcd, dispatcher, [new BindingRecord(ast, "memo", null)]);
|
||||
cd.hydrate(new TestData("value"), null);
|
||||
|
||||
cd.detectChanges();
|
||||
@@ -239,15 +244,18 @@ export function main() {
|
||||
});
|
||||
|
||||
describe("group changes", () => {
|
||||
var dirMemento1 = new FakeDirectiveMemento(1);
|
||||
var dirMemento2 = new FakeDirectiveMemento(2);
|
||||
|
||||
it("should notify the dispatcher when a group of records changes", () => {
|
||||
var pcd = createProtoChangeDetector();
|
||||
|
||||
var dispatcher = new TestDispatcher();
|
||||
var cd = pcd.instantiate(dispatcher, [
|
||||
new BindingRecord(ast("1 + 2"), "memo", "1"),
|
||||
new BindingRecord(ast("10 + 20"), "memo", "1"),
|
||||
new BindingRecord(ast("100 + 200"), "memo", "2")
|
||||
], null);
|
||||
var cd = instantiate(pcd, dispatcher, [
|
||||
new BindingRecord(ast("1 + 2"), "memo", dirMemento1),
|
||||
new BindingRecord(ast("10 + 20"), "memo", dirMemento1),
|
||||
new BindingRecord(ast("100 + 200"), "memo", dirMemento2)
|
||||
]);
|
||||
|
||||
cd.detectChanges();
|
||||
|
||||
@@ -257,11 +265,11 @@ export function main() {
|
||||
it("should notify the dispatcher before switching to the next group", () => {
|
||||
var pcd = createProtoChangeDetector();
|
||||
var dispatcher = new TestDispatcher();
|
||||
var cd = pcd.instantiate(dispatcher, [
|
||||
new BindingRecord(ast("a()"), "a", "1"),
|
||||
new BindingRecord(ast("b()"), "b", "2"),
|
||||
new BindingRecord(ast("c()"), "c", "2")
|
||||
], null);
|
||||
var cd = instantiate(pcd, dispatcher, [
|
||||
new BindingRecord(ast("a()"), "a", dirMemento1),
|
||||
new BindingRecord(ast("b()"), "b", dirMemento2),
|
||||
new BindingRecord(ast("c()"), "c", dirMemento2)
|
||||
]);
|
||||
|
||||
var tr = new TestRecord();
|
||||
tr.a = () => {
|
||||
@@ -283,6 +291,46 @@ export function main() {
|
||||
expect(dispatcher.loggedValues).toEqual(['InvokeA', ['a'], 'InvokeB', 'InvokeC', ['b', 'c']]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("onAllChangesDone", () => {
|
||||
it("should notify the dispatcher about processing all the children", () => {
|
||||
var pcd = createProtoChangeDetector();
|
||||
var dispatcher = new TestDispatcher();
|
||||
|
||||
var memento1 = new FakeDirectiveMemento(1, false);
|
||||
var memento2 = new FakeDirectiveMemento(2, true);
|
||||
|
||||
var cd = pcd.instantiate(dispatcher, [
|
||||
new BindingRecord(ast("1"), "a", memento1),
|
||||
new BindingRecord(ast("2"), "b", memento2)
|
||||
], null, [memento1, memento2]);
|
||||
|
||||
cd.hydrate(null, null);
|
||||
|
||||
cd.detectChanges();
|
||||
|
||||
expect(dispatcher.loggedOnAllChangesDone).toEqual([memento2]);
|
||||
});
|
||||
|
||||
it("should notify in reverse order so the child is always notified before the parent", () => {
|
||||
var pcd = createProtoChangeDetector();
|
||||
var dispatcher = new TestDispatcher();
|
||||
|
||||
var memento1 = new FakeDirectiveMemento(1, true);
|
||||
var memento2 = new FakeDirectiveMemento(2, true);
|
||||
|
||||
var cd = pcd.instantiate(dispatcher, [
|
||||
new BindingRecord(ast("1"), "a", memento1),
|
||||
new BindingRecord(ast("2"), "b", memento2)
|
||||
], null, [memento1, memento2]);
|
||||
|
||||
cd.hydrate(null, null);
|
||||
|
||||
cd.detectChanges();
|
||||
|
||||
expect(dispatcher.loggedOnAllChangesDone).toEqual([memento2, memento1]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("enforce no new changes", () => {
|
||||
@@ -291,9 +339,9 @@ export function main() {
|
||||
pcd.addAst(ast("a"), "a", 1);
|
||||
|
||||
var dispatcher = new TestDispatcher();
|
||||
var cd = pcd.instantiate(dispatcher, [
|
||||
var cd = instantiate(pcd, dispatcher, [
|
||||
new BindingRecord(ast("a"), "a", 1)
|
||||
], null);
|
||||
]);
|
||||
cd.hydrate(new TestData('value'), null);
|
||||
|
||||
expect(() => {
|
||||
@@ -308,7 +356,7 @@ export function main() {
|
||||
var pcd = createProtoChangeDetector();
|
||||
var cd = pcd.instantiate(new TestDispatcher(), [
|
||||
new BindingRecord(ast("invalidProp", "someComponent"), "a", 1)
|
||||
], null);
|
||||
], null, []);
|
||||
cd.hydrate(null, null);
|
||||
|
||||
try {
|
||||
@@ -363,10 +411,10 @@ export function main() {
|
||||
|
||||
beforeEach(() => {
|
||||
var protoParent = createProtoChangeDetector();
|
||||
parent = protoParent.instantiate(null, [], null);
|
||||
parent = instantiate(protoParent, null, []);
|
||||
|
||||
var protoChild = createProtoChangeDetector();
|
||||
child = protoChild.instantiate(null, [], null);
|
||||
child = instantiate(protoChild, null, []);
|
||||
});
|
||||
|
||||
it("should add children", () => {
|
||||
@@ -409,7 +457,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it("should change CHECK_ONCE to CHECKED", () => {
|
||||
var cd = createProtoChangeDetector().instantiate(null, [], null);
|
||||
var cd = instantiate(createProtoChangeDetector(), null, []);
|
||||
cd.mode = CHECK_ONCE;
|
||||
|
||||
cd.detectChanges();
|
||||
@@ -418,7 +466,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it("should not change the CHECK_ALWAYS", () => {
|
||||
var cd = createProtoChangeDetector().instantiate(null, [], null);
|
||||
var cd = instantiate(createProtoChangeDetector(), null, []);
|
||||
cd.mode = CHECK_ALWAYS;
|
||||
|
||||
cd.detectChanges();
|
||||
@@ -429,7 +477,7 @@ export function main() {
|
||||
|
||||
describe("markPathToRootAsCheckOnce", () => {
|
||||
function changeDetector(mode, parent) {
|
||||
var cd = createProtoChangeDetector().instantiate(null, [], null);
|
||||
var cd = instantiate(createProtoChangeDetector(), null, []);
|
||||
cd.mode = mode;
|
||||
if (isPresent(parent)) parent.addChild(cd);
|
||||
return cd;
|
||||
@@ -700,16 +748,28 @@ class TestData {
|
||||
}
|
||||
}
|
||||
|
||||
class FakeDirectiveMemento {
|
||||
value:any;
|
||||
notifyOnAllChangesDone:boolean;
|
||||
|
||||
constructor(value, notifyOnAllChangesDone:boolean = false) {
|
||||
this.value = value;
|
||||
this.notifyOnAllChangesDone = notifyOnAllChangesDone;
|
||||
}
|
||||
}
|
||||
|
||||
class TestDispatcher extends ChangeDispatcher {
|
||||
log:List;
|
||||
loggedValues:List;
|
||||
changeRecords:List;
|
||||
loggedOnAllChangesDone:List;
|
||||
onChange:Function;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.log = null;
|
||||
this.loggedValues = null;
|
||||
this.loggedOnAllChangesDone = null;
|
||||
this.onChange = (_, __) => {};
|
||||
this.clear();
|
||||
}
|
||||
@@ -717,6 +777,7 @@ class TestDispatcher extends ChangeDispatcher {
|
||||
clear() {
|
||||
this.log = ListWrapper.create();
|
||||
this.loggedValues = ListWrapper.create();
|
||||
this.loggedOnAllChangesDone = ListWrapper.create();
|
||||
this.changeRecords = ListWrapper.create();
|
||||
}
|
||||
|
||||
@@ -724,7 +785,7 @@ class TestDispatcher extends ChangeDispatcher {
|
||||
ListWrapper.push(this.loggedValues, value);
|
||||
}
|
||||
|
||||
onRecordChange(group, changeRecords:List) {
|
||||
onRecordChange(directiveMemento, changeRecords:List) {
|
||||
var value = changeRecords[0].change.currentValue;
|
||||
var memento = changeRecords[0].bindingMemento;
|
||||
ListWrapper.push(this.log, memento + '=' + this._asString(value));
|
||||
@@ -734,9 +795,12 @@ class TestDispatcher extends ChangeDispatcher {
|
||||
|
||||
ListWrapper.push(this.changeRecords, changeRecords);
|
||||
|
||||
this.onChange(group, changeRecords);
|
||||
this.onChange(directiveMemento, changeRecords);
|
||||
}
|
||||
|
||||
onAllChangesDone(directiveMemento) {
|
||||
ListWrapper.push(this.loggedOnAllChangesDone, directiveMemento);
|
||||
}
|
||||
|
||||
_asString(value) {
|
||||
return (isBlank(value) ? 'null' : value.toString());
|
||||
|
||||
+12
-11
@@ -222,6 +222,18 @@ export function main() {
|
||||
|
||||
expect(protoChild.directParent()).toEqual(null);
|
||||
});
|
||||
|
||||
it("should allow for direct access using getDirectiveBindingAtIndex", function () {
|
||||
var binding = DirectiveBinding.createFromBinding(
|
||||
bind(SimpleDirective).toClass(SimpleDirective), null);
|
||||
var proto = new ProtoElementInjector(null, 0, [binding]);
|
||||
|
||||
expect(proto.getDirectiveBindingAtIndex(0)).toBeAnInstanceOf(DirectiveBinding);
|
||||
expect(() => proto.getDirectiveBindingAtIndex(-1)).toThrowError(
|
||||
'Index -1 is out-of-bounds.');
|
||||
expect(() => proto.getDirectiveBindingAtIndex(10)).toThrowError(
|
||||
'Index 10 is out-of-bounds.');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -419,17 +431,6 @@ export function main() {
|
||||
'Index 10 is out-of-bounds.');
|
||||
});
|
||||
|
||||
it("should allow for direct access using getBindingAtIndex", function () {
|
||||
var inj = injector([
|
||||
DirectiveBinding.createFromBinding(bind(SimpleDirective).toClass(SimpleDirective), null)
|
||||
]);
|
||||
expect(inj.getDirectiveBindingAtIndex(0)).toBeAnInstanceOf(DirectiveBinding);
|
||||
expect(() => inj.getDirectiveBindingAtIndex(-1)).toThrowError(
|
||||
'Index -1 is out-of-bounds.');
|
||||
expect(() => inj.getDirectiveBindingAtIndex(10)).toThrowError(
|
||||
'Index 10 is out-of-bounds.');
|
||||
});
|
||||
|
||||
it("should handle cyclic dependencies", function () {
|
||||
expect(() => {
|
||||
var bAneedsB = bind(A_Needs_B).toFactory((a) => new A_Needs_B(a), [B_Needs_A]);
|
||||
|
||||
@@ -11,7 +11,7 @@ import {DynamicProtoChangeDetector, ChangeDetector, Lexer, Parser} from 'angular
|
||||
|
||||
function createView(nodes) {
|
||||
var view = new View(null, nodes, MapWrapper.create());
|
||||
var cd = new DynamicProtoChangeDetector(null).instantiate(view, [], null);
|
||||
var cd = new DynamicProtoChangeDetector(null).instantiate(view, [], null, []);
|
||||
view.init(cd, [], [], [], [], [], [], [], [], []);
|
||||
return view;
|
||||
}
|
||||
|
||||
+24
-1
@@ -3,7 +3,7 @@ import {ProtoView, ElementPropertyMemento, DirectivePropertyMemento} from 'angul
|
||||
import {ProtoElementInjector, ElementInjector, DirectiveBinding} from 'angular2/src/core/compiler/element_injector';
|
||||
import {EmulatedScopedShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
||||
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
|
||||
import {Component, Decorator, Viewport, Directive, onChange} from 'angular2/src/core/annotations/annotations';
|
||||
import {Component, Decorator, Viewport, Directive, onChange, onAllChangesDone} from 'angular2/src/core/annotations/annotations';
|
||||
import {Lexer, Parser, DynamicProtoChangeDetector,
|
||||
ChangeDetector} from 'angular2/change_detection';
|
||||
import {EventEmitter} from 'angular2/src/core/annotations/di';
|
||||
@@ -627,6 +627,21 @@ export function main() {
|
||||
cd.detectChanges();
|
||||
expect(directive.changes).toEqual({"a" : new PropertyUpdate(100, 0)});
|
||||
});
|
||||
|
||||
it('should invoke the onAllChangesDone callback', () => {
|
||||
var pv = new ProtoView(el('<div class="ng-binding"></div>'),
|
||||
new DynamicProtoChangeDetector(null), null);
|
||||
|
||||
pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [
|
||||
DirectiveBinding.createFromType(DirectiveImplementingOnAllChangesDone, new Directive({lifecycle: [onAllChangesDone]}))
|
||||
]));
|
||||
|
||||
createViewAndChangeDetector(pv);
|
||||
cd.detectChanges();
|
||||
|
||||
var directive = view.elementInjectors[0].get(DirectiveImplementingOnAllChangesDone);
|
||||
expect(directive.onAllChangesDoneCalled).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -678,6 +693,14 @@ class DirectiveImplementingOnChange {
|
||||
}
|
||||
}
|
||||
|
||||
class DirectiveImplementingOnAllChangesDone {
|
||||
onAllChangesDoneCalled;
|
||||
|
||||
onAllChangesDone() {
|
||||
this.onAllChangesDoneCalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
class SomeService {}
|
||||
|
||||
@Component({services: [SomeService]})
|
||||
|
||||
Reference in New Issue
Block a user