feat(query): notify on changes
This commit is contained in:
parent
73d152506b
commit
5bfcca2d5b
|
@ -203,7 +203,12 @@ export class ChangeDetectorJITGenerator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return notifications.join("\n");
|
var directiveNotifications = notifications.join("\n");
|
||||||
|
|
||||||
|
return `
|
||||||
|
this.dispatcher.notifyOnAllChangesDone();
|
||||||
|
${directiveNotifications}
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
_genLocalDefinitions(): string { return this._localNames.map((n) => `var ${n};`).join("\n"); }
|
_genLocalDefinitions(): string { return this._localNames.map((n) => `var ${n};`).join("\n"); }
|
||||||
|
|
|
@ -110,6 +110,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
||||||
}
|
}
|
||||||
|
|
||||||
callOnAllChangesDone() {
|
callOnAllChangesDone() {
|
||||||
|
this.dispatcher.notifyOnAllChangesDone();
|
||||||
var dirs = this.directiveRecords;
|
var dirs = this.directiveRecords;
|
||||||
for (var i = dirs.length - 1; i >= 0; --i) {
|
for (var i = dirs.length - 1; i >= 0; --i) {
|
||||||
var dir = dirs[i];
|
var dir = dirs[i];
|
||||||
|
|
|
@ -26,7 +26,6 @@ export class BaseQueryList<T> {
|
||||||
this._dirty = true;
|
this._dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(rado): hook up with change detection after #995.
|
|
||||||
fireCallbacks() {
|
fireCallbacks() {
|
||||||
if (this._dirty) {
|
if (this._dirty) {
|
||||||
ListWrapper.forEach(this._callbacks, (c) => c());
|
ListWrapper.forEach(this._callbacks, (c) => c());
|
||||||
|
|
|
@ -707,6 +707,15 @@ export class ElementInjector extends TreeNode<ElementInjector> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onAllChangesDone(): void {
|
||||||
|
if (isPresent(this._query0) && this._query0.originator === this)
|
||||||
|
this._query0.list.fireCallbacks();
|
||||||
|
if (isPresent(this._query1) && this._query1.originator === this)
|
||||||
|
this._query1.list.fireCallbacks();
|
||||||
|
if (isPresent(this._query2) && this._query2.originator === this)
|
||||||
|
this._query2.list.fireCallbacks();
|
||||||
|
}
|
||||||
|
|
||||||
hydrate(injector: Injector, host: ElementInjector, preBuiltObjects: PreBuiltObjects): void {
|
hydrate(injector: Injector, host: ElementInjector, preBuiltObjects: PreBuiltObjects): void {
|
||||||
var p = this._proto;
|
var p = this._proto;
|
||||||
|
|
||||||
|
|
|
@ -111,6 +111,13 @@ export class AppView implements ChangeDispatcher, EventDispatcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notifyOnAllChangesDone(): void {
|
||||||
|
var ei = this.elementInjectors;
|
||||||
|
for (var i = ei.length - 1; i >= 0; i--) {
|
||||||
|
if (isPresent(ei[i])) ei[i].onAllChangesDone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getDirectiveFor(directive: DirectiveIndex) {
|
getDirectiveFor(directive: DirectiveIndex) {
|
||||||
var elementInjector = this.elementInjectors[directive.elementIndex];
|
var elementInjector = this.elementInjectors[directive.elementIndex];
|
||||||
return elementInjector.getDirectiveAtIndex(directive.directiveIndex);
|
return elementInjector.getDirectiveAtIndex(directive.directiveIndex);
|
||||||
|
|
|
@ -262,11 +262,15 @@ class _CodegenState {
|
||||||
/// them.
|
/// them.
|
||||||
String _getCallOnAllChangesDoneBody() {
|
String _getCallOnAllChangesDoneBody() {
|
||||||
// NOTE(kegluneq): Order is important!
|
// NOTE(kegluneq): Order is important!
|
||||||
return _directiveRecords.reversed
|
var directiveNotifications = _directiveRecords.reversed
|
||||||
.where((rec) => rec.callOnAllChangesDone)
|
.where((rec) => rec.callOnAllChangesDone)
|
||||||
.map((rec) =>
|
.map((rec) =>
|
||||||
'${_genGetDirective(rec.directiveIndex)}.onAllChangesDone();')
|
'${_genGetDirective(rec.directiveIndex)}.onAllChangesDone();')
|
||||||
.join('');
|
.join('');
|
||||||
|
return '''
|
||||||
|
_dispatcher.notifyOnAllChangesDone();
|
||||||
|
${directiveNotifications}
|
||||||
|
''';
|
||||||
}
|
}
|
||||||
|
|
||||||
String _genLocalDefinitions() =>
|
String _genLocalDefinitions() =>
|
||||||
|
|
|
@ -324,6 +324,12 @@ export function main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should notify the dispatcher on all changes done', () => {
|
||||||
|
var val = _createChangeDetector('name', new Person('bob'));
|
||||||
|
val.changeDetector.detectChanges();
|
||||||
|
expect(val.dispatcher.onAllChangesDoneCalled).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
describe('updating directives', () => {
|
describe('updating directives', () => {
|
||||||
var directive1;
|
var directive1;
|
||||||
var directive2;
|
var directive2;
|
||||||
|
@ -975,6 +981,7 @@ class FakeDirectives {
|
||||||
class TestDispatcher extends ChangeDispatcher {
|
class TestDispatcher extends ChangeDispatcher {
|
||||||
log: List<string>;
|
log: List<string>;
|
||||||
loggedValues: List<any>;
|
loggedValues: List<any>;
|
||||||
|
onAllChangesDoneCalled: boolean = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
@ -984,6 +991,7 @@ class TestDispatcher extends ChangeDispatcher {
|
||||||
clear() {
|
clear() {
|
||||||
this.log = ListWrapper.create();
|
this.log = ListWrapper.create();
|
||||||
this.loggedValues = ListWrapper.create();
|
this.loggedValues = ListWrapper.create();
|
||||||
|
this.onAllChangesDoneCalled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyOnBinding(binding, value) {
|
notifyOnBinding(binding, value) {
|
||||||
|
@ -991,6 +999,8 @@ class TestDispatcher extends ChangeDispatcher {
|
||||||
ListWrapper.push(this.loggedValues, value);
|
ListWrapper.push(this.loggedValues, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notifyOnAllChangesDone() { this.onAllChangesDoneCalled = true; }
|
||||||
|
|
||||||
_asString(value) { return (isBlank(value) ? 'null' : value.toString()); }
|
_asString(value) { return (isBlank(value) ? 'null' : value.toString()); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,8 @@ import {
|
||||||
beforeEach,
|
beforeEach,
|
||||||
SpyObject,
|
SpyObject,
|
||||||
proxy,
|
proxy,
|
||||||
|
inject,
|
||||||
|
AsyncTestCompleter,
|
||||||
el,
|
el,
|
||||||
containsRegexp
|
containsRegexp
|
||||||
} from 'angular2/test_lib';
|
} from 'angular2/test_lib';
|
||||||
|
@ -730,7 +732,7 @@ export function main() {
|
||||||
expect(d.dependency).toBeAnInstanceOf(SimpleDirective);
|
expect(d.dependency).toBeAnInstanceOf(SimpleDirective);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should throw when a depenency cannot be resolved", () => {
|
it("should throw when a dependency cannot be resolved", () => {
|
||||||
expect(() => injector(ListWrapper.concat([NeedsDirectiveFromParent], extraBindings)))
|
expect(() => injector(ListWrapper.concat([NeedsDirectiveFromParent], extraBindings)))
|
||||||
.toThrowError(containsRegexp(
|
.toThrowError(containsRegexp(
|
||||||
`No provider for ${stringify(SimpleDirective) }! (${stringify(NeedsDirectiveFromParent) } -> ${stringify(SimpleDirective) })`));
|
`No provider for ${stringify(SimpleDirective) }! (${stringify(NeedsDirectiveFromParent) } -> ${stringify(SimpleDirective) })`));
|
||||||
|
@ -818,7 +820,7 @@ export function main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("lifecycle", () => {
|
describe("lifecycle", () => {
|
||||||
it("should call onDestroy on directives subscribed to this event", function() {
|
it("should call onDestroy on directives subscribed to this event", () => {
|
||||||
var inj = injector(ListWrapper.concat(
|
var inj = injector(ListWrapper.concat(
|
||||||
[DirectiveBinding.createFromType(DirectiveWithDestroy,
|
[DirectiveBinding.createFromType(DirectiveWithDestroy,
|
||||||
new dirAnn.Directive({lifecycle: [onDestroy]}))],
|
new dirAnn.Directive({lifecycle: [onDestroy]}))],
|
||||||
|
@ -828,13 +830,45 @@ export function main() {
|
||||||
expect(destroy.onDestroyCounter).toBe(1);
|
expect(destroy.onDestroyCounter).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should work with services", function() {
|
it("should work with services", () => {
|
||||||
var inj = injector(ListWrapper.concat(
|
var inj = injector(ListWrapper.concat(
|
||||||
[DirectiveBinding.createFromType(
|
[DirectiveBinding.createFromType(
|
||||||
SimpleDirective, new dirAnn.Directive({hostInjector: [SimpleService]}))],
|
SimpleDirective, new dirAnn.Directive({hostInjector: [SimpleService]}))],
|
||||||
extraBindings));
|
extraBindings));
|
||||||
inj.dehydrate();
|
inj.dehydrate();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should notify queries", inject([AsyncTestCompleter], (async) => {
|
||||||
|
var inj = injector(ListWrapper.concat([NeedsQuery], extraBindings));
|
||||||
|
var query = inj.get(NeedsQuery).query;
|
||||||
|
query.add(new CountingDirective()); // this marks the query as dirty
|
||||||
|
|
||||||
|
query.onChange(() => async.done());
|
||||||
|
|
||||||
|
inj.onAllChangesDone();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("should not notify inherited queries", inject([AsyncTestCompleter], (async) => {
|
||||||
|
var child = parentChildInjectors(ListWrapper.concat([NeedsQuery], extraBindings), []);
|
||||||
|
|
||||||
|
var query = child.parent.get(NeedsQuery).query;
|
||||||
|
|
||||||
|
var calledOnChange = false;
|
||||||
|
query.onChange(() => {
|
||||||
|
// make sure the callback is called only once
|
||||||
|
expect(calledOnChange).toEqual(false);
|
||||||
|
expect(query.length).toEqual(2);
|
||||||
|
|
||||||
|
calledOnChange = true;
|
||||||
|
async.done()
|
||||||
|
});
|
||||||
|
|
||||||
|
query.add(new CountingDirective());
|
||||||
|
child.onAllChangesDone(); // this does not notify the query
|
||||||
|
|
||||||
|
query.add(new CountingDirective());
|
||||||
|
child.parent.onAllChangesDone();
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("dynamicallyCreateComponent", () => {
|
describe("dynamicallyCreateComponent", () => {
|
||||||
|
|
|
@ -18,6 +18,7 @@ import {QueryList} from 'angular2/core';
|
||||||
import {Query, Component, Directive, View} from 'angular2/annotations';
|
import {Query, Component, Directive, View} from 'angular2/annotations';
|
||||||
|
|
||||||
import {NgIf, NgFor} from 'angular2/angular2';
|
import {NgIf, NgFor} from 'angular2/angular2';
|
||||||
|
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||||
|
|
||||||
import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter';
|
import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter';
|
||||||
|
|
||||||
|
@ -120,6 +121,55 @@ export function main() {
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should notify query on change',
|
||||||
|
inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => {
|
||||||
|
var template = '<needs-query-desc #q>' +
|
||||||
|
'<div text="1"></div>' +
|
||||||
|
'<div *ng-if="shouldShow" text="2"></div>' +
|
||||||
|
'</needs-query-desc>';
|
||||||
|
|
||||||
|
tb.createView(MyComp, {html: template})
|
||||||
|
.then((view) => {
|
||||||
|
var q = view.rawView.locals.get("q");
|
||||||
|
view.detectChanges();
|
||||||
|
|
||||||
|
q.query.onChange(() => {
|
||||||
|
expect(q.query.first.text).toEqual("1");
|
||||||
|
expect(q.query.last.text).toEqual("2");
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
|
||||||
|
view.context.shouldShow = true;
|
||||||
|
view.detectChanges();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("should notify child's query before notifying parent's query",
|
||||||
|
inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => {
|
||||||
|
var template = '<needs-query-desc #q1>' +
|
||||||
|
'<needs-query-desc #q2>' +
|
||||||
|
'<div text="1"></div>' +
|
||||||
|
'</needs-query-desc>' +
|
||||||
|
'</needs-query-desc>';
|
||||||
|
|
||||||
|
tb.createView(MyComp, {html: template})
|
||||||
|
.then((view) => {
|
||||||
|
var q1 = view.rawView.locals.get("q1");
|
||||||
|
var q2 = view.rawView.locals.get("q2");
|
||||||
|
|
||||||
|
var firedQ2 = false;
|
||||||
|
|
||||||
|
q2.query.onChange(() => { firedQ2 = true; });
|
||||||
|
q1.query.onChange(() => {
|
||||||
|
expect(firedQ2).toBe(true);
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
|
||||||
|
view.detectChanges();
|
||||||
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,9 @@ class _MyComponent_ChangeDetector0 extends _gen.AbstractChangeDetector {
|
||||||
_alreadyChecked = true;
|
_alreadyChecked = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void callOnAllChangesDone() {}
|
void callOnAllChangesDone() {
|
||||||
|
_dispatcher.notifyOnAllChangesDone();
|
||||||
|
}
|
||||||
|
|
||||||
void hydrate(MyComponent context, locals, directives) {
|
void hydrate(MyComponent context, locals, directives) {
|
||||||
mode = 'ALWAYS_CHECK';
|
mode = 'ALWAYS_CHECK';
|
||||||
|
|
|
@ -381,4 +381,5 @@ class FakeDirectives {
|
||||||
|
|
||||||
class DummyDispatcher extends ChangeDispatcher {
|
class DummyDispatcher extends ChangeDispatcher {
|
||||||
notifyOnBinding(bindingRecord, newValue) { throw "Should not be used"; }
|
notifyOnBinding(bindingRecord, newValue) { throw "Should not be used"; }
|
||||||
|
notifyOnAllChangesDone() {}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue