Skip to content

Commit cbfc9cb

Browse files
committed
feat(change_detection): added an experimental support for observables
1 parent ed81cb9 commit cbfc9cb

18 files changed

+243
-44
lines changed

modules/angular2/src/change_detection/abstract_change_detector.ts

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
1+
import {isPresent, isBlank, BaseException, StringWrapper} from 'angular2/src/facade/lang';
22
import {List, ListWrapper} from 'angular2/src/facade/collection';
33
import {ChangeDetectionUtil} from './change_detection_util';
44
import {ChangeDetectorRef} from './change_detector_ref';
@@ -13,8 +13,9 @@ import {
1313
import {ProtoRecord} from './proto_record';
1414
import {BindingRecord} from './binding_record';
1515
import {Locals} from './parser/locals';
16-
import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH} from './constants';
16+
import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED} from './constants';
1717
import {wtfCreateScope, wtfLeave, WtfScopeFn} from '../profile/profile';
18+
import {isObservable} from './observable_facade';
1819

1920
var _scope_check: WtfScopeFn = wtfCreateScope(`ChangeDetector#check(ascii id, bool throwOnChange)`);
2021

@@ -42,6 +43,10 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
4243
firstProtoInCurrentBinding: number;
4344
protos: List<ProtoRecord>;
4445

