feat(change_detection): updated handling ON_PUSH detectors so they get notified when their bindings change
This commit is contained in:
@@ -28,7 +28,7 @@ export function main() {
|
||||
}
|
||||
|
||||
function dirs(directives:List) {
|
||||
return new FakeDirectives(directives);
|
||||
return new FakeDirectives(directives, []);
|
||||
}
|
||||
|
||||
function convertLocalsToVariableBindings(locals) {
|
||||
@@ -246,9 +246,9 @@ export function main() {
|
||||
});
|
||||
|
||||
describe("updating directives", () => {
|
||||
var dirRecord1 = new DirectiveRecord(0, 0, true, true);
|
||||
var dirRecord2 = new DirectiveRecord(0, 1, true, true);
|
||||
var dirRecordNoCallbacks = new DirectiveRecord(0, 0, false, false);
|
||||
var dirRecord1 = new DirectiveRecord(0, 0, true, true, DEFAULT);
|
||||
var dirRecord2 = new DirectiveRecord(0, 1, true, true, DEFAULT);
|
||||
var dirRecordNoCallbacks = new DirectiveRecord(0, 0, false, false, DEFAULT);
|
||||
|
||||
function updateA(exp:string, dirRecord) {
|
||||
return BindingRecord.createForDirective(ast(exp), "a", (o,v) => o.a = v, dirRecord);
|
||||
@@ -553,6 +553,44 @@ export function main() {
|
||||
|
||||
expect(cd.mode).toEqual(CHECK_ALWAYS);
|
||||
});
|
||||
|
||||
describe("marking ON_PUSH detectors as CHECK_ONCE after an update", () => {
|
||||
var checkedDetector;
|
||||
var dirRecordWithOnPush;
|
||||
var updateDirWithOnPushRecord;
|
||||
var directives;
|
||||
|
||||
beforeEach(() => {
|
||||
var proto = createProtoChangeDetector(null, ON_PUSH);
|
||||
checkedDetector = instantiate(proto, null, [], []);
|
||||
checkedDetector.hydrate(null, null, null);
|
||||
checkedDetector.mode = CHECKED;
|
||||
|
||||
// this directive is a component with ON_PUSH change detection
|
||||
dirRecordWithOnPush = new DirectiveRecord(0, 0, false, false, ON_PUSH);
|
||||
|
||||
// a record updating a component
|
||||
updateDirWithOnPushRecord =
|
||||
BindingRecord.createForDirective(ast("42"), "a", (o,v) => o.a = v, dirRecordWithOnPush);
|
||||
|
||||
var targetDirective = new TestData(null);
|
||||
directives = new FakeDirectives([targetDirective], [checkedDetector]);
|
||||
});
|
||||
|
||||
it("should set the mode to CHECK_ONCE when a binding is updated", () => {
|
||||
var proto = createProtoChangeDetector(null);
|
||||
|
||||
var cd = instantiate(proto, null, [updateDirWithOnPushRecord], [dirRecordWithOnPush]);
|
||||
cd.hydrate(null, null, directives);
|
||||
|
||||
expect(checkedDetector.mode).toEqual(CHECKED);
|
||||
|
||||
// evaluate the record, update the targetDirective, and mark its detector as CHECK_ONCE
|
||||
cd.detectChanges();
|
||||
|
||||
expect(checkedDetector.mode).toEqual(CHECK_ONCE);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("markPathToRootAsCheckOnce", () => {
|
||||
@@ -664,7 +702,7 @@ export function main() {
|
||||
expect(pipe.destroyCalled).toEqual(true);
|
||||
});
|
||||
|
||||
it("should inject the binding propagation configuration " +
|
||||
it("should inject the ChangeDetectorRef " +
|
||||
"of the encompassing component into a pipe", () => {
|
||||
|
||||
var registry = new FakePipeRegistry('pipe', () => new IdentityPipe());
|
||||
@@ -673,7 +711,7 @@ export function main() {
|
||||
|
||||
cd.detectChanges();
|
||||
|
||||
expect(registry.cdRef).toBe(cd.changeDetectorRef);
|
||||
expect(registry.cdRef).toBe(cd.ref);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -853,14 +891,20 @@ class TestData {
|
||||
|
||||
class FakeDirectives {
|
||||
directives:List;
|
||||
detectors:List;
|
||||
|
||||
constructor(directives:List) {
|
||||
constructor(directives:List, detectors:List) {
|
||||
this.directives = directives;
|
||||
this.detectors = detectors;
|
||||
}
|
||||
|
||||
directive(directiveRecord:DirectiveRecord) {
|
||||
getDirectiveFor(directiveRecord:DirectiveRecord) {
|
||||
return this.directives[directiveRecord.directiveIndex];
|
||||
}
|
||||
|
||||
getDetectorFor(directiveRecord:DirectiveRecord) {
|
||||
return this.detectors[directiveRecord.directiveIndex];
|
||||
}
|
||||
}
|
||||
|
||||
class TestDispatcher extends ChangeDispatcher {
|
||||
|
||||
@@ -11,7 +11,7 @@ import {AppProtoView, AppView} from 'angular2/src/core/compiler/view';
|
||||
import {ViewContainer} from 'angular2/src/core/compiler/view_container';
|
||||
import {NgElement} from 'angular2/src/core/compiler/ng_element';
|
||||
import {Directive} from 'angular2/src/core/annotations/annotations';
|
||||
import {ChangeDetectorRef, Parser, Lexer} from 'angular2/change_detection';
|
||||
import {DynamicChangeDetector, ChangeDetectorRef, Parser, Lexer} from 'angular2/change_detection';
|
||||
import {ViewRef, Renderer, EventBinding} from 'angular2/src/render/api';
|
||||
import {QueryList} from 'angular2/src/core/compiler/query_list';
|
||||
|
||||
@@ -621,10 +621,10 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should return changeDetectorRef', function () {
|
||||
var config = new ChangeDetectorRef(null);
|
||||
var inj = injector([], null, null, new PreBuiltObjects(null, null, null, config));
|
||||
var cd = new DynamicChangeDetector(null, null, null, [], []);
|
||||
var inj = injector([], null, null, new PreBuiltObjects(null, null, null, cd));
|
||||
|
||||
expect(inj.get(ChangeDetectorRef)).toEqual(config);
|
||||
expect(inj.get(ChangeDetectorRef)).toBe(cd.ref);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+52
-7
@@ -390,13 +390,13 @@ export function main() {
|
||||
})
|
||||
}));
|
||||
|
||||
describe("ChangeDetectorRef", () => {
|
||||
it("can be used to disable the change detection of the component's template",
|
||||
describe("ON_PUSH components", () => {
|
||||
it("should use ChangeDetectorRef to manually request a check",
|
||||
inject([TestBed, AsyncTestCompleter], (tb, async) => {
|
||||
|
||||
tb.overrideView(MyComp, new View({
|
||||
template: '<push-cmp #cmp></push-cmp>',
|
||||
directives: [[[PushBasedComp]]]
|
||||
template: '<push-cmp-with-ref #cmp></push-cmp-with-ref>',
|
||||
directives: [[[PushCmpWithRef]]]
|
||||
}));
|
||||
|
||||
tb.createView(MyComp, {context: ctx}).then((view) => {
|
||||
@@ -417,10 +417,33 @@ export function main() {
|
||||
})
|
||||
}));
|
||||
|
||||
it('should not affect updating properties on the component', inject([TestBed, AsyncTestCompleter], (tb, async) => {
|
||||
it("should be checked when its bindings got updated",
|
||||
inject([TestBed, AsyncTestCompleter], (tb, async) => {
|
||||
|
||||
tb.overrideView(MyComp, new View({
|
||||
template: '<push-cmp [prop]="ctxProp" #cmp></push-cmp>',
|
||||
directives: [[[PushBasedComp]]]
|
||||
directives: [[[PushCmp]]]
|
||||
}));
|
||||
|
||||
tb.createView(MyComp, {context: ctx}).then((view) => {
|
||||
var cmp = view.rawView.locals.get('cmp');
|
||||
|
||||
ctx.ctxProp = "one";
|
||||
view.detectChanges();
|
||||
expect(cmp.numberOfChecks).toEqual(1);
|
||||
|
||||
ctx.ctxProp = "two";
|
||||
view.detectChanges();
|
||||
expect(cmp.numberOfChecks).toEqual(2);
|
||||
|
||||
async.done();
|
||||
})
|
||||
}));
|
||||
|
||||
it('should not affect updating properties on the component', inject([TestBed, AsyncTestCompleter], (tb, async) => {
|
||||
tb.overrideView(MyComp, new View({
|
||||
template: '<push-cmp-with-ref [prop]="ctxProp" #cmp></push-cmp-with-ref>',
|
||||
directives: [[[PushCmpWithRef]]]
|
||||
}));
|
||||
|
||||
tb.createView(MyComp, {context: ctx}).then((view) => {
|
||||
@@ -800,7 +823,29 @@ class MyDir {
|
||||
changeDetection:ON_PUSH
|
||||
})
|
||||
@View({template: '{{field}}'})
|
||||
class PushBasedComp {
|
||||
class PushCmp {
|
||||
numberOfChecks:number;
|
||||
prop;
|
||||
|
||||
constructor() {
|
||||
this.numberOfChecks = 0;
|
||||
}
|
||||
|
||||
get field(){
|
||||
this.numberOfChecks++;
|
||||
return "fixed";
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'push-cmp-with-ref',
|
||||
properties: {
|
||||
'prop': 'prop'
|
||||
},
|
||||
changeDetection:ON_PUSH
|
||||
})
|
||||
@View({template: '{{field}}'})
|
||||
class PushCmpWithRef {
|
||||
numberOfChecks:number;
|
||||
ref:ChangeDetectorRef;
|
||||
prop;
|
||||
|
||||
Reference in New Issue
Block a user