@angular-wave/angular.ts 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/.eslintignore +1 -0
  2. package/.eslintrc.cjs +29 -0
  3. package/.github/workflows/playwright.yml +27 -0
  4. package/CHANGELOG.md +17974 -0
  5. package/CODE_OF_CONDUCT.md +3 -0
  6. package/CONTRIBUTING.md +246 -0
  7. package/DEVELOPERS.md +488 -0
  8. package/LICENSE +22 -0
  9. package/Makefile +31 -0
  10. package/README.md +115 -0
  11. package/RELEASE.md +98 -0
  12. package/SECURITY.md +16 -0
  13. package/TRIAGING.md +135 -0
  14. package/css/angular.css +22 -0
  15. package/dist/angular-ts.cjs.js +36843 -0
  16. package/dist/angular-ts.esm.js +36841 -0
  17. package/dist/angular-ts.umd.js +36848 -0
  18. package/dist/build/angular-animate.js +4272 -0
  19. package/dist/build/angular-aria.js +426 -0
  20. package/dist/build/angular-message-format.js +1072 -0
  21. package/dist/build/angular-messages.js +829 -0
  22. package/dist/build/angular-mocks.js +3757 -0
  23. package/dist/build/angular-parse-ext.js +1275 -0
  24. package/dist/build/angular-resource.js +911 -0
  25. package/dist/build/angular-route.js +1266 -0
  26. package/dist/build/angular-sanitize.js +891 -0
  27. package/dist/build/angular-touch.js +368 -0
  28. package/dist/build/angular.js +36600 -0
  29. package/e2e/unit.spec.ts +15 -0
  30. package/images/android-chrome-192x192.png +0 -0
  31. package/images/android-chrome-512x512.png +0 -0
  32. package/images/apple-touch-icon.png +0 -0
  33. package/images/favicon-16x16.png +0 -0
  34. package/images/favicon-32x32.png +0 -0
  35. package/images/favicon.ico +0 -0
  36. package/images/site.webmanifest +1 -0
  37. package/index.html +104 -0
  38. package/package.json +47 -0
  39. package/playwright.config.ts +78 -0
  40. package/public/circle.html +1 -0
  41. package/public/my_child_directive.html +1 -0
  42. package/public/my_directive.html +1 -0
  43. package/public/my_other_directive.html +1 -0
  44. package/public/test.html +1 -0
  45. package/rollup.config.js +31 -0
  46. package/src/animations/animateCache.js +55 -0
  47. package/src/animations/animateChildrenDirective.js +105 -0
  48. package/src/animations/animateCss.js +1139 -0
  49. package/src/animations/animateCssDriver.js +291 -0
  50. package/src/animations/animateJs.js +367 -0
  51. package/src/animations/animateJsDriver.js +67 -0
  52. package/src/animations/animateQueue.js +851 -0
  53. package/src/animations/animation.js +506 -0
  54. package/src/animations/module.js +779 -0
  55. package/src/animations/ngAnimateSwap.js +119 -0
  56. package/src/animations/rafScheduler.js +50 -0
  57. package/src/animations/shared.js +378 -0
  58. package/src/constants.js +20 -0
  59. package/src/core/animate.js +845 -0
  60. package/src/core/animateCss.js +73 -0
  61. package/src/core/animateRunner.js +195 -0
  62. package/src/core/attributes.js +199 -0
  63. package/src/core/cache.js +45 -0
  64. package/src/core/compile.js +4727 -0
  65. package/src/core/controller.js +225 -0
  66. package/src/core/exceptionHandler.js +63 -0
  67. package/src/core/filter.js +146 -0
  68. package/src/core/interpolate.js +442 -0
  69. package/src/core/interval.js +188 -0
  70. package/src/core/intervalFactory.js +57 -0
  71. package/src/core/location.js +1086 -0
  72. package/src/core/parser/parse.js +2562 -0
  73. package/src/core/parser/parse.md +13 -0
  74. package/src/core/q.js +746 -0
  75. package/src/core/rootScope.js +1596 -0
  76. package/src/core/sanitizeUri.js +85 -0
  77. package/src/core/sce.js +1161 -0
  78. package/src/core/taskTrackerFactory.js +125 -0
  79. package/src/core/timeout.js +121 -0
  80. package/src/core/urlUtils.js +187 -0
  81. package/src/core/utils.js +1349 -0
  82. package/src/directive/a.js +37 -0
  83. package/src/directive/attrs.js +283 -0
  84. package/src/directive/bind.js +51 -0
  85. package/src/directive/bind.md +142 -0
  86. package/src/directive/change.js +12 -0
  87. package/src/directive/change.md +25 -0
  88. package/src/directive/cloak.js +12 -0
  89. package/src/directive/cloak.md +24 -0
  90. package/src/directive/events.js +75 -0
  91. package/src/directive/events.md +166 -0
  92. package/src/directive/form.js +725 -0
  93. package/src/directive/init.js +15 -0
  94. package/src/directive/init.md +41 -0
  95. package/src/directive/input.js +1783 -0
  96. package/src/directive/list.js +46 -0
  97. package/src/directive/list.md +22 -0
  98. package/src/directive/ngClass.js +249 -0
  99. package/src/directive/ngController.js +64 -0
  100. package/src/directive/ngCsp.js +82 -0
  101. package/src/directive/ngIf.js +134 -0
  102. package/src/directive/ngInclude.js +217 -0
  103. package/src/directive/ngModel.js +1356 -0
  104. package/src/directive/ngModelOptions.js +509 -0
  105. package/src/directive/ngOptions.js +670 -0
  106. package/src/directive/ngRef.js +90 -0
  107. package/src/directive/ngRepeat.js +650 -0
  108. package/src/directive/ngShowHide.js +255 -0
  109. package/src/directive/ngSwitch.js +178 -0
  110. package/src/directive/ngTransclude.js +98 -0
  111. package/src/directive/non-bindable.js +11 -0
  112. package/src/directive/non-bindable.md +17 -0
  113. package/src/directive/script.js +30 -0
  114. package/src/directive/select.js +624 -0
  115. package/src/directive/style.js +25 -0
  116. package/src/directive/style.md +23 -0
  117. package/src/directive/validators.js +329 -0
  118. package/src/exts/aria.js +544 -0
  119. package/src/exts/messages.js +852 -0
  120. package/src/filters/filter.js +207 -0
  121. package/src/filters/filter.md +69 -0
  122. package/src/filters/filters.js +239 -0
  123. package/src/filters/json.md +16 -0
  124. package/src/filters/limit-to.js +43 -0
  125. package/src/filters/limit-to.md +19 -0
  126. package/src/filters/order-by.js +183 -0
  127. package/src/filters/order-by.md +83 -0
  128. package/src/index.js +13 -0
  129. package/src/injector.js +1034 -0
  130. package/src/jqLite.js +1117 -0
  131. package/src/loader.js +1320 -0
  132. package/src/public.js +215 -0
  133. package/src/routeToRegExp.js +41 -0
  134. package/src/services/anchorScroll.js +135 -0
  135. package/src/services/browser.js +321 -0
  136. package/src/services/cacheFactory.js +398 -0
  137. package/src/services/cookieReader.js +72 -0
  138. package/src/services/document.js +64 -0
  139. package/src/services/http.js +1537 -0
  140. package/src/services/httpBackend.js +206 -0
  141. package/src/services/log.js +160 -0
  142. package/src/services/templateRequest.js +139 -0
  143. package/test/angular.spec.js +2153 -0
  144. package/test/aria/aria.spec.js +1245 -0
  145. package/test/binding.spec.js +504 -0
  146. package/test/build-test.html +14 -0
  147. package/test/injector.spec.js +2327 -0
  148. package/test/jasmine/jasmine-5.1.2/boot0.js +65 -0
  149. package/test/jasmine/jasmine-5.1.2/boot1.js +133 -0
  150. package/test/jasmine/jasmine-5.1.2/jasmine-html.js +963 -0
  151. package/test/jasmine/jasmine-5.1.2/jasmine.css +320 -0
  152. package/test/jasmine/jasmine-5.1.2/jasmine.js +10824 -0
  153. package/test/jasmine/jasmine-5.1.2/jasmine_favicon.png +0 -0
  154. package/test/jasmine/jasmine-browser.json +17 -0
  155. package/test/jasmine/jasmine.json +9 -0
  156. package/test/jqlite.spec.js +2133 -0
  157. package/test/loader.spec.js +219 -0
  158. package/test/messages/messages.spec.js +1146 -0
  159. package/test/min-err.spec.js +174 -0
  160. package/test/mock-test.html +13 -0
  161. package/test/module-test.html +15 -0
  162. package/test/ng/anomate.spec.js +606 -0
  163. package/test/ng/cache-factor.spec.js +334 -0
  164. package/test/ng/compile.spec.js +17956 -0
  165. package/test/ng/controller-provider.spec.js +227 -0
  166. package/test/ng/cookie-reader.spec.js +98 -0
  167. package/test/ng/directive/a.spec.js +192 -0
  168. package/test/ng/directive/bind.spec.js +334 -0
  169. package/test/ng/directive/boolean.spec.js +136 -0
  170. package/test/ng/directive/change.spec.js +71 -0
  171. package/test/ng/directive/class.spec.js +858 -0
  172. package/test/ng/directive/click.spec.js +38 -0
  173. package/test/ng/directive/cloak.spec.js +44 -0
  174. package/test/ng/directive/constoller.spec.js +194 -0
  175. package/test/ng/directive/element-style.spec.js +92 -0
  176. package/test/ng/directive/event.spec.js +282 -0
  177. package/test/ng/directive/form.spec.js +1518 -0
  178. package/test/ng/directive/href.spec.js +143 -0
  179. package/test/ng/directive/if.spec.js +402 -0
  180. package/test/ng/directive/include.spec.js +828 -0
  181. package/test/ng/directive/init.spec.js +68 -0
  182. package/test/ng/directive/input.spec.js +3810 -0
  183. package/test/ng/directive/list.spec.js +170 -0
  184. package/test/ng/directive/model-options.spec.js +1008 -0
  185. package/test/ng/directive/model.spec.js +1905 -0
  186. package/test/ng/directive/non-bindable.spec.js +55 -0
  187. package/test/ng/directive/options.spec.js +3583 -0
  188. package/test/ng/directive/ref.spec.js +575 -0
  189. package/test/ng/directive/repeat.spec.js +1675 -0
  190. package/test/ng/directive/script.spec.js +52 -0
  191. package/test/ng/directive/scrset.spec.js +67 -0
  192. package/test/ng/directive/select.spec.js +2541 -0
  193. package/test/ng/directive/show-hide.spec.js +253 -0
  194. package/test/ng/directive/src.spec.js +157 -0
  195. package/test/ng/directive/style.spec.js +178 -0
  196. package/test/ng/directive/switch.spec.js +647 -0
  197. package/test/ng/directive/validators.spec.js +717 -0
  198. package/test/ng/document.spec.js +52 -0
  199. package/test/ng/filter/filter.spec.js +714 -0
  200. package/test/ng/filter/filters.spec.js +35 -0
  201. package/test/ng/filter/limit-to.spec.js +251 -0
  202. package/test/ng/filter/order-by.spec.js +891 -0
  203. package/test/ng/filter.spec.js +149 -0
  204. package/test/ng/http-backend.spec.js +398 -0
  205. package/test/ng/http.spec.js +4071 -0
  206. package/test/ng/interpolate.spec.js +642 -0
  207. package/test/ng/interval.spec.js +343 -0
  208. package/test/ng/location.spec.js +3488 -0
  209. package/test/ng/on.spec.js +229 -0
  210. package/test/ng/parse.spec.js +4655 -0
  211. package/test/ng/prop.spec.js +805 -0
  212. package/test/ng/q.spec.js +2904 -0
  213. package/test/ng/root-element.spec.js +16 -0
  214. package/test/ng/sanitize-uri.spec.js +249 -0
  215. package/test/ng/sce.spec.js +660 -0
  216. package/test/ng/scope.spec.js +3442 -0
  217. package/test/ng/template-request.spec.js +236 -0
  218. package/test/ng/timeout.spec.js +351 -0
  219. package/test/ng/url-utils.spec.js +156 -0
  220. package/test/ng/utils.spec.js +144 -0
  221. package/test/original-test.html +21 -0
  222. package/test/public.spec.js +34 -0
  223. package/test/sanitize/bing-html.spec.js +36 -0
  224. package/test/server/express.js +158 -0
  225. package/test/test-utils.js +11 -0
  226. package/tsconfig.json +17 -0
  227. package/types/angular.d.ts +138 -0
  228. package/types/global.d.ts +9 -0
  229. package/types/index.d.ts +2357 -0
  230. package/types/jqlite.d.ts +558 -0
  231. package/vite.config.js +14 -0
@@ -0,0 +1,1596 @@
1
+ import {
2
+ concat,
3
+ forEach,
4
+ minErr,
5
+ nextUid,
6
+ isFunction,
7
+ isUndefined,
8
+ isObject,
9
+ isArrayLike,
10
+ isNumberNaN,
11
+ arrayRemove,
12
+ equals,
13
+ } from "./utils";
14
+
15
+ /**
16
+ * @typedef {"$apply" | "$digest"} ScopePhase
17
+ */
18
+
19
+ /**
20
+ * @ngdoc provider
21
+ * @name $rootScopeProvider
22
+ * @description
23
+ *
24
+ * Provider for the $rootScope service.
25
+ */
26
+
27
+ /**
28
+ * @ngdoc method
29
+ * @name $rootScopeProvider#digestTtl
30
+ * @description
31
+ *
32
+ * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and
33
+ * assuming that the model is unstable.
34
+ *
35
+ * The current default is 10 iterations.
36
+ *
37
+ * In complex applications it's possible that the dependencies between `$watch`s will result in
38
+ * several digest iterations. However if an application needs more than the default 10 digest
39
+ * iterations for its model to stabilize then you should investigate what is causing the model to
40
+ * continuously change during the digest.
41
+ *
42
+ * Increasing the TTL could have performance implications, so you should not change it without
43
+ * proper justification.
44
+ *
45
+ * @param {number} limit The number of digest iterations.
46
+ */
47
+
48
+ let TTL = 10;
49
+
50
+ const $rootScopeMinErr = minErr("$rootScope");
51
+ const $$asyncQueue = [];
52
+ const $$postDigestQueue = [];
53
+ const $$applyAsyncQueue = [];
54
+ let postDigestQueuePosition = 0;
55
+ let lastDirtyWatch = null;
56
+ let applyAsyncId = null;
57
+ let $parse;
58
+ let $browser;
59
+ let $exceptionHandler;
60
+
61
+ /**
62
+ * @ngdoc service
63
+ * @name $rootScope
64
+ *
65
+ * @description
66
+ *
67
+ * Every application has a single root {@link ng.$rootScope.Scope scope}.
68
+ * All other scopes are descendant scopes of the root scope. Scopes provide separation
69
+ * between the model and the view, via a mechanism for watching the model for changes.
70
+ * They also provide event emission/broadcast and subscription facility. See the
71
+ * {@link guide/scope developer guide on scopes}.
72
+ */
73
+ export function $RootScopeProvider() {
74
+ this.digestTtl = function (value) {
75
+ if (arguments.length) {
76
+ TTL = value;
77
+ }
78
+ return TTL;
79
+ };
80
+
81
+ this.$get = [
82
+ "$exceptionHandler",
83
+ "$parse",
84
+ "$browser",
85
+ function (exceptionHandler, parse, browser) {
86
+ $exceptionHandler = exceptionHandler;
87
+ $parse = parse;
88
+ $browser = browser;
89
+ /**
90
+ * @ngdoc type
91
+ * @name $rootScope.Scope
92
+ *
93
+ * @description
94
+ * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the
95
+ * {@link auto.$injector $injector}. Child scopes are created using the
96
+ * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when
97
+ * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for
98
+ * an in-depth introduction and usage examples.
99
+ *
100
+ *
101
+ * ## Inheritance
102
+ * A scope can inherit from a parent scope, as in this example:
103
+ * ```js
104
+ let parent = $rootScope;
105
+ let child = parent.$new();
106
+
107
+ parent.salutation = "Hello";
108
+ expect(child.salutation).toEqual('Hello');
109
+
110
+ child.salutation = "Welcome";
111
+ expect(child.salutation).toEqual('Welcome');
112
+ expect(parent.salutation).toEqual('Hello');
113
+ * ```
114
+ *
115
+ *
116
+ *
117
+ * @param {Object.<string, function()>=} providers Map of service factory which need to be
118
+ * provided for the current scope. Defaults to {@link ng}.
119
+ * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should
120
+ * append/override services provided by `providers`. This is handy
121
+ * when unit-testing and having the need to override a default
122
+ * service.
123
+ * @returns {Object} Newly created scope.
124
+ *
125
+ */
126
+
127
+ const $rootScope = new Scope();
128
+ return $rootScope;
129
+ },
130
+ ];
131
+ }
132
+
133
+ /**
134
+ * DESIGN NOTES
135
+ *
136
+ * The design decisions behind the scope are heavily favored for speed and memory consumption.
137
+ *
138
+ * The typical use of scope is to watch the expressions, which most of the time return the same
139
+ * value as last time so we optimize the operation.
140
+ *
141
+ * Closures construction is expensive in terms of speed as well as memory:
142
+ * - No closures, instead use prototypical inheritance for API
143
+ * - Internal state needs to be stored on scope directly, which means that private state is
144
+ * exposed as $$____ properties
145
+ *
146
+ * Loop operations are optimized by using while(count--) { ... }
147
+ * - This means that in order to keep the same order of execution as addition we have to add
148
+ * items to the array at the beginning (unshift) instead of at the end (push)
149
+ *
150
+ * Child scopes are created and removed often
151
+ * - Using an array would be slow since inserts in the middle are expensive; so we use linked lists
152
+ *
153
+ * There are fewer watches than observers. This is why you don't want the observer to be implemented
154
+ * in the same way as watch. Watch requires return of the initialization function which is expensive
155
+ * to construct.
156
+ */
157
+
158
+ export function getQueues() {
159
+ return {
160
+ asyncQueue: $$asyncQueue,
161
+ postDigestQueue: $$postDigestQueue,
162
+ applyAsyncQueue: $$applyAsyncQueue,
163
+ };
164
+ }
165
+
166
+ /**
167
+ * @param { * } parse
168
+ * @param {import('../services/browser').Browser} browser
169
+ * @param {Function} exceptionHandler
170
+ */
171
+ export function initialize(parse, browser, exceptionHandler) {
172
+ $parse = parse;
173
+ $browser = browser;
174
+ $exceptionHandler = exceptionHandler;
175
+ }
176
+
177
+ /**
178
+ * @type {angular.IScope}
179
+ */
180
+ class Scope {
181
+ constructor() {
182
+ /**
183
+ * @type {number} Unique scope ID (monotonically increasing) useful for debugging.
184
+ */
185
+ this.$id = nextUid();
186
+ this.$$phase = null;
187
+
188
+ /**
189
+ * @type {?Scope} Reference to the parent scope.
190
+ */
191
+ this.$parent = null;
192
+
193
+ /**
194
+ * @type {Scope}
195
+ */
196
+ this.$root = this;
197
+
198
+ this.$$watchers = null;
199
+ this.$$nextSibling = null;
200
+ this.$$prevSibling = null;
201
+ this.$$childHead = null;
202
+ this.$$childTail = null;
203
+ this.$$destroyed = false;
204
+ this.$$suspended = false;
205
+ this.$$listeners = {};
206
+ this.$$listenerCount = {};
207
+ this.$$watchersCount = 0;
208
+ this.$$isolateBindings = null;
209
+
210
+ /**
211
+ * @type {Scope}
212
+ */
213
+ this.$$ChildScope = null;
214
+ }
215
+
216
+ /**
217
+ * @description
218
+ * Creates a new child {@link Scope}.
219
+ *
220
+ * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event.
221
+ * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}.
222
+ *
223
+ * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is
224
+ * desired for the scope and its child scopes to be permanently detached from the parent and
225
+ * thus stop participating in model change detection and listener notification by invoking.
226
+ *
227
+ * @param {?boolean} isolate If true, then the scope does not prototypically inherit from the
228
+ * parent scope. The scope is isolated, as it can not see parent scope properties.
229
+ * When creating widgets, it is useful for the widget to not accidentally read parent
230
+ * state.
231
+ *
232
+ * @param {?angular.IScope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent`
233
+ * of the newly created scope. Defaults to `this` scope if not provided.
234
+ * This is used when creating a transclude scope to correctly place it
235
+ * in the scope hierarchy while maintaining the correct prototypical
236
+ * inheritance.
237
+ *
238
+ * @returns {angular.IScope} The newly created child scope.
239
+ *
240
+ */
241
+ $new(isolate, parent) {
242
+ let child;
243
+ if (isolate) {
244
+ child = new Scope();
245
+ child.$root = this.$root;
246
+ } else {
247
+ child = Object.create(this);
248
+ child.$id = nextUid();
249
+ child.$$watchers = null;
250
+ child.$$nextSibling = null;
251
+ child.$$childHead = null;
252
+ child.$$childTail = null;
253
+ child.$$listeners = {};
254
+ child.$$listenerCount = {};
255
+ child.$$watchersCount = 0;
256
+ child.$$ChildScope = null;
257
+ child.$$suspended = false;
258
+ }
259
+ child.$parent = parent || this;
260
+ child.$$prevSibling = child.$parent.$$childTail;
261
+ if (child.$parent.$$childHead) {
262
+ child.$parent.$$childTail.$$nextSibling = child;
263
+ child.$parent.$$childTail = child;
264
+ } else {
265
+ child.$parent.$$childHead = child;
266
+ child.$parent.$$childTail = child;
267
+ }
268
+
269
+ // When the new scope is not isolated or we inherit from `this`, and
270
+ // the parent scope is destroyed, the property `$$destroyed` is inherited
271
+ // prototypically. In all other cases, this property needs to be set
272
+ // when the parent scope is destroyed.
273
+ // The listener needs to be added after the parent is set
274
+ if (isolate || parent !== this) {
275
+ child.$on("$destroy", ($event) => {
276
+ $event.currentScope.$$destroyed = true;
277
+ });
278
+ }
279
+ return child;
280
+ }
281
+
282
+ /**
283
+ * @ngdoc method
284
+ * @name $rootScope.Scope#$watch
285
+ * @kind function
286
+ *
287
+ * @description
288
+ * Registers a `listener` callback to be executed whenever the `watchExpression` changes.
289
+ *
290
+ * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest
291
+ * $digest()} and should return the value that will be watched. (`watchExpression` should not change
292
+ * its value when executed multiple times with the same input because it may be executed multiple
293
+ * times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be
294
+ * [idempotent](http://en.wikipedia.org/wiki/Idempotence).)
295
+ * - The `listener` is called only when the value from the current `watchExpression` and the
296
+ * previous call to `watchExpression` are not equal (with the exception of the initial run,
297
+ * see below). Inequality is determined according to reference inequality,
298
+ * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators)
299
+ * via the `!==` Javascript operator, unless `objectEquality == true`
300
+ * (see next point)
301
+ * - When `objectEquality == true`, inequality of the `watchExpression` is determined
302
+ * according to the {@link angular.equals} function. To save the value of the object for
303
+ * later comparison, the {@link structuredClone} function is used. This therefore means that
304
+ * watching complex objects will have adverse memory and performance implications.
305
+ * - This should not be used to watch for changes in objects that are (or contain)
306
+ * [File](https://developer.mozilla.org/docs/Web/API/File) objects due to limitations with {@link structuredClone `structuredClone`}.
307
+ * - The watch `listener` may change the model, which may trigger other `listener`s to fire.
308
+ * This is achieved by rerunning the watchers until no changes are detected. The rerun
309
+ * iteration limit is 10 to prevent an infinite loop deadlock.
310
+ *
311
+ *
312
+ * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called,
313
+ * you can register a `watchExpression` function with no `listener`. (Be prepared for
314
+ * multiple calls to your `watchExpression` because it will execute multiple times in a
315
+ * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.)
316
+ *
317
+ * After a watcher is registered with the scope, the `listener` fn is called asynchronously
318
+ * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the
319
+ * watcher. In rare cases, this is undesirable because the listener is called when the result
320
+ * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you
321
+ * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the
322
+ * listener was called due to initialization.
323
+ *
324
+ *
325
+ *
326
+ * @example
327
+ * ```js
328
+ // let's assume that scope was dependency injected as the $rootScope
329
+ let scope = $rootScope;
330
+ scope.name = 'misko';
331
+ scope.counter = 0;
332
+
333
+ expect(scope.counter).toEqual(0);
334
+ scope.$watch('name', function(newValue, oldValue) {
335
+ scope.counter = scope.counter + 1;
336
+ });
337
+ expect(scope.counter).toEqual(0);
338
+
339
+ scope.$digest();
340
+ // the listener is always called during the first $digest loop after it was registered
341
+ expect(scope.counter).toEqual(1);
342
+
343
+ scope.$digest();
344
+ // but now it will not be called unless the value changes
345
+ expect(scope.counter).toEqual(1);
346
+
347
+ scope.name = 'adam';
348
+ scope.$digest();
349
+ expect(scope.counter).toEqual(2);
350
+
351
+ // Using a function as a watchExpression
352
+ let food;
353
+ scope.foodCounter = 0;
354
+ expect(scope.foodCounter).toEqual(0);
355
+ scope.$watch(
356
+ // This function returns the value being watched. It is called for each turn of the $digest loop
357
+ function() { return food; },
358
+ // This is the change listener, called when the value returned from the above function changes
359
+ function(newValue, oldValue) {
360
+ if ( newValue !== oldValue ) {
361
+ // Only increment the counter if the value changed
362
+ scope.foodCounter = scope.foodCounter + 1;
363
+ }
364
+ }
365
+ );
366
+ // No digest has been run so the counter will be zero
367
+ expect(scope.foodCounter).toEqual(0);
368
+
369
+ // Run the digest but since food has not changed count will still be zero
370
+ scope.$digest();
371
+ expect(scope.foodCounter).toEqual(0);
372
+
373
+ // Update food and run digest. Now the counter will increment
374
+ food = 'cheeseburger';
375
+ scope.$digest();
376
+ expect(scope.foodCounter).toEqual(1);
377
+
378
+ * ```
379
+ *
380
+ *
381
+ *
382
+ * @param {(function()|string)} watchExpression Expression that is evaluated on each
383
+ * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers
384
+ * a call to the `listener`.
385
+ *
386
+ * - `string`: Evaluated as {@link guide/expression expression}
387
+ * - `function(scope)`: called with current `scope` as a parameter.
388
+ * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value
389
+ * of `watchExpression` changes.
390
+ *
391
+ * - `newVal` contains the current value of the `watchExpression`
392
+ * - `oldVal` contains the previous value of the `watchExpression`
393
+ * - `scope` refers to the current scope
394
+ * @param {boolean=} [objectEquality=false] Compare for object equality using {@link angular.equals} instead of
395
+ * comparing for reference equality.
396
+ * @returns {function()} Returns a deregistration function for this listener.
397
+ */
398
+ $watch(watchExp, listener, objectEquality) {
399
+ const get = $parse(watchExp);
400
+ const fn = isFunction(listener) ? listener : () => {};
401
+
402
+ if (get.$$watchDelegate) {
403
+ return get.$$watchDelegate(this, fn, objectEquality, get, watchExp);
404
+ }
405
+ const scope = this;
406
+ let array = scope.$$watchers;
407
+ const watcher = {
408
+ fn,
409
+ last: initWatchVal,
410
+ get,
411
+ exp: watchExp,
412
+ eq: !!objectEquality,
413
+ };
414
+
415
+ lastDirtyWatch = null;
416
+
417
+ if (!array) {
418
+ array = scope.$$watchers = [];
419
+ array.$$digestWatchIndex = -1;
420
+ }
421
+ // we use unshift since we use a while loop in $digest for speed.
422
+ // the while loop reads in reverse order.
423
+ array.unshift(watcher);
424
+ array.$$digestWatchIndex++;
425
+ this.incrementWatchersCount(1);
426
+ const self = this;
427
+
428
+ return function deregisterWatch() {
429
+ const index = arrayRemove(array, watcher);
430
+ if (index >= 0) {
431
+ self.incrementWatchersCount(-1);
432
+ if (index < array.$$digestWatchIndex) {
433
+ array.$$digestWatchIndex--;
434
+ }
435
+ }
436
+ lastDirtyWatch = null;
437
+ };
438
+ }
439
+
440
+ /**
441
+ * @ngdoc method
442
+ * @name $rootScope.Scope#$watchGroup
443
+ * @kind function
444
+ *
445
+ * @description
446
+ * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`.
447
+ * If any one expression in the collection changes the `listener` is executed.
448
+ *
449
+ * - The items in the `watchExpressions` array are observed via the standard `$watch` operation. Their return
450
+ * values are examined for changes on every call to `$digest`.
451
+ * - The `listener` is called whenever any expression in the `watchExpressions` array changes.
452
+ *
453
+ * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually
454
+ * watched using {@link ng.$rootScope.Scope#$watch $watch()}
455
+ *
456
+ * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any
457
+ * expression in `watchExpressions` changes
458
+ * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching
459
+ * those of `watchExpression`
460
+ * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching
461
+ * those of `watchExpression`
462
+ * The `scope` refers to the current scope.
463
+ * @returns {function()} Returns a de-registration function for all listeners.
464
+ */
465
+ $watchGroup(watchExpressions, listener) {
466
+ const oldValues = new Array(watchExpressions.length);
467
+ const newValues = new Array(watchExpressions.length);
468
+ const deregisterFns = [];
469
+ const self = this;
470
+ let changeReactionScheduled = false;
471
+ let firstRun = true;
472
+
473
+ if (!watchExpressions.length) {
474
+ // No expressions means we call the listener ASAP
475
+ let shouldCall = true;
476
+ self.$evalAsync(() => {
477
+ if (shouldCall) listener(newValues, newValues, self);
478
+ });
479
+ return function deregisterWatchGroup() {
480
+ shouldCall = false;
481
+ };
482
+ }
483
+
484
+ if (watchExpressions.length === 1) {
485
+ // Special case size of one
486
+ return this.$watch(watchExpressions[0], (value, oldValue, scope) => {
487
+ newValues[0] = value;
488
+ oldValues[0] = oldValue;
489
+ listener(newValues, value === oldValue ? newValues : oldValues, scope);
490
+ });
491
+ }
492
+
493
+ forEach(watchExpressions, (expr, i) => {
494
+ const unwatchFn = self.$watch(expr, (value) => {
495
+ newValues[i] = value;
496
+ if (!changeReactionScheduled) {
497
+ changeReactionScheduled = true;
498
+ self.$evalAsync(watchGroupAction);
499
+ }
500
+ });
501
+ deregisterFns.push(unwatchFn);
502
+ });
503
+
504
+ function watchGroupAction() {
505
+ changeReactionScheduled = false;
506
+
507
+ try {
508
+ if (firstRun) {
509
+ firstRun = false;
510
+ listener(newValues, newValues, self);
511
+ } else {
512
+ listener(newValues, oldValues, self);
513
+ }
514
+ } finally {
515
+ for (let i = 0; i < watchExpressions.length; i++) {
516
+ oldValues[i] = newValues[i];
517
+ }
518
+ }
519
+ }
520
+
521
+ return function deregisterWatchGroup() {
522
+ while (deregisterFns.length) {
523
+ deregisterFns.shift()();
524
+ }
525
+ };
526
+ }
527
+
528
+ /**
529
+ * @ngdoc method
530
+ * @name $rootScope.Scope#$watchCollection
531
+ * @kind function
532
+ *
533
+ * @description
534
+ * Shallow watches the properties of an object and fires whenever any of the properties change
535
+ * (for arrays, this implies watching the array items; for object maps, this implies watching
536
+ * the properties). If a change is detected, the `listener` callback is fired.
537
+ *
538
+ * - The `obj` collection is observed via standard $watch operation and is examined on every
539
+ * call to $digest() to see if any items have been added, removed, or moved.
540
+ * - The `listener` is called whenever anything within the `obj` has changed. Examples include
541
+ * adding, removing, and moving items belonging to an object or array.
542
+ *
543
+ *
544
+ *
545
+ *
546
+ * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The
547
+ * expression value should evaluate to an object or an array which is observed on each
548
+ * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the
549
+ * collection will trigger a call to the `listener`.
550
+ *
551
+ * @param {function(newCollection, oldCollection, scope)} listener a callback function called
552
+ * when a change is detected.
553
+ * - The `newCollection` object is the newly modified data obtained from the `obj` expression
554
+ * - The `oldCollection` object is a copy of the former collection data.
555
+ * Due to performance considerations, the`oldCollection` value is computed only if the
556
+ * `listener` function declares two or more arguments.
557
+ * - The `scope` argument refers to the current scope.
558
+ *
559
+ * @returns {function()} Returns a de-registration function for this listener. When the
560
+ * de-registration function is executed, the internal watch operation is terminated.
561
+ */
562
+ $watchCollection(obj, listener) {
563
+ // Mark the interceptor as
564
+ // ... $$pure when literal since the instance will change when any input changes
565
+ $watchCollectionInterceptor.$$pure = $parse(obj).literal;
566
+ // ... $stateful when non-literal since we must read the state of the collection
567
+ $watchCollectionInterceptor.$stateful = !$watchCollectionInterceptor.$$pure;
568
+
569
+ const self = this;
570
+ // the current value, updated on each dirty-check run
571
+ let newValue;
572
+ // a shallow copy of the newValue from the last dirty-check run,
573
+ // updated to match newValue during dirty-check run
574
+ let oldValue;
575
+ // a shallow copy of the newValue from when the last change happened
576
+ let veryOldValue;
577
+ // only track veryOldValue if the listener is asking for it
578
+ const trackVeryOldValue = listener.length > 1;
579
+ let changeDetected = 0;
580
+ const changeDetector = $parse(obj, $watchCollectionInterceptor);
581
+ const internalArray = [];
582
+ let internalObject = {};
583
+ let initRun = true;
584
+ let oldLength = 0;
585
+
586
+ function $watchCollectionInterceptor(_value) {
587
+ newValue = _value;
588
+ let newLength;
589
+ let key;
590
+ let bothNaN;
591
+ let newItem;
592
+ let oldItem;
593
+
594
+ // If the new value is undefined, then return undefined as the watch may be a one-time watch
595
+ if (isUndefined(newValue)) return;
596
+
597
+ if (!isObject(newValue)) {
598
+ // if primitive
599
+ if (oldValue !== newValue) {
600
+ oldValue = newValue;
601
+ changeDetected++;
602
+ }
603
+ } else if (isArrayLike(newValue)) {
604
+ if (oldValue !== internalArray) {
605
+ // we are transitioning from something which was not an array into array.
606
+ oldValue = internalArray;
607
+ oldLength = oldValue.length = 0;
608
+ changeDetected++;
609
+ }
610
+
611
+ newLength = newValue.length;
612
+
613
+ if (oldLength !== newLength) {
614
+ // if lengths do not match we need to trigger change notification
615
+ changeDetected++;
616
+ oldValue.length = oldLength = newLength;
617
+ }
618
+ // copy the items to oldValue and look for changes.
619
+ for (let i = 0; i < newLength; i++) {
620
+ oldItem = oldValue[i];
621
+ newItem = newValue[i];
622
+
623
+ // eslint-disable-next-line no-self-compare
624
+ bothNaN = oldItem !== oldItem && newItem !== newItem;
625
+ if (!bothNaN && oldItem !== newItem) {
626
+ changeDetected++;
627
+ oldValue[i] = newItem;
628
+ }
629
+ }
630
+ } else {
631
+ if (oldValue !== internalObject) {
632
+ // we are transitioning from something which was not an object into object.
633
+ oldValue = internalObject = {};
634
+ oldLength = 0;
635
+ changeDetected++;
636
+ }
637
+ // copy the items to oldValue and look for changes.
638
+ newLength = 0;
639
+ for (key in newValue) {
640
+ if (Object.hasOwnProperty.call(newValue, key)) {
641
+ newLength++;
642
+ newItem = newValue[key];
643
+ oldItem = oldValue[key];
644
+
645
+ if (key in oldValue) {
646
+ // eslint-disable-next-line no-self-compare
647
+ bothNaN = oldItem !== oldItem && newItem !== newItem;
648
+ if (!bothNaN && oldItem !== newItem) {
649
+ changeDetected++;
650
+ oldValue[key] = newItem;
651
+ }
652
+ } else {
653
+ oldLength++;
654
+ oldValue[key] = newItem;
655
+ changeDetected++;
656
+ }
657
+ }
658
+ }
659
+ if (oldLength > newLength) {
660
+ // we used to have more keys, need to find them and destroy them.
661
+ changeDetected++;
662
+ for (key in oldValue) {
663
+ if (!Object.hasOwnProperty.call(newValue, key)) {
664
+ oldLength--;
665
+ delete oldValue[key];
666
+ }
667
+ }
668
+ }
669
+ }
670
+ return changeDetected;
671
+ }
672
+
673
+ function $watchCollectionAction() {
674
+ if (initRun) {
675
+ initRun = false;
676
+ listener(newValue, newValue, self);
677
+ } else {
678
+ listener(newValue, veryOldValue, self);
679
+ }
680
+
681
+ // make a copy for the next time a collection is changed
682
+ if (trackVeryOldValue) {
683
+ if (!isObject(newValue)) {
684
+ // primitive
685
+ veryOldValue = newValue;
686
+ } else if (isArrayLike(newValue)) {
687
+ veryOldValue = new Array(newValue.length);
688
+ for (let i = 0; i < newValue.length; i++) {
689
+ veryOldValue[i] = newValue[i];
690
+ }
691
+ } else {
692
+ // if object
693
+ veryOldValue = {};
694
+ for (const key in newValue) {
695
+ if (Object.hasOwnProperty.call(newValue, key)) {
696
+ veryOldValue[key] = newValue[key];
697
+ }
698
+ }
699
+ }
700
+ }
701
+ }
702
+
703
+ return this.$watch(changeDetector, $watchCollectionAction);
704
+ }
705
+
706
+ /**
707
+ * @ngdoc method
708
+ * @name $rootScope.Scope#$digest
709
+ * @kind function
710
+ *
711
+ * @description
712
+ * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and
713
+ * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change
714
+ * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers}
715
+ * until no more listeners are firing. This means that it is possible to get into an infinite
716
+ * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of
717
+ * iterations exceeds 10.
718
+ *
719
+ * Usually, you don't call `$digest()` directly in
720
+ * {@link ng.directive:ngController controllers} or in
721
+ * {@link ng.$compileProvider#directive directives}.
722
+ * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within
723
+ * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`.
724
+ *
725
+ * If you want to be notified whenever `$digest()` is called,
726
+ * you can register a `watchExpression` function with
727
+ * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`.
728
+ *
729
+ * In unit tests, you may need to call `$digest()` to simulate the scope life cycle.
730
+ *
731
+ * @example
732
+ * ```js
733
+ let scope = ...;
734
+ scope.name = 'misko';
735
+ scope.counter = 0;
736
+
737
+ expect(scope.counter).toEqual(0);
738
+ scope.$watch('name', function(newValue, oldValue) {
739
+ scope.counter = scope.counter + 1;
740
+ });
741
+ expect(scope.counter).toEqual(0);
742
+
743
+ scope.$digest();
744
+ // the listener is always called during the first $digest loop after it was registered
745
+ expect(scope.counter).toEqual(1);
746
+
747
+ scope.$digest();
748
+ // but now it will not be called unless the value changes
749
+ expect(scope.counter).toEqual(1);
750
+
751
+ scope.name = 'adam';
752
+ scope.$digest();
753
+ expect(scope.counter).toEqual(2);
754
+ * ```
755
+ *
756
+ */
757
+ $digest() {
758
+ let watch;
759
+ let value;
760
+ let last;
761
+ let fn;
762
+ let get;
763
+ let watchers;
764
+ let dirty;
765
+ let ttl = TTL;
766
+ let next;
767
+ /**
768
+ * @type {angular.IScope}
769
+ */
770
+ let current;
771
+ const target = $$asyncQueue.length ? this.$root : this;
772
+ const watchLog = [];
773
+ let logIdx;
774
+ let asyncTask;
775
+
776
+ this.beginPhase("$digest");
777
+ // Check for changes to browser url that happened in sync before the call to $digest
778
+ // TODO Implement browser
779
+ $browser.$$checkUrlChange();
780
+
781
+ if (this === this.$root && applyAsyncId !== null) {
782
+ // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then
783
+ // cancel the scheduled $apply and flush the queue of expressions to be evaluated.
784
+ $browser.defer.cancel(applyAsyncId);
785
+ flushApplyAsync();
786
+ applyAsyncId = null;
787
+ }
788
+
789
+ lastDirtyWatch = null;
790
+ do {
791
+ // "while dirty" loop
792
+ dirty = false;
793
+ current = target;
794
+
795
+ // It's safe for asyncQueuePosition to be a local variable here because this loop can't
796
+ // be reentered recursively. Calling $digest from a function passed to $evalAsync would
797
+ // lead to a '$digest already in progress' error.
798
+ for (
799
+ let asyncQueuePosition = 0;
800
+ asyncQueuePosition < $$asyncQueue.length;
801
+ asyncQueuePosition++
802
+ ) {
803
+ try {
804
+ asyncTask = $$asyncQueue[asyncQueuePosition];
805
+ fn = asyncTask.fn;
806
+ fn(asyncTask.scope, asyncTask.locals);
807
+ } catch (e) {
808
+ $exceptionHandler(e);
809
+ }
810
+ lastDirtyWatch = null;
811
+ }
812
+ $$asyncQueue.length = 0;
813
+
814
+ do {
815
+ // "traverse the scopes" loop
816
+ if ((watchers = !current.$$suspended && current.$$watchers)) {
817
+ // process our watches
818
+ watchers.$$digestWatchIndex = watchers.length;
819
+ while (watchers.$$digestWatchIndex--) {
820
+ try {
821
+ watch = watchers[watchers.$$digestWatchIndex];
822
+ // Most common watches are on primitives, in which case we can short
823
+ // circuit it with === operator, only when === fails do we use .equals
824
+ if (watch) {
825
+ get = watch.get;
826
+ if (
827
+ (value = get(current)) !== (last = watch.last) &&
828
+ !(watch.eq
829
+ ? equals(value, last)
830
+ : isNumberNaN(value) && isNumberNaN(last))
831
+ ) {
832
+ dirty = true;
833
+ lastDirtyWatch = watch;
834
+ watch.last = watch.eq ? structuredClone(value) : value;
835
+ fn = watch.fn;
836
+ fn(value, last === initWatchVal ? value : last, current);
837
+ if (ttl < 5) {
838
+ logIdx = 4 - ttl;
839
+ if (!watchLog[logIdx]) watchLog[logIdx] = [];
840
+ watchLog[logIdx].push({
841
+ msg: isFunction(watch.exp)
842
+ ? `fn: ${watch.exp.name || watch.exp.toString()}`
843
+ : watch.exp,
844
+ newVal: value,
845
+ oldVal: last,
846
+ });
847
+ }
848
+ } else if (watch === lastDirtyWatch) {
849
+ // If the most recently dirty watcher is now clean, short circuit since the remaining watchers
850
+ // have already been tested.
851
+ dirty = false;
852
+ break;
853
+ }
854
+ }
855
+ } catch (e) {
856
+ $exceptionHandler(e);
857
+ }
858
+ }
859
+ }
860
+
861
+ // Insanity Warning: scope depth-first traversal
862
+ // yes, this code is a bit crazy, but it works and we have tests to prove it!
863
+ // this piece should be kept in sync with the traversal in $broadcast
864
+ // (though it differs due to having the extra check for $$suspended and does not
865
+ // check $$listenerCount)
866
+ if (
867
+ !(next =
868
+ (!current.$$suspended &&
869
+ current.$$watchersCount &&
870
+ current.$$childHead) ||
871
+ (current !== target && current.$$nextSibling))
872
+ ) {
873
+ while (current !== target && !(next = current.$$nextSibling)) {
874
+ current = current.$parent;
875
+ }
876
+ }
877
+ } while ((current = next));
878
+
879
+ // `break traverseScopesLoop;` takes us to here
880
+
881
+ if ((dirty || $$asyncQueue.length) && !ttl--) {
882
+ this.clearPhase();
883
+ throw $rootScopeMinErr(
884
+ "infdig",
885
+ "{0} $digest() iterations reached. Aborting!\n" +
886
+ "Watchers fired in the last 5 iterations: {1}",
887
+ TTL,
888
+ watchLog,
889
+ );
890
+ }
891
+ } while (dirty || $$asyncQueue.length);
892
+
893
+ this.clearPhase();
894
+
895
+ // postDigestQueuePosition isn't local here because this loop can be reentered recursively.
896
+ while (postDigestQueuePosition < $$postDigestQueue.length) {
897
+ try {
898
+ $$postDigestQueue[postDigestQueuePosition++]();
899
+ } catch (e) {
900
+ $exceptionHandler(e);
901
+ }
902
+ }
903
+ $$postDigestQueue.length = postDigestQueuePosition = 0;
904
+
905
+ // Check for changes to browser url that happened during the $digest
906
+ // (for which no event is fired; e.g. via `history.pushState()`)
907
+ $browser.$$checkUrlChange();
908
+ }
909
+
910
+ /**
911
+ *
912
+ * @param {ScopePhase} phase
913
+ */
914
+ beginPhase(phase) {
915
+ if (this.$root.$$phase) {
916
+ throw $rootScopeMinErr(
917
+ "inprog",
918
+ "{0} already in progress",
919
+ this.$root.$$phase,
920
+ );
921
+ }
922
+
923
+ this.$root.$$phase = phase;
924
+ }
925
+
926
+ /**
927
+ * @ngdoc method
928
+ * @name $rootScope.Scope#$suspend
929
+ * @kind function
930
+ *
931
+ * @description
932
+ * Suspend watchers of this scope subtree so that they will not be invoked during digest.
933
+ *
934
+ * This can be used to optimize your application when you know that running those watchers
935
+ * is redundant.
936
+ *
937
+ * **Warning**
938
+ *
939
+ * Suspending scopes from the digest cycle can have unwanted and difficult to debug results.
940
+ * Only use this approach if you are confident that you know what you are doing and have
941
+ * ample tests to ensure that bindings get updated as you expect.
942
+ *
943
+ * Some of the things to consider are:
944
+ *
945
+ * * Any external event on a directive/component will not trigger a digest while the hosting
946
+ * scope is suspended - even if the event handler calls `$apply()` or `$rootScope.$digest()`.
947
+ * * Transcluded content exists on a scope that inherits from outside a directive but exists
948
+ * as a child of the directive's containing scope. If the containing scope is suspended the
949
+ * transcluded scope will also be suspended, even if the scope from which the transcluded
950
+ * scope inherits is not suspended.
951
+ * * Multiple directives trying to manage the suspended status of a scope can confuse each other:
952
+ * * A call to `$suspend()` on an already suspended scope is a no-op.
953
+ * * A call to `$resume()` on a non-suspended scope is a no-op.
954
+ * * If two directives suspend a scope, then one of them resumes the scope, the scope will no
955
+ * longer be suspended. This could result in the other directive believing a scope to be
956
+ * suspended when it is not.
957
+ * * If a parent scope is suspended then all its descendants will be also excluded from future
958
+ * digests whether or not they have been suspended themselves. Note that this also applies to
959
+ * isolate child scopes.
960
+ * * Calling `$digest()` directly on a descendant of a suspended scope will still run the watchers
961
+ * for that scope and its descendants. When digesting we only check whether the current scope is
962
+ * locally suspended, rather than checking whether it has a suspended ancestor.
963
+ * * Calling `$resume()` on a scope that has a suspended ancestor will not cause the scope to be
964
+ * included in future digests until all its ancestors have been resumed.
965
+ * * Resolved promises, e.g. from explicit `$q` deferreds and `$http` calls, trigger `$apply()`
966
+ * against the `$rootScope` and so will still trigger a global digest even if the promise was
967
+ * initiated by a component that lives on a suspended scope.
968
+ */
969
+ $suspend() {
970
+ this.$$suspended = true;
971
+ }
972
+
973
+ /**
974
+ * @ngdoc method
975
+ * @name $rootScope.Scope#$isSuspended
976
+ * @kind function
977
+ *
978
+ * @description
979
+ * Call this method to determine if this scope has been explicitly suspended. It will not
980
+ * tell you whether an ancestor has been suspended.
981
+ * To determine if this scope will be excluded from a digest triggered at the $rootScope,
982
+ * for example, you must check all its ancestors:
983
+ *
984
+ * ```
985
+ * function isExcludedFromDigest(scope) {
986
+ * while(scope) {
987
+ * if (scope.$isSuspended()) return true;
988
+ * scope = scope.$parent;
989
+ * }
990
+ * return false;
991
+ * ```
992
+ *
993
+ * Be aware that a scope may not be included in digests if it has a suspended ancestor,
994
+ * even if `$isSuspended()` returns false.
995
+ *
996
+ * @returns true if the current scope has been suspended.
997
+ */
998
+ $isSuspended() {
999
+ return this.$$suspended;
1000
+ }
1001
+
1002
+ /**
1003
+ * @ngdoc method
1004
+ * @name $rootScope.Scope#$resume
1005
+ * @kind function
1006
+ *
1007
+ * @description
1008
+ * Resume watchers of this scope subtree in case it was suspended.
1009
+ *
1010
+ * See {@link $rootScope.Scope#$suspend} for information about the dangers of using this approach.
1011
+ */
1012
+ $resume() {
1013
+ this.$$suspended = false;
1014
+ }
1015
+
1016
+ /**
1017
+ * @ngdoc event
1018
+ * @name $rootScope.Scope#$destroy
1019
+ * @eventType broadcast on scope being destroyed
1020
+ *
1021
+ * @description
1022
+ * Broadcasted when a scope and its children are being destroyed.
1023
+ *
1024
+ * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
1025
+ * clean up DOM bindings before an element is removed from the DOM.
1026
+ */
1027
+
1028
+ /**
1029
+ * @ngdoc method
1030
+ * @name $rootScope.Scope#$destroy
1031
+ * @kind function
1032
+ *
1033
+ * @description
1034
+ * Removes the current scope (and all of its children) from the parent scope. Removal implies
1035
+ * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer
1036
+ * propagate to the current scope and its children. Removal also implies that the current
1037
+ * scope is eligible for garbage collection.
1038
+ *
1039
+ * The `$destroy()` is usually used by directives such as
1040
+ * {@link ng.directive:ngRepeat ngRepeat} for managing the
1041
+ * unrolling of the loop.
1042
+ *
1043
+ * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope.
1044
+ * Application code can register a `$destroy` event handler that will give it a chance to
1045
+ * perform any necessary cleanup.
1046
+ *
1047
+ * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to
1048
+ * clean up DOM bindings before an element is removed from the DOM.
1049
+ */
1050
+ $destroy() {
1051
+ // We can't destroy a scope that has been already destroyed.
1052
+ if (this.$$destroyed) return;
1053
+ const parent = this.$parent;
1054
+
1055
+ this.$broadcast("$destroy");
1056
+ this.$$destroyed = true;
1057
+
1058
+ if (this === this.$root) {
1059
+ // Remove handlers attached to window when $rootScope is removed
1060
+ $browser.$$applicationDestroyed();
1061
+ }
1062
+
1063
+ this.incrementWatchersCount(-this.$$watchersCount);
1064
+ for (const eventName in this.$$listenerCount) {
1065
+ this.decrementListenerCount(this.$$listenerCount[eventName], eventName);
1066
+ }
1067
+
1068
+ // sever all the references to parent scopes (after this cleanup, the current scope should
1069
+ // not be retained by any of our references and should be eligible for garbage collection)
1070
+ if (parent && parent.$$childHead === this)
1071
+ parent.$$childHead = this.$$nextSibling;
1072
+ if (parent && parent.$$childTail === this)
1073
+ parent.$$childTail = this.$$prevSibling;
1074
+ if (this.$$prevSibling)
1075
+ this.$$prevSibling.$$nextSibling = this.$$nextSibling;
1076
+ if (this.$$nextSibling)
1077
+ this.$$nextSibling.$$prevSibling = this.$$prevSibling;
1078
+
1079
+ // Disable listeners, watchers and apply/digest methods
1080
+ this.$destroy =
1081
+ this.$digest =
1082
+ this.$apply =
1083
+ this.$evalAsync =
1084
+ this.$applyAsync =
1085
+ () => {};
1086
+ this.$on =
1087
+ this.$watch =
1088
+ this.$watchGroup =
1089
+ function () {
1090
+ return () => {};
1091
+ };
1092
+ this.$$listeners = {};
1093
+
1094
+ // Disconnect the next sibling to prevent `cleanUpScope` destroying those too
1095
+ this.$$nextSibling = null;
1096
+ this.$parent = null;
1097
+ this.$$nextSibling = null;
1098
+ this.$$prevSibling = null;
1099
+ this.$$childHead = null;
1100
+ this.$$childTail = null;
1101
+ this.$root = null;
1102
+ this.$$watchers = null;
1103
+ }
1104
+
1105
+ /**
1106
+ * @ngdoc method
1107
+ * @name $rootScope.Scope#$eval
1108
+ * @kind function
1109
+ *
1110
+ * @description
1111
+ * Executes the `expression` on the current scope and returns the result. Any exceptions in
1112
+ * the expression are propagated (uncaught). This is useful when evaluating AngularJS
1113
+ * expressions.
1114
+ *
1115
+ * @example
1116
+ * ```js
1117
+ let scope = ng.$rootScope.Scope();
1118
+ scope.a = 1;
1119
+ scope.b = 2;
1120
+
1121
+ expect(scope.$eval('a+b')).toEqual(3);
1122
+ expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
1123
+ * ```
1124
+ *
1125
+ * @param {(string|function())=} expr An AngularJS expression to be executed.
1126
+ *
1127
+ * - `string`: execute using the rules as defined in {@link guide/expression expression}.
1128
+ * - `function(scope)`: execute the function with the current `scope` parameter.
1129
+ *
1130
+ * @param {(object)=} locals Local variables object, useful for overriding values in scope.
1131
+ * @returns {*} The result of evaluating the expression.
1132
+ */
1133
+ $eval(expr, locals) {
1134
+ return $parse(expr)(this, locals);
1135
+ }
1136
+
1137
+ /**
1138
+ * @ngdoc method
1139
+ * @name $rootScope.Scope#$evalAsync
1140
+ * @kind function
1141
+ *
1142
+ * @description
1143
+ * Executes the expression on the current scope at a later point in time.
1144
+ *
1145
+ * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only
1146
+ * that:
1147
+ *
1148
+ * - it will execute after the function that scheduled the evaluation (preferably before DOM
1149
+ * rendering).
1150
+ * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after
1151
+ * `expression` execution.
1152
+ *
1153
+ * Any exceptions from the execution of the expression are forwarded to the
1154
+ * {@link ng.$exceptionHandler $exceptionHandler} service.
1155
+ *
1156
+ * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle
1157
+ * will be scheduled. However, it is encouraged to always call code that changes the model
1158
+ * from within an `$apply` call. That includes code evaluated via `$evalAsync`.
1159
+ *
1160
+ * @param {(string|function())=} expr An AngularJS expression to be executed.
1161
+ *
1162
+ * - `string`: execute using the rules as defined in {@link guide/expression expression}.
1163
+ * - `function(scope)`: execute the function with the current `scope` parameter.
1164
+ *
1165
+ * @param {(object)=} locals Local variables object, useful for overriding values in scope.
1166
+ */
1167
+ $evalAsync(expr, locals) {
1168
+ // if we are outside of an $digest loop and this is the first time we are scheduling async
1169
+ // task also schedule async auto-flush
1170
+ let id;
1171
+ if (!this.$root.$$phase && !$$asyncQueue.length) {
1172
+ id = $browser.defer(
1173
+ () => {
1174
+ if ($$asyncQueue.length) {
1175
+ this.$root.$digest();
1176
+ }
1177
+ },
1178
+ null,
1179
+ "$evalAsync",
1180
+ );
1181
+ }
1182
+
1183
+ $$asyncQueue.push({
1184
+ scope: this,
1185
+ fn: $parse(expr),
1186
+ locals,
1187
+ });
1188
+
1189
+ return id;
1190
+ }
1191
+
1192
+ $$postDigest(fn) {
1193
+ $$postDigestQueue.push(fn);
1194
+ }
1195
+
1196
+ /**
1197
+ * @ngdoc method
1198
+ * @name $rootScope.Scope#$apply
1199
+ * @kind function
1200
+ *
1201
+ * @description
1202
+ * `$apply()` is used to execute an expression in AngularJS from outside of the AngularJS
1203
+ * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries).
1204
+ * Because we are calling into the AngularJS framework we need to perform proper scope life
1205
+ * cycle of {@link ng.$exceptionHandler exception handling},
1206
+ * {@link ng.$rootScope.Scope#$digest executing watches}.
1207
+ *
1208
+ * **Life cycle: Pseudo-Code of `$apply()`**
1209
+ *
1210
+ * ```js
1211
+ function $apply(expr) {
1212
+ try {
1213
+ return $eval(expr);
1214
+ } catch (e) {
1215
+ $exceptionHandler(e);
1216
+ } finally {
1217
+ $root.$digest();
1218
+ }
1219
+ }
1220
+ * ```
1221
+ *
1222
+ *
1223
+ * Scope's `$apply()` method transitions through the following stages:
1224
+ *
1225
+ * 1. The {@link guide/expression expression} is executed using the
1226
+ * {@link ng.$rootScope.Scope#$eval $eval()} method.
1227
+ * 2. Any exceptions from the execution of the expression are forwarded to the
1228
+ * {@link ng.$exceptionHandler $exceptionHandler} service.
1229
+ * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the
1230
+ * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method.
1231
+ *
1232
+ *
1233
+ * @param {(string|function())=} expr An AngularJS expression to be executed.
1234
+ *
1235
+ * - `string`: execute using the rules as defined in {@link guide/expression expression}.
1236
+ * - `function(scope)`: execute the function with current `scope` parameter.
1237
+ *
1238
+ * @returns {*} The result of evaluating the expression.
1239
+ */
1240
+ $apply(expr) {
1241
+ try {
1242
+ this.beginPhase("$apply");
1243
+ try {
1244
+ return this.$eval(expr);
1245
+ } finally {
1246
+ this.clearPhase();
1247
+ }
1248
+ } catch (e) {
1249
+ $exceptionHandler(e);
1250
+ } finally {
1251
+ try {
1252
+ this.$root.$digest();
1253
+ } catch (e) {
1254
+ $exceptionHandler(e);
1255
+ // eslint-disable-next-line no-unsafe-finally
1256
+ throw e;
1257
+ }
1258
+ }
1259
+ }
1260
+
1261
+ clearPhase() {
1262
+ this.$root.$$phase = null;
1263
+ }
1264
+
1265
+ /**
1266
+ * @ngdoc method
1267
+ * @name $rootScope.Scope#$applyAsync
1268
+ * @kind function
1269
+ *
1270
+ * @description
1271
+ * Schedule the invocation of $apply to occur at a later time. The actual time difference
1272
+ * varies across browsers, but is typically around ~10 milliseconds.
1273
+ *
1274
+ * This can be used to queue up multiple expressions which need to be evaluated in the same
1275
+ * digest.
1276
+ *
1277
+ * @param {(string|function())=} expr An AngularJS expression to be executed.
1278
+ *
1279
+ * - `string`: execute using the rules as defined in {@link guide/expression expression}.
1280
+ * - `function(scope)`: execute the function with current `scope` parameter.
1281
+ */
1282
+ $applyAsync(expr) {
1283
+ const scope = this;
1284
+ if (expr) {
1285
+ $$applyAsyncQueue.push(() => scope.$eval(expr));
1286
+ }
1287
+ expr = $parse(expr);
1288
+
1289
+ if (applyAsyncId === null) {
1290
+ applyAsyncId = $browser.defer(flushApplyAsync, null, "$applyAsync");
1291
+ }
1292
+ }
1293
+
1294
+ /**
1295
+ * @description
1296
+ * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for
1297
+ * discussion of event life cycle.
1298
+ *
1299
+ * The event listener function format is: `function(event, args...)`. The `event` object
1300
+ * passed into the listener has the following attributes:
1301
+ *
1302
+ * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or
1303
+ * `$broadcast`-ed.
1304
+ * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the
1305
+ * event propagates through the scope hierarchy, this property is set to null.
1306
+ * - `name` - `{string}`: name of the event.
1307
+ * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel
1308
+ * further event propagation (available only for events that were `$emit`-ed).
1309
+ * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag
1310
+ * to true.
1311
+ * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called.
1312
+ *
1313
+ * @param {string} name Event name to listen on.
1314
+ * @param {function(event, ...args)} listener Function to call when the event is emitted.
1315
+ * @returns {function()} Returns a deregistration function for this listener.
1316
+ */
1317
+ $on(name, listener) {
1318
+ let namedListeners = this.$$listeners[name];
1319
+ if (!namedListeners) {
1320
+ this.$$listeners[name] = namedListeners = [];
1321
+ }
1322
+ namedListeners.push(listener);
1323
+
1324
+ let current = this;
1325
+ do {
1326
+ current.$$listenerCount[name] = (current.$$listenerCount[name] ?? 0) + 1;
1327
+ } while ((current = current.$parent));
1328
+
1329
+ return () => {
1330
+ const indexOfListener = namedListeners.indexOf(listener);
1331
+ if (indexOfListener !== -1) {
1332
+ // Use delete in the hope of the browser deallocating the memory for the array entry,
1333
+ // while not shifting the array indexes of other listeners.
1334
+ // See issue https://github.com/angular/angular.js/issues/16135
1335
+ delete namedListeners[indexOfListener];
1336
+ this.decrementListenerCount(1, name);
1337
+ }
1338
+ };
1339
+ }
1340
+
1341
+ /**
1342
+ * @param {number} count
1343
+ */
1344
+ incrementWatchersCount(count) {
1345
+ this.$$watchersCount += count;
1346
+ if (this.$parent) {
1347
+ this.$parent.incrementWatchersCount(count);
1348
+ }
1349
+ }
1350
+
1351
+ /**
1352
+ * @param {number} count
1353
+ * @param {string} name
1354
+ */
1355
+ decrementListenerCount(count, name) {
1356
+ let self = this;
1357
+ // @ts-ignore
1358
+ for (; self; self = self.$parent) {
1359
+ if (self.$$listenerCount[name] !== undefined) {
1360
+ self.$$listenerCount[name] -= count;
1361
+
1362
+ if (self.$$listenerCount[name] === 0) {
1363
+ delete self.$$listenerCount[name];
1364
+ }
1365
+ }
1366
+ }
1367
+ }
1368
+
1369
+ /**
1370
+ * @ngdoc method
1371
+ * @name $rootScope.Scope#$emit
1372
+ * @kind function
1373
+ *
1374
+ * @description
1375
+ * Dispatches an event `name` upwards through the scope hierarchy notifying the
1376
+ * registered {@link ng.$rootScope.Scope#$on} listeners.
1377
+ *
1378
+ * The event life cycle starts at the scope on which `$emit` was called. All
1379
+ * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
1380
+ * notified. Afterwards, the event traverses upwards toward the root scope and calls all
1381
+ * registered listeners along the way. The event will stop propagating if one of the listeners
1382
+ * cancels it.
1383
+ *
1384
+ * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
1385
+ * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
1386
+ *
1387
+ * @param {string} name Event name to emit.
1388
+ * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
1389
+ * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}).
1390
+ */
1391
+ $emit(name, ...args) {
1392
+ const empty = [];
1393
+ let namedListeners;
1394
+ let scope = this;
1395
+ let stopPropagation = false;
1396
+ const event = {
1397
+ name,
1398
+ targetScope: scope,
1399
+ stopPropagation() {
1400
+ stopPropagation = true;
1401
+ },
1402
+ preventDefault() {
1403
+ event.defaultPrevented = true;
1404
+ },
1405
+ defaultPrevented: false,
1406
+ };
1407
+ const listenerArgs = concat([event], [event].concat(args), 1);
1408
+ let i;
1409
+ let length;
1410
+
1411
+ do {
1412
+ namedListeners = scope.$$listeners[name] || empty;
1413
+ event.currentScope = scope;
1414
+ for (i = 0, length = namedListeners.length; i < length; i++) {
1415
+ // if listeners were deregistered, defragment the array
1416
+ if (!namedListeners[i]) {
1417
+ namedListeners.splice(i, 1);
1418
+ i--;
1419
+ length--;
1420
+ continue;
1421
+ }
1422
+ try {
1423
+ // allow all listeners attached to the current scope to run
1424
+ namedListeners[i].apply(null, listenerArgs);
1425
+ } catch (e) {
1426
+ $exceptionHandler(e);
1427
+ }
1428
+ }
1429
+ // if any listener on the current scope stops propagation, prevent bubbling
1430
+ if (stopPropagation) {
1431
+ break;
1432
+ }
1433
+ // traverse upwards
1434
+ scope = scope.$parent;
1435
+ } while (scope);
1436
+
1437
+ event.currentScope = null;
1438
+
1439
+ return event;
1440
+ }
1441
+
1442
+ /**
1443
+ * @ngdoc method
1444
+ * @name $rootScope.Scope#$broadcast
1445
+ * @kind function
1446
+ *
1447
+ * @description
1448
+ * Dispatches an event `name` downwards to all child scopes (and their children) notifying the
1449
+ * registered {@link ng.$rootScope.Scope#$on} listeners.
1450
+ *
1451
+ * The event life cycle starts at the scope on which `$broadcast` was called. All
1452
+ * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
1453
+ * notified. Afterwards, the event propagates to all direct and indirect scopes of the current
1454
+ * scope and calls all registered listeners along the way. The event cannot be canceled.
1455
+ *
1456
+ * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
1457
+ * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
1458
+ *
1459
+ * @param {string} name Event name to broadcast.
1460
+ * @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
1461
+ * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on}
1462
+ */
1463
+ $broadcast(name, ...args) {
1464
+ const target = this;
1465
+ let current = target;
1466
+
1467
+ let next = target;
1468
+ const event = {
1469
+ name,
1470
+ targetScope: target,
1471
+ preventDefault() {
1472
+ event.defaultPrevented = true;
1473
+ },
1474
+ defaultPrevented: false,
1475
+ };
1476
+
1477
+ if (!target.$$listenerCount[name]) return event;
1478
+
1479
+ const listenerArgs = concat([event], [event].concat(args), 1);
1480
+ let listeners;
1481
+ let i;
1482
+ let length;
1483
+
1484
+ // down while you can, then up and next sibling or up and next sibling until back at root
1485
+ while ((current = next)) {
1486
+ event.currentScope = current;
1487
+ listeners = current.$$listeners[name] || [];
1488
+ for (i = 0, length = listeners.length; i < length; i++) {
1489
+ // if listeners were deregistered, defragment the array
1490
+ if (!listeners[i]) {
1491
+ listeners.splice(i, 1);
1492
+ i--;
1493
+ length--;
1494
+ continue;
1495
+ }
1496
+
1497
+ try {
1498
+ listeners[i].apply(null, listenerArgs);
1499
+ } catch (e) {
1500
+ $exceptionHandler(e);
1501
+ }
1502
+ }
1503
+
1504
+ // Insanity Warning: scope depth-first traversal
1505
+ // yes, this code is a bit crazy, but it works and we have tests to prove it!
1506
+ // this piece should be kept in sync with the traversal in $digest
1507
+ // (though it differs due to having the extra check for $$listenerCount and
1508
+ // does not check $$suspended)
1509
+ if (
1510
+ !(next =
1511
+ (current.$$listenerCount[name] && current.$$childHead) ||
1512
+ (current !== target && current.$$nextSibling))
1513
+ ) {
1514
+ // TODO: current check fixes "contents are destroyed along with transcluding directive" test which sets current to null
1515
+ while (
1516
+ current &&
1517
+ current !== target &&
1518
+ !(next = current.$$nextSibling)
1519
+ ) {
1520
+ current = current.$parent;
1521
+ }
1522
+ }
1523
+ }
1524
+
1525
+ event.currentScope = null;
1526
+ return event;
1527
+ }
1528
+ }
1529
+
1530
+ /**
1531
+ * function used as an initial value for watchers.
1532
+ * because it's unique we can easily tell it apart from other values
1533
+ */
1534
+ function initWatchVal() {}
1535
+
1536
+ function flushApplyAsync() {
1537
+ while ($$applyAsyncQueue.length) {
1538
+ try {
1539
+ $$applyAsyncQueue.shift()();
1540
+ } catch (e) {
1541
+ $exceptionHandler(e);
1542
+ }
1543
+ }
1544
+ applyAsyncId = null;
1545
+ }
1546
+
1547
+ /**
1548
+ * Counts all the watchers of direct and indirect child scopes of the current scope.
1549
+ *
1550
+ * The watchers of the current scope are included in the count and so are all the watchers of
1551
+ * isolate child scopes.
1552
+ * @param {Scope} scope
1553
+ * @returns {number} Total number of watchers.
1554
+ */
1555
+ export function countWatchers(scope) {
1556
+ var count = scope.$$watchers ? scope.$$watchers.length : 0; // include the current scope
1557
+ var pendingChildHeads = [scope.$$childHead];
1558
+ var currentScope;
1559
+
1560
+ while (pendingChildHeads.length) {
1561
+ currentScope = pendingChildHeads.shift();
1562
+
1563
+ while (currentScope) {
1564
+ count += currentScope.$$watchers ? currentScope.$$watchers.length : 0;
1565
+ pendingChildHeads.push(currentScope.$$childHead);
1566
+ currentScope = currentScope.$$nextSibling;
1567
+ }
1568
+ }
1569
+
1570
+ return count;
1571
+ }
1572
+
1573
+ /**
1574
+ * Counts all the direct and indirect child scopes of the current scope.
1575
+ *
1576
+ * The current scope is excluded from the count. The count includes all isolate child scopes.
1577
+ * @param {Scope} scope
1578
+ * @returns {number} Total number of child scopes.
1579
+ */
1580
+ export function countChildScopes(scope) {
1581
+ var count = 0; // exclude the current scope
1582
+ var pendingChildHeads = [scope.$$childHead];
1583
+ var currentScope;
1584
+
1585
+ while (pendingChildHeads.length) {
1586
+ currentScope = pendingChildHeads.shift();
1587
+
1588
+ while (currentScope) {
1589
+ count += 1;
1590
+ pendingChildHeads.push(currentScope.$$childHead);
1591
+ currentScope = currentScope.$$nextSibling;
1592
+ }
1593
+ }
1594
+
1595
+ return count;
1596
+ }