46+
// This is an experimental feature. Works only in Dart.
47+
subscriptions: any[];
48+
streams: any[];
49+
4550
constructor(public id: string, dispatcher: ChangeDispatcher, protos: List<ProtoRecord>,
4651
directiveRecords: List<DirectiveRecord>, public modeOnHydrate: string) {
4752
this.ref = new ChangeDetectorRef(this);
@@ -79,13 +84,14 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
7984
checkNoChanges(): void { throw new BaseException("Not implemented"); }
8085

8186
runDetectChanges(throwOnChange: boolean): void {
82-
if (this.mode === DETACHED || this.mode === CHECKED) return;
87+
if (StringWrapper.equals(this.mode, DETACHED) || StringWrapper.equals(this.mode, CHECKED))
88+
return;
8389
var s = _scope_check(this.id, throwOnChange);
8490
this.detectChangesInRecords(throwOnChange);
8591
this._detectChangesInLightDomChildren(throwOnChange);
8692
if (throwOnChange === false) this.callOnAllChangesDone();
8793
this._detectChangesInShadowDomChildren(throwOnChange);
88-
if (this.mode === CHECK_ONCE) this.mode = CHECKED;
94+
if (StringWrapper.equals(this.mode, CHECK_ONCE)) this.mode = CHECKED;
8995
wtfLeave(s);
9096
}
9197

@@ -132,6 +138,10 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
132138
// implementation of `dehydrateDirectives`.
133139
dehydrate(): void {
134140
this.dehydrateDirectives(true);
141+
142+
// This is an experimental feature. Works only in Dart.
143+
this.unsubsribeFromObservables();
144+
135145
this.context = null;
136146
this.locals = null;
137147
this.pipes = null;
@@ -163,12 +173,43 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
163173

164174
markPathToRootAsCheckOnce(): void {
165175
var c: ChangeDetector = this;
166-
while (isPresent(c) && c.mode != DETACHED) {
167-
if (c.mode === CHECKED) c.mode = CHECK_ONCE;
176+
while (isPresent(c) && !StringWrapper.equals(c.mode, DETACHED)) {
177+
if (StringWrapper.equals(c.mode, CHECKED)) c.mode = CHECK_ONCE;
168178
c = c.parent;
169179
}
170180
}
171181

182+
private unsubsribeFromObservables(): void {
183+
if (isPresent(this.subscriptions)) {
184+
for (var i = 0; i < this.subscriptions.length; ++i) {
185+
var s = this.subscriptions[i];
186+
if (isPresent(this.subscriptions[i])) {
187+
s.cancel();
188+
this.subscriptions[i] = null;
189+
}
190+
}
191+
}
192+
}
193+
194+
// This is an experimental feature. Works only in Dart.
195+
protected observe(value: any, index: number): any {
196+
if (isObservable(value)) {
197+
if (isBlank(this.subscriptions)) {
198+
this.subscriptions = ListWrapper.createFixedSize(this.protos.length + 1);
199+
this.streams = ListWrapper.createFixedSize(this.protos.length + 1);
200+
}
201+
if (isBlank(this.subscriptions[index])) {
202+
this.streams[index] = value.changes;
203+
this.subscriptions[index] = value.changes.listen((_) => this.ref.requestCheck());
204+
} else if (this.streams[index] !== value.changes) {
205+
this.subscriptions[index].cancel();
206+
this.streams[index] = value.changes;
207+
this.subscriptions[index] = value.changes.listen((_) => this.ref.requestCheck());
208+
}
209+
}
210+
return value;
211+
}
212+
172213
protected notifyDispatcher(value: any): void {
173214
this.dispatcher.notifyOnBinding(this._currentBinding(), value);
174215
}

modules/angular2/src/change_detection/binding_record.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ export class BindingRecord {
2323
return isPresent(this.directiveRecord) && this.directiveRecord.callOnChange;
2424
}
2525

26-
isOnPushChangeDetection(): boolean {
27-
return isPresent(this.directiveRecord) && this.directiveRecord.isOnPushChangeDetection();
26+
isDefaultChangeDetection(): boolean {
27+
return isBlank(this.directiveRecord) || this.directiveRecord.isDefaultChangeDetection();
2828
}
2929

3030
isDirective(): boolean { return this.mode === DIRECTIVE; }

modules/angular2/src/change_detection/change_detection_jit_generator.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export class ChangeDetectorJITGenerator {
3535
public directiveRecords: List<any>, private generateCheckNoChanges: boolean) {
3636
this._names =
3737
new CodegenNameUtil(this.records, this.eventBindings, this.directiveRecords, UTIL);
38-
this._logic = new CodegenLogicUtil(this._names, UTIL);
38+
this._logic = new CodegenLogicUtil(this._names, UTIL, changeDetectionStrategy);
3939
this._typeName = sanitizeName(`ChangeDetector_${this.id}`);
4040
}
4141

@@ -116,10 +116,10 @@ export class ChangeDetectorJITGenerator {
116116

117117
_genMarkPathToRootAsCheckOnce(r: ProtoRecord): string {
118118
var br = r.bindingRecord;
119-
if (br.isOnPushChangeDetection()) {
120-
return `${this._names.getDetectorName(br.directiveRecord.directiveIndex)}.markPathToRootAsCheckOnce();`;
121-
} else {
119+
if (br.isDefaultChangeDetection()) {
122120
return "";
121+
} else {
122+
return `${this._names.getDetectorName(br.directiveRecord.directiveIndex)}.markPathToRootAsCheckOnce();`;
123123
}
124124
}
125125

@@ -369,7 +369,7 @@ export class ChangeDetectorJITGenerator {
369369

370370
_genNotifyOnPushDetectors(r: ProtoRecord): string {
371371
var br = r.bindingRecord;
372-
if (!r.lastInDirective || !br.isOnPushChangeDetection()) return "";
372+
if (!r.lastInDirective || br.isDefaultChangeDetection()) return "";
373373
var retVal = `
374374
if(${IS_CHANGED_LOCAL}) {
375375
${this._names.getDetectorName(br.directiveRecord.directiveIndex)}.markAsCheckOnce();

modules/angular2/src/change_detection/change_detection_util.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
1-
import {CONST_EXPR, isPresent, isBlank, BaseException, Type} from 'angular2/src/facade/lang';
1+
import {
2+
CONST_EXPR,
3+
isPresent,
4+
isBlank,
5+
BaseException,
6+
Type,
7+
StringWrapper
8+
} from 'angular2/src/facade/lang';
29
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
310
import {ProtoRecord} from './proto_record';
4-
import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH} from './constants';
11+
import {
12+
CHECK_ALWAYS,
13+
CHECK_ONCE,
14+
CHECKED,
15+
DETACHED,
16+
isDefaultChangeDetectionStrategy
17+
} from './constants';
518
import {implementsOnDestroy} from './pipe_lifecycle_reflector';
619

720

@@ -166,7 +179,7 @@ export class ChangeDetectionUtil {
166179
}
167180

168181
static changeDetectionMode(strategy: string): string {
169-
return strategy == ON_PUSH ? CHECK_ONCE : CHECK_ALWAYS;
182+
return isDefaultChangeDetectionStrategy(strategy) ? CHECK_ALWAYS : CHECK_ONCE;
170183
}
171184

172185
static simpleChange(previousValue: any, currentValue: any): SimpleChange {

modules/angular2/src/change_detection/codegen_logic_util.ts

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
import {ListWrapper} from 'angular2/src/facade/collection';
2-
import {BaseException, Json} from 'angular2/src/facade/lang';
2+
import {BaseException, Json, StringWrapper} from 'angular2/src/facade/lang';
33
import {CodegenNameUtil} from './codegen_name_util';
44
import {codify, combineGeneratedStrings, rawString} from './codegen_facade';
55
import {ProtoRecord, RecordType} from './proto_record';
66

7+
/**
8+
* This is an experimental feature. Works only in Dart.
9+
*/
10+
const ON_PUSH_OBSERVE = "ON_PUSH_OBSERVE";
11+
712
/**
813
* Class responsible for providing change detection logic for chagne detector classes.
914
*/
1015
export class CodegenLogicUtil {
11-
constructor(private _names: CodegenNameUtil, private _utilName: string) {}
16+
constructor(private _names: CodegenNameUtil, private _utilName: string,
17+
private _changeDetection: string) {}
1218

1319
/**
1420
* Generates a statement which updates the local variable representing `protoRec` with the current
@@ -46,28 +52,31 @@ export class CodegenLogicUtil {
4652
break;
4753

4854
case RecordType.PROPERTY_READ:
49-
rhs = `${context}.${protoRec.name}`;
55+
rhs = this._observe(`${context}.${protoRec.name}`, protoRec);
5056
break;
5157

5258
case RecordType.SAFE_PROPERTY:
53-
rhs = `${this._utilName}.isValueBlank(${context}) ? null : ${context}.${protoRec.name}`;
59+
var read = this._observe(`${context}.${protoRec.name}`, protoRec);
60+
rhs =
61+
`${this._utilName}.isValueBlank(${context}) ? null : ${this._observe(read, protoRec)}`;
5462
break;
5563

5664
case RecordType.PROPERTY_WRITE:
5765
rhs = `${context}.${protoRec.name} = ${getLocalName(protoRec.args[0])}`;
5866
break;
5967

6068
case RecordType.LOCAL:
61-
rhs = `${localsAccessor}.get(${rawString(protoRec.name)})`;
69+
rhs = this._observe(`${localsAccessor}.get(${rawString(protoRec.name)})`, protoRec);
6270
break;
6371

6472
case RecordType.INVOKE_METHOD:
65-
rhs = `${context}.${protoRec.name}(${argString})`;
73+
rhs = this._observe(`${context}.${protoRec.name}(${argString})`, protoRec);
6674
break;
6775

6876
case RecordType.SAFE_INVOKE_METHOD:
77+
var invoke = `${context}.${protoRec.name}(${argString})`;
6978
rhs =
70-
`${this._utilName}.isValueBlank(${context}) ? null : ${context}.${protoRec.name}(${argString})`;
79+
`${this._utilName}.isValueBlank(${context}) ? null : ${this._observe(invoke, protoRec)}`;
7180
break;
7281

7382
case RecordType.INVOKE_CLOSURE:
@@ -87,7 +96,7 @@ export class CodegenLogicUtil {
8796
break;
8897

8998
case RecordType.KEYED_READ:
90-
rhs = `${context}[${getLocalName(protoRec.args[0])}]`;
99+
rhs = this._observe(`${context}[${getLocalName(protoRec.args[0])}]`, protoRec);
91100
break;
92101

93102
case RecordType.KEYED_WRITE:
@@ -104,6 +113,14 @@ export class CodegenLogicUtil {
104113
return `${getLocalName(protoRec.selfIndex)} = ${rhs};`;
105114
}
106115

116+
_observe(exp: string, rec: ProtoRecord): string {
117+
// This is an experimental feature. Works only in Dart.
118+
if (StringWrapper.equals(this._changeDetection, ON_PUSH_OBSERVE)) {
119+
return `this.observe(${exp}, ${rec.selfIndex})`;
120+
} else {
121+
return exp;
122+
}
123+
}
107124

108125
_genInterpolation(protoRec: ProtoRecord): string {
109126
var iVals = [];

modules/angular2/src/change_detection/codegen_name_util.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ export class CodegenNameUtil {
124124
MapWrapper.forEach(this._sanitizedEventNames, (names, eb) => {
125125
for (var i = 0; i < names.length; ++i) {
126126
if (i !== CONTEXT_INDEX) {
127-
res.push(this.getEventLocalName(eb, i));
127+
res.push(`${this.getEventLocalName(eb, i)}`);
128128
}
129129
}
130130
});
@@ -155,7 +155,7 @@ export class CodegenNameUtil {
155155
for (var j = 0, jLen = this.directiveRecords.length; j < jLen; ++j) {
156156
var dRec = this.directiveRecords[j];
157157
fieldList.push(this.getDirectiveName(dRec.directiveIndex));
158-
if (dRec.isOnPushChangeDetection()) {
158+
if (!dRec.isDefaultChangeDetection()) {
159159
fieldList.push(this.getDetectorName(dRec.directiveIndex));
160160
}
161161
}
@@ -202,7 +202,7 @@ export class CodegenNameUtil {
202202

203203
getAllDetectorNames(): List<string> {
204204
return ListWrapper.map(
205-
ListWrapper.filter(this.directiveRecords, r => r.isOnPushChangeDetection()),
205+
ListWrapper.filter(this.directiveRecords, r => !r.isDefaultChangeDetection()),
206206
(d) => this.getDetectorName(d.directiveIndex));
207207
}
208208

modules/angular2/src/change_detection/constants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// TODO:vsavkin Use enums after switching to TypeScript
2+
import {StringWrapper, normalizeBool, isBlank} from 'angular2/src/facade/lang';
23

34
/**
45
* CHECK_ONCE means that after calling detectChanges the mode of the change detector
@@ -33,3 +34,7 @@ export const ON_PUSH: string = "ON_PUSH";
3334
* DEFAULT means that the change detector's mode will be set to CHECK_ALWAYS during hydration.
3435
*/
3536
export const DEFAULT: string = "DEFAULT";
37+
38+
export function isDefaultChangeDetectionStrategy(changeDetectionStrategy: string): boolean {
39+
return isBlank(changeDetectionStrategy) || StringWrapper.equals(changeDetectionStrategy, DEFAULT);
40+
}

modules/angular2/src/change_detection/directive_record.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import {ON_PUSH} from './constants';
2-
import {StringWrapper, normalizeBool} from 'angular2/src/facade/lang';
1+
import {StringWrapper, normalizeBool, isBlank} from 'angular2/src/facade/lang';
2+
import {isDefaultChangeDetectionStrategy} from './constants';
33

44
export class DirectiveIndex {
55
constructor(public elementIndex: number, public directiveIndex: number) {}
@@ -32,5 +32,7 @@ export class DirectiveRecord {
3232
this.changeDetection = changeDetection;
3333
}
3434

35-
isOnPushChangeDetection(): boolean { return StringWrapper.equals(this.changeDetection, ON_PUSH); }
35+
isDefaultChangeDetection(): boolean {
36+
return isDefaultChangeDetectionStrategy(this.changeDetection);
37+
}
3638
}

modules/angular2/src/change_detection/dynamic_change_detector.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import {isPresent, isBlank, BaseException, FunctionWrapper} from 'angular2/src/facade/lang';
1+
import {
2+
isPresent,
3+
isBlank,
4+
BaseException,
5+
FunctionWrapper,
6+
StringWrapper
7+
} from 'angular2/src/facade/lang';
28
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
39

410
import {AbstractChangeDetector} from './abstract_change_detector';
@@ -64,7 +70,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
6470
}
6571

6672
_markPathAsCheckOnce(proto: ProtoRecord): void {
67-
if (proto.bindingRecord.isOnPushChangeDetection()) {
73+
if (!proto.bindingRecord.isDefaultChangeDetection()) {
6874
var dir = proto.bindingRecord.directiveRecord;
6975
this._getDetectorFor(dir.directiveIndex).markPathToRootAsCheckOnce();
7076
}
@@ -136,7 +142,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
136142

137143
if (proto.lastInDirective) {
138144
changes = null;
139-
if (isChanged && bindingRecord.isOnPushChangeDetection()) {
145+
if (isChanged && !bindingRecord.isDefaultChangeDetection()) {
140146
this._getDetectorFor(directiveRecord.directiveIndex).markAsCheckOnce();
141147
}
142148

@@ -198,7 +204,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
198204
return null;
199205
}
200206

201-
var currValue = this._calculateCurrValue(proto, values, locals);
207+
var currValue = this.observe(this._calculateCurrValue(proto, values, locals), proto.selfIndex);
202208
if (proto.shouldBeChecked()) {
203209
var prevValue = this._readSelf(proto, values);
204210
if (!isSame(prevValue, currValue)) {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import 'package:observe/observe.dart';
2+
3+
bool isObservable(value) => value is Observable;

0 commit comments

Comments
 (0)