feat(change_detection): added an experimental support for observables
This commit is contained in:
parent
ed81cb94b0
commit
cbfc9cb344
|
@ -1,4 +1,4 @@
|
||||||
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
|
import {isPresent, isBlank, BaseException, StringWrapper} from 'angular2/src/facade/lang';
|
||||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||||
import {ChangeDetectionUtil} from './change_detection_util';
|
import {ChangeDetectionUtil} from './change_detection_util';
|
||||||
import {ChangeDetectorRef} from './change_detector_ref';
|
import {ChangeDetectorRef} from './change_detector_ref';
|
||||||
|
@ -13,8 +13,9 @@ import {
|
||||||
import {ProtoRecord} from './proto_record';
|
import {ProtoRecord} from './proto_record';
|
||||||
import {BindingRecord} from './binding_record';
|
import {BindingRecord} from './binding_record';
|
||||||
import {Locals} from './parser/locals';
|
import {Locals} from './parser/locals';
|
||||||
import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH} from './constants';
|
import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED} from './constants';
|
||||||
import {wtfCreateScope, wtfLeave, WtfScopeFn} from '../profile/profile';
|
import {wtfCreateScope, wtfLeave, WtfScopeFn} from '../profile/profile';
|
||||||
|
import {isObservable} from './observable_facade';
|
||||||
|
|
||||||
var _scope_check: WtfScopeFn = wtfCreateScope(`ChangeDetector#check(ascii id, bool throwOnChange)`);
|
var _scope_check: WtfScopeFn = wtfCreateScope(`ChangeDetector#check(ascii id, bool throwOnChange)`);
|
||||||
|
|
||||||
|
@ -42,6 +43,10 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
|
||||||
firstProtoInCurrentBinding: number;
|
firstProtoInCurrentBinding: number;
|
||||||
protos: List<ProtoRecord>;
|
protos: List<ProtoRecord>;
|
||||||
|
|
||||||
|
// This is an experimental feature. Works only in Dart.
|
||||||
|
subscriptions: any[];
|
||||||
|
streams: any[];
|
||||||
|
|
||||||
constructor(public id: string, dispatcher: ChangeDispatcher, protos: List<ProtoRecord>,
|
constructor(public id: string, dispatcher: ChangeDispatcher, protos: List<ProtoRecord>,
|
||||||
directiveRecords: List<DirectiveRecord>, public modeOnHydrate: string) {
|
directiveRecords: List<DirectiveRecord>, public modeOnHydrate: string) {
|
||||||
this.ref = new ChangeDetectorRef(this);
|
this.ref = new ChangeDetectorRef(this);
|
||||||
|
@ -79,13 +84,14 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
|
||||||
checkNoChanges(): void { throw new BaseException("Not implemented"); }
|
checkNoChanges(): void { throw new BaseException("Not implemented"); }
|
||||||
|
|
||||||
runDetectChanges(throwOnChange: boolean): void {
|
runDetectChanges(throwOnChange: boolean): void {
|
||||||
if (this.mode === DETACHED || this.mode === CHECKED) return;
|
if (StringWrapper.equals(this.mode, DETACHED) || StringWrapper.equals(this.mode, CHECKED))
|
||||||
|
return;
|
||||||
var s = _scope_check(this.id, throwOnChange);
|
var s = _scope_check(this.id, throwOnChange);
|
||||||
this.detectChangesInRecords(throwOnChange);
|
this.detectChangesInRecords(throwOnChange);
|
||||||
this._detectChangesInLightDomChildren(throwOnChange);
|
this._detectChangesInLightDomChildren(throwOnChange);
|
||||||
if (throwOnChange === false) this.callOnAllChangesDone();
|
if (throwOnChange === false) this.callOnAllChangesDone();
|
||||||
this._detectChangesInShadowDomChildren(throwOnChange);
|
this._detectChangesInShadowDomChildren(throwOnChange);
|
||||||
if (this.mode === CHECK_ONCE) this.mode = CHECKED;
|
if (StringWrapper.equals(this.mode, CHECK_ONCE)) this.mode = CHECKED;
|
||||||
wtfLeave(s);
|
wtfLeave(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,6 +138,10 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
|
||||||
// implementation of `dehydrateDirectives`.
|
// implementation of `dehydrateDirectives`.
|
||||||
dehydrate(): void {
|
dehydrate(): void {
|
||||||
this.dehydrateDirectives(true);
|
this.dehydrateDirectives(true);
|
||||||
|
|
||||||
|
// This is an experimental feature. Works only in Dart.
|
||||||
|
this.unsubsribeFromObservables();
|
||||||
|
|
||||||
this.context = null;
|
this.context = null;
|
||||||
this.locals = null;
|
this.locals = null;
|
||||||
this.pipes = null;
|
this.pipes = null;
|
||||||
|
@ -163,12 +173,43 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
|
||||||
|
|
||||||
markPathToRootAsCheckOnce(): void {
|
markPathToRootAsCheckOnce(): void {
|
||||||
var c: ChangeDetector = this;
|
var c: ChangeDetector = this;
|
||||||
while (isPresent(c) && c.mode != DETACHED) {
|
while (isPresent(c) && !StringWrapper.equals(c.mode, DETACHED)) {
|
||||||
if (c.mode === CHECKED) c.mode = CHECK_ONCE;
|
if (StringWrapper.equals(c.mode, CHECKED)) c.mode = CHECK_ONCE;
|
||||||
c = c.parent;
|
c = c.parent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private unsubsribeFromObservables(): void {
|
||||||
|
if (isPresent(this.subscriptions)) {
|
||||||
|
for (var i = 0; i < this.subscriptions.length; ++i) {
|
||||||
|
var s = this.subscriptions[i];
|
||||||
|
if (isPresent(this.subscriptions[i])) {
|
||||||
|
s.cancel();
|
||||||
|
this.subscriptions[i] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is an experimental feature. Works only in Dart.
|
||||||
|
protected observe(value: any, index: number): any {
|
||||||
|
if (isObservable(value)) {
|
||||||
|
if (isBlank(this.subscriptions)) {
|
||||||
|
this.subscriptions = ListWrapper.createFixedSize(this.protos.length + 1);
|
||||||
|
this.streams = ListWrapper.createFixedSize(this.protos.length + 1);
|
||||||
|
}
|
||||||
|
if (isBlank(this.subscriptions[index])) {
|
||||||
|
this.streams[index] = value.changes;
|
||||||
|
this.subscriptions[index] = value.changes.listen((_) => this.ref.requestCheck());
|
||||||
|
} else if (this.streams[index] !== value.changes) {
|
||||||
|
this.subscriptions[index].cancel();
|
||||||
|
this.streams[index] = value.changes;
|
||||||
|
this.subscriptions[index] = value.changes.listen((_) => this.ref.requestCheck());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
protected notifyDispatcher(value: any): void {
|
protected notifyDispatcher(value: any): void {
|
||||||
this.dispatcher.notifyOnBinding(this._currentBinding(), value);
|
this.dispatcher.notifyOnBinding(this._currentBinding(), value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,8 +23,8 @@ export class BindingRecord {
|
||||||
return isPresent(this.directiveRecord) && this.directiveRecord.callOnChange;
|
return isPresent(this.directiveRecord) && this.directiveRecord.callOnChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
isOnPushChangeDetection(): boolean {
|
isDefaultChangeDetection(): boolean {
|
||||||
return isPresent(this.directiveRecord) && this.directiveRecord.isOnPushChangeDetection();
|
return isBlank(this.directiveRecord) || this.directiveRecord.isDefaultChangeDetection();
|
||||||
}
|
}
|
||||||
|
|
||||||
isDirective(): boolean { return this.mode === DIRECTIVE; }
|
isDirective(): boolean { return this.mode === DIRECTIVE; }
|
||||||
|
|
|
@ -35,7 +35,7 @@ export class ChangeDetectorJITGenerator {
|
||||||
public directiveRecords: List<any>, private generateCheckNoChanges: boolean) {
|
public directiveRecords: List<any>, private generateCheckNoChanges: boolean) {
|
||||||
this._names =
|
this._names =
|
||||||
new CodegenNameUtil(this.records, this.eventBindings, this.directiveRecords, UTIL);
|
new CodegenNameUtil(this.records, this.eventBindings, this.directiveRecords, UTIL);
|
||||||
this._logic = new CodegenLogicUtil(this._names, UTIL);
|
this._logic = new CodegenLogicUtil(this._names, UTIL, changeDetectionStrategy);
|
||||||
this._typeName = sanitizeName(`ChangeDetector_${this.id}`);
|
this._typeName = sanitizeName(`ChangeDetector_${this.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,10 +116,10 @@ export class ChangeDetectorJITGenerator {
|
||||||
|
|
||||||
_genMarkPathToRootAsCheckOnce(r: ProtoRecord): string {
|
_genMarkPathToRootAsCheckOnce(r: ProtoRecord): string {
|
||||||
var br = r.bindingRecord;
|
var br = r.bindingRecord;
|
||||||
if (br.isOnPushChangeDetection()) {
|
if (br.isDefaultChangeDetection()) {
|
||||||
return `${this._names.getDetectorName(br.directiveRecord.directiveIndex)}.markPathToRootAsCheckOnce();`;
|
|
||||||
} else {
|
|
||||||
return "";
|
return "";
|
||||||
|
} else {
|
||||||
|
return `${this._names.getDetectorName(br.directiveRecord.directiveIndex)}.markPathToRootAsCheckOnce();`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -369,7 +369,7 @@ export class ChangeDetectorJITGenerator {
|
||||||
|
|
||||||
_genNotifyOnPushDetectors(r: ProtoRecord): string {
|
_genNotifyOnPushDetectors(r: ProtoRecord): string {
|
||||||
var br = r.bindingRecord;
|
var br = r.bindingRecord;
|
||||||
if (!r.lastInDirective || !br.isOnPushChangeDetection()) return "";
|
if (!r.lastInDirective || br.isDefaultChangeDetection()) return "";
|
||||||
var retVal = `
|
var retVal = `
|
||||||
if(${IS_CHANGED_LOCAL}) {
|
if(${IS_CHANGED_LOCAL}) {
|
||||||
${this._names.getDetectorName(br.directiveRecord.directiveIndex)}.markAsCheckOnce();
|
${this._names.getDetectorName(br.directiveRecord.directiveIndex)}.markAsCheckOnce();
|
||||||
|
|
|
@ -1,7 +1,20 @@
|
||||||
import {CONST_EXPR, isPresent, isBlank, BaseException, Type} from 'angular2/src/facade/lang';
|
import {
|
||||||
|
CONST_EXPR,
|
||||||
|
isPresent,
|
||||||
|
isBlank,
|
||||||
|
BaseException,
|
||||||
|
Type,
|
||||||
|
StringWrapper
|
||||||
|
} from 'angular2/src/facade/lang';
|
||||||
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||||
import {ProtoRecord} from './proto_record';
|
import {ProtoRecord} from './proto_record';
|
||||||
import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH} from './constants';
|
import {
|
||||||
|
CHECK_ALWAYS,
|
||||||
|
CHECK_ONCE,
|
||||||
|
CHECKED,
|
||||||
|
DETACHED,
|
||||||
|
isDefaultChangeDetectionStrategy
|
||||||
|
} from './constants';
|
||||||
import {implementsOnDestroy} from './pipe_lifecycle_reflector';
|
import {implementsOnDestroy} from './pipe_lifecycle_reflector';
|
||||||
|
|
||||||
|
|
||||||
|
@ -166,7 +179,7 @@ export class ChangeDetectionUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
static changeDetectionMode(strategy: string): string {
|
static changeDetectionMode(strategy: string): string {
|
||||||
return strategy == ON_PUSH ? CHECK_ONCE : CHECK_ALWAYS;
|
return isDefaultChangeDetectionStrategy(strategy) ? CHECK_ALWAYS : CHECK_ONCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static simpleChange(previousValue: any, currentValue: any): SimpleChange {
|
static simpleChange(previousValue: any, currentValue: any): SimpleChange {
|
||||||
|
|
|
@ -1,14 +1,20 @@
|
||||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||||
import {BaseException, Json} from 'angular2/src/facade/lang';
|
import {BaseException, Json, StringWrapper} from 'angular2/src/facade/lang';
|
||||||
import {CodegenNameUtil} from './codegen_name_util';
|
import {CodegenNameUtil} from './codegen_name_util';
|
||||||
import {codify, combineGeneratedStrings, rawString} from './codegen_facade';
|
import {codify, combineGeneratedStrings, rawString} from './codegen_facade';
|
||||||
import {ProtoRecord, RecordType} from './proto_record';
|
import {ProtoRecord, RecordType} from './proto_record';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is an experimental feature. Works only in Dart.
|
||||||
|
*/
|
||||||
|
const ON_PUSH_OBSERVE = "ON_PUSH_OBSERVE";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class responsible for providing change detection logic for chagne detector classes.
|
* Class responsible for providing change detection logic for chagne detector classes.
|
||||||
*/
|
*/
|
||||||
export class CodegenLogicUtil {
|
export class CodegenLogicUtil {
|
||||||
constructor(private _names: CodegenNameUtil, private _utilName: string) {}
|
constructor(private _names: CodegenNameUtil, private _utilName: string,
|
||||||
|
private _changeDetection: string) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a statement which updates the local variable representing `protoRec` with the current
|
* Generates a statement which updates the local variable representing `protoRec` with the current
|
||||||
|
@ -46,11 +52,13 @@ export class CodegenLogicUtil {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RecordType.PROPERTY_READ:
|
case RecordType.PROPERTY_READ:
|
||||||
rhs = `${context}.${protoRec.name}`;
|
rhs = this._observe(`${context}.${protoRec.name}`, protoRec);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RecordType.SAFE_PROPERTY:
|
case RecordType.SAFE_PROPERTY:
|
||||||
rhs = `${this._utilName}.isValueBlank(${context}) ? null : ${context}.${protoRec.name}`;
|
var read = this._observe(`${context}.${protoRec.name}`, protoRec);
|
||||||
|
rhs =
|
||||||
|
`${this._utilName}.isValueBlank(${context}) ? null : ${this._observe(read, protoRec)}`;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RecordType.PROPERTY_WRITE:
|
case RecordType.PROPERTY_WRITE:
|
||||||
|
@ -58,16 +66,17 @@ export class CodegenLogicUtil {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RecordType.LOCAL:
|
case RecordType.LOCAL:
|
||||||
rhs = `${localsAccessor}.get(${rawString(protoRec.name)})`;
|
rhs = this._observe(`${localsAccessor}.get(${rawString(protoRec.name)})`, protoRec);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RecordType.INVOKE_METHOD:
|
case RecordType.INVOKE_METHOD:
|
||||||
rhs = `${context}.${protoRec.name}(${argString})`;
|
rhs = this._observe(`${context}.${protoRec.name}(${argString})`, protoRec);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RecordType.SAFE_INVOKE_METHOD:
|
case RecordType.SAFE_INVOKE_METHOD:
|
||||||
|
var invoke = `${context}.${protoRec.name}(${argString})`;
|
||||||
rhs =
|
rhs =
|
||||||
`${this._utilName}.isValueBlank(${context}) ? null : ${context}.${protoRec.name}(${argString})`;
|
`${this._utilName}.isValueBlank(${context}) ? null : ${this._observe(invoke, protoRec)}`;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RecordType.INVOKE_CLOSURE:
|
case RecordType.INVOKE_CLOSURE:
|
||||||
|
@ -87,7 +96,7 @@ export class CodegenLogicUtil {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RecordType.KEYED_READ:
|
case RecordType.KEYED_READ:
|
||||||
rhs = `${context}[${getLocalName(protoRec.args[0])}]`;
|
rhs = this._observe(`${context}[${getLocalName(protoRec.args[0])}]`, protoRec);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RecordType.KEYED_WRITE:
|
case RecordType.KEYED_WRITE:
|
||||||
|
@ -104,6 +113,14 @@ export class CodegenLogicUtil {
|
||||||
return `${getLocalName(protoRec.selfIndex)} = ${rhs};`;
|
return `${getLocalName(protoRec.selfIndex)} = ${rhs};`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_observe(exp: string, rec: ProtoRecord): string {
|
||||||
|
// This is an experimental feature. Works only in Dart.
|
||||||
|
if (StringWrapper.equals(this._changeDetection, ON_PUSH_OBSERVE)) {
|
||||||
|
return `this.observe(${exp}, ${rec.selfIndex})`;
|
||||||
|
} else {
|
||||||
|
return exp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_genInterpolation(protoRec: ProtoRecord): string {
|
_genInterpolation(protoRec: ProtoRecord): string {
|
||||||
var iVals = [];
|
var iVals = [];
|
||||||
|
|
|
@ -124,7 +124,7 @@ export class CodegenNameUtil {
|
||||||
MapWrapper.forEach(this._sanitizedEventNames, (names, eb) => {
|
MapWrapper.forEach(this._sanitizedEventNames, (names, eb) => {
|
||||||
for (var i = 0; i < names.length; ++i) {
|
for (var i = 0; i < names.length; ++i) {
|
||||||
if (i !== CONTEXT_INDEX) {
|
if (i !== CONTEXT_INDEX) {
|
||||||
res.push(this.getEventLocalName(eb, i));
|
res.push(`${this.getEventLocalName(eb, i)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -155,7 +155,7 @@ export class CodegenNameUtil {
|
||||||
for (var j = 0, jLen = this.directiveRecords.length; j < jLen; ++j) {
|
for (var j = 0, jLen = this.directiveRecords.length; j < jLen; ++j) {
|
||||||
var dRec = this.directiveRecords[j];
|
var dRec = this.directiveRecords[j];
|
||||||
fieldList.push(this.getDirectiveName(dRec.directiveIndex));
|
fieldList.push(this.getDirectiveName(dRec.directiveIndex));
|
||||||
if (dRec.isOnPushChangeDetection()) {
|
if (!dRec.isDefaultChangeDetection()) {
|
||||||
fieldList.push(this.getDetectorName(dRec.directiveIndex));
|
fieldList.push(this.getDetectorName(dRec.directiveIndex));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,7 +202,7 @@ export class CodegenNameUtil {
|
||||||
|
|
||||||
getAllDetectorNames(): List<string> {
|
getAllDetectorNames(): List<string> {
|
||||||
return ListWrapper.map(
|
return ListWrapper.map(
|
||||||
ListWrapper.filter(this.directiveRecords, r => r.isOnPushChangeDetection()),
|
ListWrapper.filter(this.directiveRecords, r => !r.isDefaultChangeDetection()),
|
||||||
(d) => this.getDetectorName(d.directiveIndex));
|
(d) => this.getDetectorName(d.directiveIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// TODO:vsavkin Use enums after switching to TypeScript
|
// TODO:vsavkin Use enums after switching to TypeScript
|
||||||
|
import {StringWrapper, normalizeBool, isBlank} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CHECK_ONCE means that after calling detectChanges the mode of the change detector
|
* CHECK_ONCE means that after calling detectChanges the mode of the change detector
|
||||||
|
@ -33,3 +34,7 @@ export const ON_PUSH: string = "ON_PUSH";
|
||||||
* DEFAULT means that the change detector's mode will be set to CHECK_ALWAYS during hydration.
|
* DEFAULT means that the change detector's mode will be set to CHECK_ALWAYS during hydration.
|
||||||
*/
|
*/
|
||||||
export const DEFAULT: string = "DEFAULT";
|
export const DEFAULT: string = "DEFAULT";
|
||||||
|
|
||||||
|
export function isDefaultChangeDetectionStrategy(changeDetectionStrategy: string): boolean {
|
||||||
|
return isBlank(changeDetectionStrategy) || StringWrapper.equals(changeDetectionStrategy, DEFAULT);
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import {ON_PUSH} from './constants';
|
import {StringWrapper, normalizeBool, isBlank} from 'angular2/src/facade/lang';
|
||||||
import {StringWrapper, normalizeBool} from 'angular2/src/facade/lang';
|
import {isDefaultChangeDetectionStrategy} from './constants';
|
||||||
|
|
||||||
export class DirectiveIndex {
|
export class DirectiveIndex {
|
||||||
constructor(public elementIndex: number, public directiveIndex: number) {}
|
constructor(public elementIndex: number, public directiveIndex: number) {}
|
||||||
|
@ -32,5 +32,7 @@ export class DirectiveRecord {
|
||||||
this.changeDetection = changeDetection;
|
this.changeDetection = changeDetection;
|
||||||
}
|
}
|
||||||
|
|
||||||
isOnPushChangeDetection(): boolean { return StringWrapper.equals(this.changeDetection, ON_PUSH); }
|
isDefaultChangeDetection(): boolean {
|
||||||
|
return isDefaultChangeDetectionStrategy(this.changeDetection);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,10 @@
|
||||||
import {isPresent, isBlank, BaseException, FunctionWrapper} from 'angular2/src/facade/lang';
|
import {
|
||||||
|
isPresent,
|
||||||
|
isBlank,
|
||||||
|
BaseException,
|
||||||
|
FunctionWrapper,
|
||||||
|
StringWrapper
|
||||||
|
} from 'angular2/src/facade/lang';
|
||||||
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||||
|
|
||||||
import {AbstractChangeDetector} from './abstract_change_detector';
|
import {AbstractChangeDetector} from './abstract_change_detector';
|
||||||
|
@ -64,7 +70,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
|
||||||
}
|
}
|
||||||
|
|
||||||
_markPathAsCheckOnce(proto: ProtoRecord): void {
|
_markPathAsCheckOnce(proto: ProtoRecord): void {
|
||||||
if (proto.bindingRecord.isOnPushChangeDetection()) {
|
if (!proto.bindingRecord.isDefaultChangeDetection()) {
|
||||||
var dir = proto.bindingRecord.directiveRecord;
|
var dir = proto.bindingRecord.directiveRecord;
|
||||||
this._getDetectorFor(dir.directiveIndex).markPathToRootAsCheckOnce();
|
this._getDetectorFor(dir.directiveIndex).markPathToRootAsCheckOnce();
|
||||||
}
|
}
|
||||||
|
@ -136,7 +142,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
|
||||||
|
|
||||||
if (proto.lastInDirective) {
|
if (proto.lastInDirective) {
|
||||||
changes = null;
|
changes = null;
|
||||||
if (isChanged && bindingRecord.isOnPushChangeDetection()) {
|
if (isChanged && !bindingRecord.isDefaultChangeDetection()) {
|
||||||
this._getDetectorFor(directiveRecord.directiveIndex).markAsCheckOnce();
|
this._getDetectorFor(directiveRecord.directiveIndex).markAsCheckOnce();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,7 +204,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var currValue = this._calculateCurrValue(proto, values, locals);
|
var currValue = this.observe(this._calculateCurrValue(proto, values, locals), proto.selfIndex);
|
||||||
if (proto.shouldBeChecked()) {
|
if (proto.shouldBeChecked()) {
|
||||||
var prevValue = this._readSelf(proto, values);
|
var prevValue = this._readSelf(proto, values);
|
||||||
if (!isSame(prevValue, currValue)) {
|
if (!isSame(prevValue, currValue)) {
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
import 'package:observe/observe.dart';
|
||||||
|
|
||||||
|
bool isObservable(value) => value is Observable;
|
|
@ -0,0 +1,3 @@
|
||||||
|
export function isObservable(value: any): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
|
@ -48,10 +48,8 @@ class ObservableListDiff extends DefaultIterableDiffer {
|
||||||
return super.diff(collection);
|
return super.diff(collection);
|
||||||
|
|
||||||
// No updates has been registered.
|
// No updates has been registered.
|
||||||
// Returning this tells change detection that object has not change,
|
|
||||||
// so it should NOT update the binding.
|
|
||||||
} else {
|
} else {
|
||||||
return this;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,7 +100,7 @@ class _CodegenState {
|
||||||
var protoRecords = createPropertyRecords(def);
|
var protoRecords = createPropertyRecords(def);
|
||||||
var eventBindings = createEventRecords(def);
|
var eventBindings = createEventRecords(def);
|
||||||
var names = new CodegenNameUtil(protoRecords, eventBindings, def.directiveRecords, _UTIL);
|
var names = new CodegenNameUtil(protoRecords, eventBindings, def.directiveRecords, _UTIL);
|
||||||
var logic = new CodegenLogicUtil(names, _UTIL);
|
var logic = new CodegenLogicUtil(names, _UTIL, def.strategy);
|
||||||
return new _CodegenState._(
|
return new _CodegenState._(
|
||||||
def.id,
|
def.id,
|
||||||
typeName,
|
typeName,
|
||||||
|
@ -193,7 +193,7 @@ class _CodegenState {
|
||||||
|
|
||||||
String _genMarkPathToRootAsCheckOnce(ProtoRecord r) {
|
String _genMarkPathToRootAsCheckOnce(ProtoRecord r) {
|
||||||
var br = r.bindingRecord;
|
var br = r.bindingRecord;
|
||||||
if (br.isOnPushChangeDetection()) {
|
if (!br.isDefaultChangeDetection()) {
|
||||||
return "${_names.getDetectorName(br.directiveRecord.directiveIndex)}.markPathToRootAsCheckOnce();";
|
return "${_names.getDetectorName(br.directiveRecord.directiveIndex)}.markPathToRootAsCheckOnce();";
|
||||||
} else {
|
} else {
|
||||||
return "";
|
return "";
|
||||||
|
@ -469,7 +469,7 @@ class _CodegenState {
|
||||||
|
|
||||||
String _genNotifyOnPushDetectors(ProtoRecord r) {
|
String _genNotifyOnPushDetectors(ProtoRecord r) {
|
||||||
var br = r.bindingRecord;
|
var br = r.bindingRecord;
|
||||||
if (!r.lastInDirective || !br.isOnPushChangeDetection()) return '';
|
if (!r.lastInDirective || br.isDefaultChangeDetection()) return '';
|
||||||
return '''
|
return '''
|
||||||
if($_IS_CHANGED_LOCAL) {
|
if($_IS_CHANGED_LOCAL) {
|
||||||
${_names.getDetectorName(br.directiveRecord.directiveIndex)}.markAsCheckOnce();
|
${_names.getDetectorName(br.directiveRecord.directiveIndex)}.markAsCheckOnce();
|
||||||
|
|
|
@ -103,7 +103,14 @@ export function getDefinition(id: string): TestDefinition {
|
||||||
let cdDef = new ChangeDetectorDefinition(id, null, [], [], eventRecords,
|
let cdDef = new ChangeDetectorDefinition(id, null, [], [], eventRecords,
|
||||||
[_DirectiveUpdating.basicRecords[0]], true);
|
[_DirectiveUpdating.basicRecords[0]], true);
|
||||||
testDef = new TestDefinition(id, cdDef, null);
|
testDef = new TestDefinition(id, cdDef, null);
|
||||||
|
|
||||||
|
} else if (id == "onPushObserve") {
|
||||||
|
var records = _createBindingRecords("a");
|
||||||
|
let cdDef = new ChangeDetectorDefinition(id, "ON_PUSH_OBSERVE", [], records, [], [], false);
|
||||||
|
testDef = new TestDefinition(id, cdDef, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (isBlank(testDef)) {
|
if (isBlank(testDef)) {
|
||||||
throw `No ChangeDetectorDefinition for ${id} available. Please modify this file if necessary.`;
|
throw `No ChangeDetectorDefinition for ${id} available. Please modify this file if necessary.`;
|
||||||
}
|
}
|
||||||
|
@ -129,6 +136,7 @@ export function getAllDefinitions(): List<TestDefinition> {
|
||||||
ListWrapper.concat(allDefs, StringMapWrapper.keys(_DirectiveUpdating.availableDefinitions));
|
ListWrapper.concat(allDefs, StringMapWrapper.keys(_DirectiveUpdating.availableDefinitions));
|
||||||
allDefs = ListWrapper.concat(allDefs, _availableEventDefinitions);
|
allDefs = ListWrapper.concat(allDefs, _availableEventDefinitions);
|
||||||
allDefs = ListWrapper.concat(allDefs, _availableHostEventDefinitions);
|
allDefs = ListWrapper.concat(allDefs, _availableHostEventDefinitions);
|
||||||
|
allDefs = ListWrapper.concat(allDefs, ["onPushObserve"]);
|
||||||
return ListWrapper.map(allDefs, (id) => getDefinition(id));
|
return ListWrapper.map(allDefs, (id) => getDefinition(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,16 @@
|
||||||
///<reference path="../../src/change_detection/pipe_transform.ts"/>
|
///<reference path="../../src/change_detection/pipe_transform.ts"/>
|
||||||
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib';
|
import {
|
||||||
|
ddescribe,
|
||||||
|
describe,
|
||||||
|
it,
|
||||||
|
iit,
|
||||||
|
xit,
|
||||||
|
expect,
|
||||||
|
beforeEach,
|
||||||
|
afterEach,
|
||||||
|
tick,
|
||||||
|
fakeAsync
|
||||||
|
} from 'angular2/test_lib';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CONST_EXPR,
|
CONST_EXPR,
|
||||||
|
@ -40,6 +51,7 @@ import {Pipes} from 'angular2/src/change_detection/pipes';
|
||||||
import {JitProtoChangeDetector} from 'angular2/src/change_detection/jit_proto_change_detector';
|
import {JitProtoChangeDetector} from 'angular2/src/change_detection/jit_proto_change_detector';
|
||||||
|
|
||||||
import {getDefinition} from './change_detector_config';
|
import {getDefinition} from './change_detector_config';
|
||||||
|
import {createObservableModel} from './change_detector_spec_util';
|
||||||
import {getFactoryById} from './generated/change_detector_classes';
|
import {getFactoryById} from './generated/change_detector_classes';
|
||||||
import {IS_DART} from '../platform';
|
import {IS_DART} from '../platform';
|
||||||
|
|
||||||
|
@ -735,6 +747,67 @@ export function main() {
|
||||||
|
|
||||||
expect(checkedDetector.mode).toEqual(CHECK_ONCE);
|
expect(checkedDetector.mode).toEqual(CHECK_ONCE);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (IS_DART) {
|
||||||
|
it('should mark ON_PUSH_OBSERVE detectors as CHECK_ONCE when an observable fires an event',
|
||||||
|
fakeAsync(() => {
|
||||||
|
var context = new TestDirective();
|
||||||
|
context.a = createObservableModel();
|
||||||
|
|
||||||
|
var cd = _createWithoutHydrate('onPushObserve').changeDetector;
|
||||||
|
cd.hydrate(context, null, directives, null);
|
||||||
|
cd.detectChanges();
|
||||||
|
|
||||||
|
expect(cd.mode).toEqual(CHECKED);
|
||||||
|
|
||||||
|
context.a.pushUpdate();
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(cd.mode).toEqual(CHECK_ONCE);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should unsubscribe from an old observable when an object changes', fakeAsync(() => {
|
||||||
|
var originalModel = createObservableModel();
|
||||||
|
var context = new TestDirective();
|
||||||
|
context.a = originalModel;
|
||||||
|
|
||||||
|
var cd = _createWithoutHydrate('onPushObserve').changeDetector;
|
||||||
|
cd.hydrate(context, null, directives, null);
|
||||||
|
cd.detectChanges();
|
||||||
|
|
||||||
|
context.a = createObservableModel();
|
||||||
|
cd.mode = CHECK_ONCE;
|
||||||
|
cd.detectChanges();
|
||||||
|
|
||||||
|
// Updating this model will not reenable the detector. This model is not longer
|
||||||
|
// used.
|
||||||
|
originalModel.pushUpdate();
|
||||||
|
tick();
|
||||||
|
expect(cd.mode).toEqual(CHECKED);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should unsubscribe from observables when dehydrating', fakeAsync(() => {
|
||||||
|
var originalModel = createObservableModel();
|
||||||
|
var context = new TestDirective();
|
||||||
|
context.a = originalModel;
|
||||||
|
|
||||||
|
var cd = _createWithoutHydrate('onPushObserve').changeDetector;
|
||||||
|
cd.hydrate(context, null, directives, null);
|
||||||
|
cd.detectChanges();
|
||||||
|
|
||||||
|
cd.dehydrate();
|
||||||
|
|
||||||
|
context.a = "not an observable model";
|
||||||
|
cd.hydrate(context, null, directives, null);
|
||||||
|
cd.detectChanges();
|
||||||
|
|
||||||
|
// Updating this model will not reenable the detector. This model is not longer
|
||||||
|
// used.
|
||||||
|
originalModel.pushUpdate();
|
||||||
|
tick();
|
||||||
|
expect(cd.mode).toEqual(CHECKED);
|
||||||
|
}));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
library angular.change_detection.change_detector_spec_util;
|
||||||
|
|
||||||
|
import 'package:observe/observe.dart' show Observable;
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
dynamic createObservableModel() {
|
||||||
|
return new Entity();
|
||||||
|
}
|
||||||
|
|
||||||
|
class Entity implements Observable {
|
||||||
|
Stream changes;
|
||||||
|
StreamController controller;
|
||||||
|
|
||||||
|
Entity() {
|
||||||
|
controller = new StreamController.broadcast();
|
||||||
|
changes = controller.stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
pushUpdate() {
|
||||||
|
controller.add("new");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get hasObservers => null;
|
||||||
|
bool deliverChanges() => null;
|
||||||
|
notifyPropertyChange(Symbol field, Object oldValue, Object newValue) => null;
|
||||||
|
void notifyChange(record) {}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
export function createObservableModel(): any {
|
||||||
|
return null;
|
||||||
|
}
|
|
@ -36,7 +36,7 @@ main() {
|
||||||
|
|
||||||
d.diff(c);
|
d.diff(c);
|
||||||
|
|
||||||
expect(d.diff(c)).toBe(d);
|
expect(d.diff(c)).toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return the wrapped value once a change has been triggered",
|
it("should return the wrapped value once a change has been triggered",
|
||||||
|
@ -50,7 +50,7 @@ main() {
|
||||||
c.add(3);
|
c.add(3);
|
||||||
|
|
||||||
// same value, because we have not detected the change yet
|
// same value, because we have not detected the change yet
|
||||||
expect(d.diff(c)).toBe(d);
|
expect(d.diff(c)).toBe(null);
|
||||||
|
|
||||||
// now we detect the change
|
// now we detect the change
|
||||||
flushMicrotasks();
|
flushMicrotasks();
|
||||||
|
@ -92,7 +92,7 @@ main() {
|
||||||
|
|
||||||
// pushing into the first collection has no effect, and we do not see the change
|
// pushing into the first collection has no effect, and we do not see the change
|
||||||
c1.add(3);
|
c1.add(3);
|
||||||
expect(d.diff(c2)).toBe(d);
|
expect(d.diff(c2)).toBe(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue