PageRenderTime 56ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/modules/angular2/src/core/change_detection/abstract_change_detector.ts

https://gitlab.com/swimly/angular
TypeScript | 324 lines | 237 code | 55 blank | 32 comment | 38 complexity | 3a084dcffabb40a70a1aa05e08d64482 MD5 | raw file
  1. import {assertionsEnabled, isPresent, isBlank, StringWrapper} from 'angular2/src/facade/lang';
  2. import {ListWrapper} from 'angular2/src/facade/collection';
  3. import {ChangeDetectionUtil} from './change_detection_util';
  4. import {ChangeDetectorRef, ChangeDetectorRef_} from './change_detector_ref';
  5. import {DirectiveIndex} from './directive_record';
  6. import {ChangeDetector, ChangeDispatcher} from './interfaces';
  7. import {Pipes} from './pipes';
  8. import {
  9. ChangeDetectionError,
  10. ExpressionChangedAfterItHasBeenCheckedException,
  11. DehydratedException
  12. } from './exceptions';
  13. import {BindingTarget} from './binding_record';
  14. import {Locals} from './parser/locals';
  15. import {ChangeDetectionStrategy, ChangeDetectorState} from './constants';
  16. import {wtfCreateScope, wtfLeave, WtfScopeFn} from '../profile/profile';
  17. import {isObservable} from './observable_facade';
  18. var _scope_check: WtfScopeFn = wtfCreateScope(`ChangeDetector#check(ascii id, bool throwOnChange)`);
  19. class _Context {
  20. constructor(public element: any, public componentElement: any, public context: any,
  21. public locals: any, public injector: any, public expression: any) {}
  22. }
  23. export class AbstractChangeDetector<T> implements ChangeDetector {
  24. contentChildren: any[] = [];
  25. viewChildren: any[] = [];
  26. parent: ChangeDetector;
  27. ref: ChangeDetectorRef;
  28. // The names of the below fields must be kept in sync with codegen_name_util.ts or
  29. // change detection will fail.
  30. state: ChangeDetectorState = ChangeDetectorState.NeverChecked;
  31. context: T;
  32. locals: Locals = null;
  33. mode: ChangeDetectionStrategy = null;
  34. pipes: Pipes = null;
  35. propertyBindingIndex: number;
  36. // This is an experimental feature. Works only in Dart.
  37. subscriptions: any[];
  38. streams: any[];
  39. constructor(public id: string, public dispatcher: ChangeDispatcher,
  40. public numberOfPropertyProtoRecords: number, public bindingTargets: BindingTarget[],
  41. public directiveIndices: DirectiveIndex[], public strategy: ChangeDetectionStrategy) {
  42. this.ref = new ChangeDetectorRef_(this);
  43. }
  44. addContentChild(cd: ChangeDetector): void {
  45. this.contentChildren.push(cd);
  46. cd.parent = this;
  47. }
  48. removeContentChild(cd: ChangeDetector): void { ListWrapper.remove(this.contentChildren, cd); }
  49. addViewChild(cd: ChangeDetector): void {
  50. this.viewChildren.push(cd);
  51. cd.parent = this;
  52. }
  53. removeViewChild(cd: ChangeDetector): void { ListWrapper.remove(this.viewChildren, cd); }
  54. remove(): void { this.parent.removeContentChild(this); }
  55. handleEvent(eventName: string, elIndex: number, locals: Locals): boolean {
  56. var res = this.handleEventInternal(eventName, elIndex, locals);
  57. this.markPathToRootAsCheckOnce();
  58. return res;
  59. }
  60. handleEventInternal(eventName: string, elIndex: number, locals: Locals): boolean { return false; }
  61. detectChanges(): void { this.runDetectChanges(false); }
  62. checkNoChanges(): void {
  63. if (assertionsEnabled()) {
  64. this.runDetectChanges(true);
  65. }
  66. }
  67. runDetectChanges(throwOnChange: boolean): void {
  68. if (this.mode === ChangeDetectionStrategy.Detached ||
  69. this.mode === ChangeDetectionStrategy.Checked || this.state === ChangeDetectorState.Errored)
  70. return;
  71. var s = _scope_check(this.id, throwOnChange);
  72. this.detectChangesInRecords(throwOnChange);
  73. this._detectChangesContentChildren(throwOnChange);
  74. if (!throwOnChange) this.afterContentLifecycleCallbacks();
  75. this._detectChangesInViewChildren(throwOnChange);
  76. if (!throwOnChange) this.afterViewLifecycleCallbacks();
  77. if (this.mode === ChangeDetectionStrategy.CheckOnce)
  78. this.mode = ChangeDetectionStrategy.Checked;
  79. this.state = ChangeDetectorState.CheckedBefore;
  80. wtfLeave(s);
  81. }
  82. // This method is not intended to be overridden. Subclasses should instead provide an
  83. // implementation of `detectChangesInRecordsInternal` which does the work of detecting changes
  84. // and which this method will call.
  85. // This method expects that `detectChangesInRecordsInternal` will set the property
  86. // `this.propertyBindingIndex` to the propertyBindingIndex of the first proto record. This is to
  87. // facilitate error reporting.
  88. detectChangesInRecords(throwOnChange: boolean): void {
  89. if (!this.hydrated()) {
  90. this.throwDehydratedError();
  91. }
  92. try {
  93. this.detectChangesInRecordsInternal(throwOnChange);
  94. } catch (e) {
  95. // throwOnChange errors aren't counted as fatal errors.
  96. if (!(e instanceof ExpressionChangedAfterItHasBeenCheckedException)) {
  97. this.state = ChangeDetectorState.Errored;
  98. }
  99. this._throwError(e, e.stack);
  100. }
  101. }
  102. // Subclasses should override this method to perform any work necessary to detect and report
  103. // changes. For example, changes should be reported via `ChangeDetectionUtil.addChange`, lifecycle
  104. // methods should be called, etc.
  105. // This implementation should also set `this.propertyBindingIndex` to the propertyBindingIndex of
  106. // the
  107. // first proto record to facilitate error reporting. See {@link #detectChangesInRecords}.
  108. detectChangesInRecordsInternal(throwOnChange: boolean): void {}
  109. // This method is not intended to be overridden. Subclasses should instead provide an
  110. // implementation of `hydrateDirectives`.
  111. hydrate(context: T, locals: Locals, directives: any, pipes: Pipes): void {
  112. this.mode = ChangeDetectionUtil.changeDetectionMode(this.strategy);
  113. this.context = context;
  114. if (this.strategy === ChangeDetectionStrategy.OnPushObserve) {
  115. this.observeComponent(context);
  116. }
  117. this.locals = locals;
  118. this.pipes = pipes;
  119. this.hydrateDirectives(directives);
  120. this.state = ChangeDetectorState.NeverChecked;
  121. }
  122. // Subclasses should override this method to hydrate any directives.
  123. hydrateDirectives(directives: any): void {}
  124. // This method is not intended to be overridden. Subclasses should instead provide an
  125. // implementation of `dehydrateDirectives`.
  126. dehydrate(): void {
  127. this.dehydrateDirectives(true);
  128. // This is an experimental feature. Works only in Dart.
  129. if (this.strategy === ChangeDetectionStrategy.OnPushObserve) {
  130. this._unsubsribeFromObservables();
  131. }
  132. this.context = null;
  133. this.locals = null;
  134. this.pipes = null;
  135. }
  136. // Subclasses should override this method to dehydrate any directives. This method should reverse
  137. // any work done in `hydrateDirectives`.
  138. dehydrateDirectives(destroyPipes: boolean): void {}
  139. hydrated(): boolean { return this.context !== null; }
  140. afterContentLifecycleCallbacks(): void {
  141. this.dispatcher.notifyAfterContentChecked();
  142. this.afterContentLifecycleCallbacksInternal();
  143. }
  144. afterContentLifecycleCallbacksInternal(): void {}
  145. afterViewLifecycleCallbacks(): void {
  146. this.dispatcher.notifyAfterViewChecked();
  147. this.afterViewLifecycleCallbacksInternal();
  148. }
  149. afterViewLifecycleCallbacksInternal(): void {}
  150. /** @internal */
  151. _detectChangesContentChildren(throwOnChange: boolean): void {
  152. var c = this.contentChildren;
  153. for (var i = 0; i < c.length; ++i) {
  154. c[i].runDetectChanges(throwOnChange);
  155. }
  156. }
  157. /** @internal */
  158. _detectChangesInViewChildren(throwOnChange: boolean): void {
  159. var c = this.viewChildren;
  160. for (var i = 0; i < c.length; ++i) {
  161. c[i].runDetectChanges(throwOnChange);
  162. }
  163. }
  164. markAsCheckOnce(): void { this.mode = ChangeDetectionStrategy.CheckOnce; }
  165. markPathToRootAsCheckOnce(): void {
  166. var c: ChangeDetector = this;
  167. while (isPresent(c) && c.mode !== ChangeDetectionStrategy.Detached) {
  168. if (c.mode === ChangeDetectionStrategy.Checked) c.mode = ChangeDetectionStrategy.CheckOnce;
  169. c = c.parent;
  170. }
  171. }
  172. // This is an experimental feature. Works only in Dart.
  173. private _unsubsribeFromObservables(): void {
  174. if (isPresent(this.subscriptions)) {
  175. for (var i = 0; i < this.subscriptions.length; ++i) {
  176. var s = this.subscriptions[i];
  177. if (isPresent(this.subscriptions[i])) {
  178. s.cancel();
  179. this.subscriptions[i] = null;
  180. }
  181. }
  182. }
  183. }
  184. // This is an experimental feature. Works only in Dart.
  185. observeValue(value: any, index: number): any {
  186. if (isObservable(value)) {
  187. this._createArrayToStoreObservables();
  188. if (isBlank(this.subscriptions[index])) {
  189. this.streams[index] = value.changes;
  190. this.subscriptions[index] = value.changes.listen((_) => this.ref.markForCheck());
  191. } else if (this.streams[index] !== value.changes) {
  192. this.subscriptions[index].cancel();
  193. this.streams[index] = value.changes;
  194. this.subscriptions[index] = value.changes.listen((_) => this.ref.markForCheck());
  195. }
  196. }
  197. return value;
  198. }
  199. // This is an experimental feature. Works only in Dart.
  200. observeDirective(value: any, index: number): any {
  201. if (isObservable(value)) {
  202. this._createArrayToStoreObservables();
  203. var arrayIndex = this.numberOfPropertyProtoRecords + index + 2; // +1 is component
  204. this.streams[arrayIndex] = value.changes;
  205. this.subscriptions[arrayIndex] = value.changes.listen((_) => this.ref.markForCheck());
  206. }
  207. return value;
  208. }
  209. // This is an experimental feature. Works only in Dart.
  210. observeComponent(value: any): any {
  211. if (isObservable(value)) {
  212. this._createArrayToStoreObservables();
  213. var index = this.numberOfPropertyProtoRecords + 1;
  214. this.streams[index] = value.changes;
  215. this.subscriptions[index] = value.changes.listen((_) => this.ref.markForCheck());
  216. }
  217. return value;
  218. }
  219. private _createArrayToStoreObservables(): void {
  220. if (isBlank(this.subscriptions)) {
  221. this.subscriptions = ListWrapper.createFixedSize(this.numberOfPropertyProtoRecords +
  222. this.directiveIndices.length + 2);
  223. this.streams = ListWrapper.createFixedSize(this.numberOfPropertyProtoRecords +
  224. this.directiveIndices.length + 2);
  225. }
  226. }
  227. getDirectiveFor(directives: any, index: number): any {
  228. return directives.getDirectiveFor(this.directiveIndices[index]);
  229. }
  230. getDetectorFor(directives: any, index: number): ChangeDetector {
  231. return directives.getDetectorFor(this.directiveIndices[index]);
  232. }
  233. notifyDispatcher(value: any): void {
  234. this.dispatcher.notifyOnBinding(this._currentBinding(), value);
  235. }
  236. logBindingUpdate(value: any): void {
  237. this.dispatcher.logBindingUpdate(this._currentBinding(), value);
  238. }
  239. addChange(changes: {[key: string]: any}, oldValue: any, newValue: any): {[key: string]: any} {
  240. if (isBlank(changes)) {
  241. changes = {};
  242. }
  243. changes[this._currentBinding().name] = ChangeDetectionUtil.simpleChange(oldValue, newValue);
  244. return changes;
  245. }
  246. private _throwError(exception: any, stack: any): void {
  247. var error;
  248. try {
  249. var c = this.dispatcher.getDebugContext(this._currentBinding().elementIndex, null);
  250. var context = isPresent(c) ? new _Context(c.element, c.componentElement, c.context, c.locals,
  251. c.injector, this._currentBinding().debug) :
  252. null;
  253. error = new ChangeDetectionError(this._currentBinding().debug, exception, stack, context);
  254. } catch (e) {
  255. // if an error happens during getting the debug context, we throw a ChangeDetectionError
  256. // without the extra information.
  257. error = new ChangeDetectionError(null, exception, stack, null);
  258. }
  259. throw error;
  260. }
  261. throwOnChangeError(oldValue: any, newValue: any): void {
  262. throw new ExpressionChangedAfterItHasBeenCheckedException(this._currentBinding().debug,
  263. oldValue, newValue, null);
  264. }
  265. throwDehydratedError(): void { throw new DehydratedException(); }
  266. private _currentBinding(): BindingTarget {
  267. return this.bindingTargets[this.propertyBindingIndex];
  268. }
  269. }