@angular-wave/angular.ts 0.3.0 → 0.4.0

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 (115) hide show
  1. package/.github/workflows/ci.yml +57 -0
  2. package/README.md +1 -1
  3. package/dist/angular-ts.esm.js +2 -2
  4. package/dist/angular-ts.umd.js +2 -2
  5. package/package.json +1 -1
  6. package/src/animations/animate-cache.js +1 -1
  7. package/src/animations/animate-css-driver.js +2 -2
  8. package/src/animations/animate-css.js +1 -1
  9. package/src/animations/animate-js-driver.js +2 -2
  10. package/src/animations/animate-js.js +2 -2
  11. package/src/animations/animate-queue.js +2 -2
  12. package/src/animations/animation.js +1 -1
  13. package/src/core/cache/cache-factory.js +18 -2
  14. package/src/core/compile/attributes.js +1 -1
  15. package/src/core/compile/compile.js +10 -12
  16. package/src/core/compile/compile.spec.js +0 -1
  17. package/src/core/compile/compile.test.js +1 -1
  18. package/src/core/controller/controller.js +1 -1
  19. package/src/core/exception-handler.js +2 -6
  20. package/src/core/filter/filter.js +2 -2
  21. package/src/core/interpolate/interpolate.js +3 -3
  22. package/src/core/interval/interval-factory.js +7 -23
  23. package/src/core/interval/interval.js +1 -1
  24. package/src/core/interval/interval.spec.js +0 -25
  25. package/src/core/location/location.js +1 -1
  26. package/src/core/location/location.spec.js +3 -3
  27. package/src/core/parser/parse.js +2 -2
  28. package/src/core/q/q.js +0 -27
  29. package/src/core/q/q.spec.js +0 -44
  30. package/src/core/sce/sce.js +2 -2
  31. package/src/core/scope/scope.js +35 -41
  32. package/src/core/scope/scope.spec.js +71 -13
  33. package/src/core/task-tracker-factory.js +1 -1
  34. package/src/core/timeout/timeout.js +4 -8
  35. package/src/directive/attrs/attrs.js +1 -1
  36. package/src/directive/if/if.js +5 -5
  37. package/src/directive/input/input.spec.js +0 -1
  38. package/src/directive/observe/observe.html +18 -0
  39. package/src/directive/observe/observe.js +37 -0
  40. package/src/directive/observe/observe.spec.js +92 -0
  41. package/src/directive/observe/observe.test.js +9 -0
  42. package/src/directive/observe/test.html +197 -0
  43. package/src/directive/script/script.js +11 -9
  44. package/src/directive/validators/validators.js +70 -42
  45. package/src/public.js +63 -66
  46. package/src/router/params/param.js +1 -2
  47. package/src/router/path/path-node.js +5 -5
  48. package/src/router/path/path-utils.js +19 -21
  49. package/src/router/state/state-builder.js +17 -11
  50. package/src/router/state/state-queue-manager.js +16 -7
  51. package/src/router/state/state-registry.js +9 -5
  52. package/src/router/state/state-service.js +7 -11
  53. package/src/router/template-factory.js +2 -2
  54. package/src/router/transition/transition-service.js +1 -1
  55. package/src/router/transition/transition.js +13 -11
  56. package/src/router/url/url-matcher.js +2 -2
  57. package/src/router/url/url-service.js +1 -1
  58. package/src/router/view/view.js +3 -3
  59. package/src/router/view-scroll.js +13 -8
  60. package/src/services/http/http.js +2 -82
  61. package/src/services/http/http.spec.js +0 -118
  62. package/src/services/http/template-request.spec.js +10 -12
  63. package/src/services/http-backend/http-backend.js +1 -1
  64. package/src/services/log.js +1 -7
  65. package/src/services/template-request.js +3 -3
  66. package/src/shared/common.js +3 -9
  67. package/src/shared/hof.js +6 -8
  68. package/src/types.js +0 -14
  69. package/types/animations/animate-cache.d.ts +2 -2
  70. package/types/animations/animate-css-driver.d.ts +3 -3
  71. package/types/animations/animate-css.d.ts +2 -2
  72. package/types/animations/animate-js-driver.d.ts +3 -3
  73. package/types/animations/animate-js.d.ts +3 -3
  74. package/types/animations/animate-queue.d.ts +3 -3
  75. package/types/animations/animation.d.ts +2 -2
  76. package/types/core/cache/cache-factory.d.ts +20 -2
  77. package/types/core/compile/attributes.d.ts +3 -3
  78. package/types/core/compile/compile.d.ts +12 -12
  79. package/types/core/controller/controller.d.ts +1 -1
  80. package/types/core/exception-handler.d.ts +3 -4
  81. package/types/core/filter/filter.d.ts +3 -3
  82. package/types/core/interpolate/interpolate.d.ts +6 -6
  83. package/types/core/interval/interval-factory.d.ts +3 -3
  84. package/types/core/interval/interval.d.ts +2 -2
  85. package/types/core/location/location.d.ts +2 -2
  86. package/types/core/parser/parse.d.ts +4 -4
  87. package/types/core/q/q.d.ts +0 -5
  88. package/types/core/sce/sce.d.ts +4 -4
  89. package/types/core/scope/scope.d.ts +12 -3
  90. package/types/core/task-tracker-factory.d.ts +1 -1
  91. package/types/core/timeout/timeout.d.ts +3 -4
  92. package/types/directive/observe/observe.d.ts +4 -0
  93. package/types/directive/script/script.d.ts +8 -5
  94. package/types/directive/validators/validators.d.ts +3 -15
  95. package/types/router/path/path-node.d.ts +0 -7
  96. package/types/router/path/path-utils.d.ts +7 -2
  97. package/types/router/state/state-queue-manager.d.ts +13 -3
  98. package/types/router/state/state-registry.d.ts +5 -2
  99. package/types/router/state/state-service.d.ts +2 -2
  100. package/types/router/template-factory.d.ts +3 -3
  101. package/types/router/transition/transition-service.d.ts +1 -1
  102. package/types/router/transition/transition.d.ts +3 -3
  103. package/types/router/url/url-service.d.ts +3 -3
  104. package/types/router/view-scroll.d.ts +3 -3
  105. package/types/services/http/http.d.ts +4 -48
  106. package/types/services/http-backend/http-backend.d.ts +2 -2
  107. package/types/services/log.d.ts +2 -8
  108. package/types/services/template-request.d.ts +1 -1
  109. package/types/shared/common.d.ts +0 -5
  110. package/types/shared/hof.d.ts +0 -1
  111. package/types/types.d.ts +1 -1
  112. package/.github/workflows/lint.yml +0 -19
  113. package/.github/workflows/playwright.yml +0 -27
  114. package/.github/workflows/types.yml +0 -19
  115. package/src/directive/csp.md +0 -63
@@ -76,11 +76,6 @@ let $browser;
76
76
  /**@type {import('../exception-handler').ErrorHandler} */
77
77
  let $exceptionHandler;
78
78
 
79
- /**
80
- * @type {Scope}
81
- */
82
- let rootScope;
83
-
84
79
  /**
85
80
  * Provider responsible for instantiating the initial scope, aka - root scope.
86
81
  * Every application has a single root {@link ng.$rootScope.Scope scope}.
@@ -92,9 +87,9 @@ let rootScope;
92
87
  * The provider also injects runtime services to make them available to all scopes.
93
88
  *
94
89
  */
95
- export class $RootScopeProvider {
90
+ export class RootScopeProvider {
96
91
  constructor() {
97
- rootScope = new Scope();
92
+ this.rootScope = new Scope(true);
98
93
  }
99
94
 
100
95
  $get = [
@@ -111,7 +106,7 @@ export class $RootScopeProvider {
111
106
  $exceptionHandler = exceptionHandler;
112
107
  $parse = parse;
113
108
  $browser = browser;
114
- return rootScope;
109
+ return this.rootScope;
115
110
  },
116
111
  ];
117
112
  }
@@ -142,7 +137,15 @@ export class $RootScopeProvider {
142
137
  */
143
138
 
144
139
  export class Scope {
145
- constructor() {
140
+ /**
141
+ * @param {boolean} [root=false] - Indicates if this scope is the root scope.
142
+ */
143
+ constructor(root = false) {
144
+ /**
145
+ * @type {boolean}
146
+ */
147
+ this.isRoot = root;
148
+
146
149
  /**
147
150
  * @type {number} Unique scope ID (monotonically increasing) useful for debugging.
148
151
  */
@@ -194,13 +197,12 @@ export class Scope {
194
197
  /** @type {boolean} */
195
198
  this.$$destroyed = false;
196
199
 
197
- // TODO use maps
198
200
  /** @type {boolean} */
199
201
  this.$$suspended = false;
200
202
 
201
203
  // TODO use maps
202
- /** @type {object} */
203
- this.$$listeners = {};
204
+ /** @type {Map<String, Function[]>} */
205
+ this.$$listeners = new Map();
204
206
 
205
207
  /** @type {object} */
206
208
  this.$$listenerCount = {};
@@ -240,19 +242,18 @@ export class Scope {
240
242
  *
241
243
  */
242
244
  $new(isolate, parent) {
243
- /** @type {Scope} */
244
- let child;
245
+ let child = isolate ? new Scope() : Object.create(this);
246
+
245
247
  if (isolate) {
246
- child = new Scope();
247
- child.$root = rootScope;
248
+ child.$root = this.isRoot ? this : this.$root;
248
249
  } else {
249
- child = Object.create(this);
250
+ // Initialize properties for a non-isolated child scope
250
251
  child.$id = nextUid();
251
252
  child.$$watchers = [];
252
253
  child.$$nextSibling = null;
253
254
  child.$$childHead = null;
254
255
  child.$$childTail = null;
255
- child.$$listeners = {};
256
+ child.$$listeners = new Map();
256
257
  child.$$listenerCount = {};
257
258
  child.$$watchersCount = 0;
258
259
  child.$$ChildScope = null;
@@ -261,6 +262,7 @@ export class Scope {
261
262
 
262
263
  child.$parent = parent || this;
263
264
  child.$$prevSibling = child.$parent.$$childTail;
265
+
264
266
  if (child.$parent.$$childHead) {
265
267
  child.$parent.$$childTail.$$nextSibling = child;
266
268
  child.$parent.$$childTail = child;
@@ -269,20 +271,13 @@ export class Scope {
269
271
  child.$parent.$$childTail = child;
270
272
  }
271
273
 
272
- // When the new scope is not isolated or we inherit from `this`, and
273
- // the parent scope is destroyed, the property `$$destroyed` is inherited
274
- // prototypically. In all other cases, this property needs to be set
275
- // when the parent scope is destroyed.
276
- // The listener needs to be added after the parent is set
274
+ // Add a destroy listener if isolated or the parent differs from `this`
277
275
  if (isolate || parent !== this) {
278
- child.$on(
279
- "$destroy",
280
- /** @param {any} $event */ //angular.IAngularEvent
281
- ($event) => {
282
- $event.currentScope.$$destroyed = true;
283
- },
284
- );
276
+ child.$on("$destroy", ($event) => {
277
+ $event.currentScope.$$destroyed = true;
278
+ });
285
279
  }
280
+
286
281
  return child;
287
282
  }
288
283
 
@@ -732,7 +727,6 @@ export class Scope {
732
727
  *
733
728
  */
734
729
  $digest() {
735
- let watch;
736
730
  let value;
737
731
  let last;
738
732
  let fn;
@@ -741,10 +735,9 @@ export class Scope {
741
735
  let dirty;
742
736
  let ttl = TTL;
743
737
  let next;
744
- /**
745
- * @type {Scope}
746
- */
738
+ /** @type {Scope} */
747
739
  let current;
740
+ /** @type {Scope} */
748
741
  const target = $$asyncQueue.length ? this.$root : this;
749
742
  const watchLog = [];
750
743
  let logIdx;
@@ -755,7 +748,7 @@ export class Scope {
755
748
  // TODO Implement browser
756
749
  $browser.$$checkUrlChange();
757
750
 
758
- if (this === this.$root && applyAsyncId !== null) {
751
+ if (this.isRoot && applyAsyncId !== null) {
759
752
  // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then
760
753
  // cancel the scheduled $apply and flush the queue of expressions to be evaluated.
761
754
  $browser.cancel(applyAsyncId);
@@ -795,7 +788,7 @@ export class Scope {
795
788
  current.$$digestWatchIndex = watchers.length;
796
789
  while (current.$$digestWatchIndex--) {
797
790
  try {
798
- watch = watchers[current.$$digestWatchIndex];
791
+ const watch = watchers[current.$$digestWatchIndex];
799
792
  // Most common watches are on primitives, in which case we can short
800
793
  // circuit it with === operator, only when === fails do we use .equals
801
794
  if (watch) {
@@ -1038,7 +1031,7 @@ export class Scope {
1038
1031
  function () {
1039
1032
  return () => {};
1040
1033
  };
1041
- this.$$listeners = {};
1034
+ this.$$listeners.clear();
1042
1035
 
1043
1036
  // Disconnect the next sibling to prevent `cleanUpScope` destroying those too
1044
1037
  this.$$nextSibling = null;
@@ -1251,9 +1244,10 @@ export class Scope {
1251
1244
  * @returns {function()} Returns a deregistration function for this listener.
1252
1245
  */
1253
1246
  $on(name, listener) {
1254
- let namedListeners = this.$$listeners[name];
1247
+ let namedListeners = this.$$listeners.get(name);
1255
1248
  if (!namedListeners) {
1256
- this.$$listeners[name] = namedListeners = [];
1249
+ namedListeners = [];
1250
+ this.$$listeners.set(name, namedListeners);
1257
1251
  }
1258
1252
  namedListeners.push(listener);
1259
1253
 
@@ -1342,7 +1336,7 @@ export class Scope {
1342
1336
  let length;
1343
1337
 
1344
1338
  do {
1345
- namedListeners = scope.$$listeners[name] || empty;
1339
+ namedListeners = scope.$$listeners.get(name) || empty;
1346
1340
  event.currentScope = scope;
1347
1341
  for (i = 0, length = namedListeners.length; i < length; i++) {
1348
1342
  // if listeners were deregistered, defragment the array
@@ -1414,7 +1408,7 @@ export class Scope {
1414
1408
  // down while you can, then up and next sibling or up and next sibling until back at root
1415
1409
  while ((current = next)) {
1416
1410
  event.currentScope = current;
1417
- listeners = current.$$listeners[name] || [];
1411
+ listeners = current.$$listeners.get(name) || [];
1418
1412
  for (i = 0, length = listeners.length; i < length; i++) {
1419
1413
  // if listeners were deregistered, defragment the array
1420
1414
  if (!listeners[i]) {
@@ -1,4 +1,4 @@
1
- import { $$asyncQueue } from "./scope";
1
+ import { $$asyncQueue, Scope } from "./scope";
2
2
  import { extend, sliceArgs } from "../../shared/utils";
3
3
  import { Angular } from "../../loader";
4
4
  import { createInjector } from "../di/injector";
@@ -8,6 +8,7 @@ describe("Scope", function () {
8
8
  let $parse;
9
9
  let $browser;
10
10
  let logs;
11
+ let scope;
11
12
 
12
13
  beforeEach(() => {
13
14
  logs = [];
@@ -27,6 +28,21 @@ describe("Scope", function () {
27
28
  $browser = injector.get("$browser");
28
29
 
29
30
  $rootScope = injector.get("$rootScope");
31
+ scope = $rootScope;
32
+ });
33
+
34
+ describe("class", () => {
35
+ it("can be constructed as a class", () => {
36
+ const scope = new Scope();
37
+ expect(scope).toBeDefined();
38
+ expect(scope.isRoot).toBeFalse();
39
+ });
40
+
41
+ it("can be constructed as a root scope", () => {
42
+ const scope = new Scope(true);
43
+ expect(scope).toBeDefined();
44
+ expect(scope.isRoot).toBeTrue();
45
+ });
30
46
  });
31
47
 
32
48
  describe("inheritance", () => {
@@ -49,7 +65,7 @@ describe("Scope", function () {
49
65
  const child = $rootScope.$new();
50
66
  child.aValue = [1, 2, 3];
51
67
 
52
- expect(parent.aValue).toBeUndefined();
68
+ expect($rootScope.aValue).toBeUndefined();
53
69
  });
54
70
 
55
71
  it("inherits the parents properties whenever they are defined", () => {
@@ -91,6 +107,7 @@ describe("Scope", function () {
91
107
 
92
108
  expect(child.aValue).toEqual([1, 2, 3, 4]);
93
109
  expect($rootScope.aValue).toEqual([1, 2, 3, 4]);
110
+ expect(child.aValue).toEqual($rootScope.aValue);
94
111
  });
95
112
  });
96
113
 
@@ -122,6 +139,7 @@ describe("Scope", function () {
122
139
  $rootScope.a = 123;
123
140
  expect(isolated.a).toBeUndefined();
124
141
  expect(trans.a).toEqual(123);
142
+ expect(trans.$root).toBe($rootScope);
125
143
  expect(trans.$parent).toBe(isolated);
126
144
  });
127
145
  });
@@ -171,13 +189,53 @@ describe("Scope", function () {
171
189
  expect($rootScope.$eval("this")).toEqual($rootScope);
172
190
  });
173
191
 
174
- it("should be able to access a constiable named 'this'", () => {
192
+ it("should be able to access a constant variable named 'this'", () => {
175
193
  $rootScope.this = 42;
176
194
  expect($rootScope.$eval("this['this']")).toBe(42);
177
195
  });
178
196
  });
179
197
 
180
198
  describe("$watch/$digest", () => {
199
+ it("calls the listener function of a watch on first $digest", function () {
200
+ var watchFn = function () {
201
+ return "wat";
202
+ };
203
+ var listenerFn = jasmine.createSpy();
204
+ $rootScope.$watch(watchFn, listenerFn);
205
+ $rootScope.$digest();
206
+ expect(listenerFn).toHaveBeenCalled();
207
+ });
208
+
209
+ it("calls the watch function with the scope as the argument", function () {
210
+ var watchFn = jasmine.createSpy();
211
+ var listenerFn = function () {};
212
+ $rootScope.$watch(watchFn, listenerFn);
213
+ $rootScope.$digest();
214
+ expect(watchFn).toHaveBeenCalledWith($rootScope);
215
+ });
216
+
217
+ it("calls the listener function when the watched value changes", function () {
218
+ scope.someValue = "a";
219
+ scope.counter = 0;
220
+ scope.$watch(
221
+ function (scope) {
222
+ return scope.someValue;
223
+ },
224
+ function (newValue, oldValue, scope) {
225
+ scope.counter++;
226
+ },
227
+ );
228
+ expect(scope.counter).toBe(0);
229
+ scope.$digest();
230
+ expect(scope.counter).toBe(1);
231
+ scope.$digest();
232
+ expect(scope.counter).toBe(1);
233
+ scope.someValue = "b";
234
+ expect(scope.counter).toBe(1);
235
+ scope.$digest();
236
+ expect(scope.counter).toBe(2);
237
+ });
238
+
181
239
  it("should watch and fire on simple property change", () => {
182
240
  const spy = jasmine.createSpy();
183
241
  $rootScope.$watch("name", spy);
@@ -2870,13 +2928,13 @@ describe("Scope", function () {
2870
2928
  const remove1 = $rootScope.$on("abc", () => {});
2871
2929
  $rootScope.$on("abc", () => {});
2872
2930
 
2873
- expect($rootScope.$$listeners.abc.length).toBe(2);
2874
- expect(0 in $rootScope.$$listeners.abc).toBe(true);
2931
+ expect($rootScope.$$listeners.get("abc").length).toBe(2);
2932
+ expect(0 in $rootScope.$$listeners.get("abc")).toBe(true);
2875
2933
 
2876
2934
  remove1();
2877
2935
 
2878
- expect($rootScope.$$listeners.abc.length).toBe(2);
2879
- expect(0 in $rootScope.$$listeners.abc).toBe(false);
2936
+ expect($rootScope.$$listeners.get("abc").length).toBe(2);
2937
+ expect(0 in $rootScope.$$listeners.get("abc")).toBe(false);
2880
2938
  });
2881
2939
 
2882
2940
  it("should call next listener after removing the current listener via its own handler", () => {
@@ -3097,14 +3155,14 @@ describe("Scope", function () {
3097
3155
 
3098
3156
  spy1.and.callFake(remove1);
3099
3157
 
3100
- expect(child.$$listeners.evt.length).toBe(3);
3158
+ expect(child.$$listeners.get("evt").length).toBe(3);
3101
3159
 
3102
3160
  // should call all listeners and remove 1st
3103
3161
  child.$emit("evt");
3104
3162
  expect(spy1).toHaveBeenCalled();
3105
3163
  expect(spy2).toHaveBeenCalled();
3106
3164
  expect(spy3).toHaveBeenCalled();
3107
- expect(child.$$listeners.evt.length).toBe(3); // cleanup will happen on next $emit
3165
+ expect(child.$$listeners.get("evt").length).toBe(3); // cleanup will happen on next $emit
3108
3166
 
3109
3167
  spy1.calls.reset();
3110
3168
  spy2.calls.reset();
@@ -3116,7 +3174,7 @@ describe("Scope", function () {
3116
3174
  expect(spy1).not.toHaveBeenCalled();
3117
3175
  expect(spy2).toHaveBeenCalled();
3118
3176
  expect(spy3).not.toHaveBeenCalled();
3119
- expect(child.$$listeners.evt.length).toBe(1);
3177
+ expect(child.$$listeners.get("evt").length).toBe(1);
3120
3178
  });
3121
3179
 
3122
3180
  it("should allow removing event listener inside a listener on $broadcast", () => {
@@ -3130,14 +3188,14 @@ describe("Scope", function () {
3130
3188
 
3131
3189
  spy1.and.callFake(remove1);
3132
3190
 
3133
- expect(child.$$listeners.evt.length).toBe(3);
3191
+ expect(child.$$listeners.get("evt").length).toBe(3);
3134
3192
 
3135
3193
  // should call all listeners and remove 1st
3136
3194
  child.$broadcast("evt");
3137
3195
  expect(spy1).toHaveBeenCalled();
3138
3196
  expect(spy2).toHaveBeenCalled();
3139
3197
  expect(spy3).toHaveBeenCalled();
3140
- expect(child.$$listeners.evt.length).toBe(3); // cleanup will happen on next $broadcast
3198
+ expect(child.$$listeners.get("evt").length).toBe(3); // cleanup will happen on next $broadcast
3141
3199
 
3142
3200
  spy1.calls.reset();
3143
3201
  spy2.calls.reset();
@@ -3149,7 +3207,7 @@ describe("Scope", function () {
3149
3207
  expect(spy1).not.toHaveBeenCalled();
3150
3208
  expect(spy2).toHaveBeenCalled();
3151
3209
  expect(spy3).not.toHaveBeenCalled();
3152
- expect(child.$$listeners.evt.length).toBe(1);
3210
+ expect(child.$$listeners.get("evt").length).toBe(1);
3153
3211
  });
3154
3212
 
3155
3213
  describe("event object", () => {
@@ -1,4 +1,4 @@
1
- export class $$TaskTrackerFactoryProvider {
1
+ export class TaskTrackerFactoryProvider {
2
2
  $get = [
3
3
  "$log",
4
4
  /**
@@ -3,23 +3,21 @@ import { isDefined, minErr, sliceArgs } from "../../shared/utils";
3
3
 
4
4
  const $timeoutMinErr = minErr("$timeout");
5
5
 
6
- export function $TimeoutProvider() {
7
- this.$get = [
6
+ export class TimeoutProvider {
7
+ $get = [
8
8
  "$rootScope",
9
9
  "$browser",
10
10
  "$q",
11
- "$$q",
12
11
  "$exceptionHandler",
13
12
  /**
14
13
  *
15
14
  * @param {import('../scope/scope').Scope} $rootScope
16
15
  * @param {import('../../services/browser').Browser} $browser
17
16
  * @param {*} $q
18
- * @param {*} $$q
19
17
  * @param {import('../exception-handler').ErrorHandler} $exceptionHandler
20
18
  * @returns
21
19
  */
22
- function ($rootScope, $browser, $q, $$q, $exceptionHandler) {
20
+ ($rootScope, $browser, $q, $exceptionHandler) => {
23
21
  const deferreds = {};
24
22
 
25
23
  /**
@@ -38,8 +36,6 @@ export function $TimeoutProvider() {
38
36
  *
39
37
  * @param {function()=} fn A function, whose execution should be delayed.
40
38
  * @param {number=} [delay=0] Delay in milliseconds.
41
- * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
42
- * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
43
39
  * @returns {import("../q/q").QPromise<any>} Promise that will be resolved when the timeout is reached. The promise
44
40
  * will be resolved with the return value of the `fn` function.
45
41
  *
@@ -47,7 +43,7 @@ export function $TimeoutProvider() {
47
43
  function timeout(fn, delay, invokeApply = true) {
48
44
  const args = sliceArgs(arguments, 3);
49
45
  const skipApply = isDefined(invokeApply) && !invokeApply;
50
- const deferred = (skipApply ? $$q : $q).defer();
46
+ const deferred = $q.defer();
51
47
  const { promise } = deferred;
52
48
  let timeoutId;
53
49
 
@@ -11,7 +11,7 @@ Object.entries(BOOLEAN_ATTR).forEach(([attrName, propName]) => {
11
11
  // binding to multiple is not supported
12
12
  if (propName === "multiple") return;
13
13
 
14
- function defaultLinkFn(scope, element, attr) {
14
+ function defaultLinkFn(scope, _element, attr) {
15
15
  scope.$watch(attr[normalized], (value) => {
16
16
  attr.$set(attrName, !!value);
17
17
  });
@@ -18,10 +18,10 @@ export function ngIfDirective($animate) {
18
18
  restrict: "A",
19
19
  /**
20
20
  *
21
- * @param {*} $scope
22
- * @param {*} $element
23
- * @param {*} $attr
24
- * @param {*} _ctrl
21
+ * @param {import("../../core/scope/scope").Scope} $scope
22
+ * @param {import("../../shared/jqlite/jqlite").JQLite} $element
23
+ * @param {import("../../core/compile/attributes").Attributes} $attr
24
+ * @param {Object} _ctrl
25
25
  * @param {*} $transclude
26
26
  */
27
27
  link($scope, $element, $attr, _ctrl, $transclude) {
@@ -32,7 +32,7 @@ export function ngIfDirective($animate) {
32
32
  let childScope;
33
33
  /** @type {import("../../shared/jqlite/jqlite").JQLite} */
34
34
  let previousElements;
35
- $scope.$watch($attr.ngIf, (value) => {
35
+ $scope.$watch($attr["ngIf"], (value) => {
36
36
  if (value) {
37
37
  if (!childScope) {
38
38
  $transclude((clone, newScope) => {
@@ -1,5 +1,4 @@
1
1
  import { Angular } from "../../loader";
2
- import { createInjector } from "../../core/di/injector";
3
2
  import { dealoc, JQLite } from "../../shared/jqlite/jqlite";
4
3
  import { EMAIL_REGEXP, ISO_DATE_REGEXP, URL_REGEXP } from "./input";
5
4
  import { forEach } from "../../shared/utils";
@@ -0,0 +1,18 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>AngularTS Test Runner</title>
6
+
7
+ <link rel="shortcut icon" type="image/png" href="/images/favicon.ico" />
8
+ <link rel="stylesheet" href="/jasmine/jasmine-5.1.2/jasmine.css" />
9
+ <script src="/jasmine/jasmine-5.1.2/jasmine.js"></script>
10
+ <script src="/jasmine/jasmine-5.1.2/jasmine-html.js"></script>
11
+ <script src="/jasmine/jasmine-5.1.2/boot0.js"></script>
12
+ <script src="/jasmine/jasmine-5.1.2/boot1.js"></script>
13
+ <script type="module" src="/src/directive/observe/observe.spec.js"></script>
14
+ </head>
15
+ <body>
16
+ <div id="dummy"></div>
17
+ </body>
18
+ </html>
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @returns {import("../../types").Directive}
3
+ */
4
+ export function ngObserveDirective() {
5
+ return {
6
+ restrict: "A",
7
+ link: (scope, element, attrs) => {
8
+ const targetElement = element[0];
9
+ const prop = targetElement.dataset["update"];
10
+ const source = attrs["ngObserve"];
11
+
12
+ if (!scope[prop]) {
13
+ scope[prop] = targetElement.getAttribute(source);
14
+ }
15
+
16
+ const observer = new MutationObserver((mutations) => {
17
+ const mutation = mutations[0];
18
+ const newValue = /** @type {HTMLElement} */ (
19
+ mutation.target
20
+ ).getAttribute(source);
21
+ if (scope[prop] !== newValue) {
22
+ scope[prop] = newValue;
23
+ scope.$digest();
24
+ }
25
+ });
26
+
27
+ observer.observe(targetElement, {
28
+ attributes: true,
29
+ attributeFilter: [source],
30
+ });
31
+
32
+ scope.$on("$destroy", () => {
33
+ observer.disconnect();
34
+ });
35
+ },
36
+ };
37
+ }
@@ -0,0 +1,92 @@
1
+ import { Angular } from "../../loader";
2
+ import { JQLite } from "../../shared/jqlite/jqlite";
3
+
4
+ describe("observe", () => {
5
+ let $compile, $scope, $rootScope, element, observerSpy;
6
+
7
+ beforeEach(() => {
8
+ window.angular = new Angular();
9
+ angular.module("myModule", ["ng"]);
10
+ angular
11
+ .bootstrap(document.getElementById("dummy"), ["myModule"])
12
+ .invoke((_$compile_, _$rootScope_) => {
13
+ $compile = _$compile_;
14
+ $rootScope = _$rootScope_;
15
+ $scope = $rootScope.$new();
16
+ });
17
+
18
+ observerSpy = jasmine.createSpyObj("MutationObserver", [
19
+ "observe",
20
+ "disconnect",
21
+ ]);
22
+ spyOn(window, "MutationObserver").and.returnValue(observerSpy); // Replace with a spy
23
+ });
24
+
25
+ function createDirective(attributeValue, updateProp) {
26
+ const template = `<div ng-observe="${attributeValue}" data-update="${updateProp}"></div>`;
27
+ element = $compile(template)($scope);
28
+ $scope.$digest();
29
+ }
30
+
31
+ it("should set the scope property to the attribute value before any changes", () => {
32
+ const scope = $rootScope.$new();
33
+ const element = JQLite(
34
+ '<div data-update="testProp" ng-observe="sourceAttr"></div>',
35
+ );
36
+ element.attr("sourceAttr", "initialValue");
37
+ $compile(element)(scope);
38
+
39
+ expect(scope.testProp).toBeDefined();
40
+ expect(scope.testProp).toEqual("initialValue");
41
+ });
42
+
43
+ it("should observe attribute changes and update the scope property", () => {
44
+ $scope.myProp = "";
45
+ createDirective("test-attribute", "myProp");
46
+ spyOn($scope, "$digest").and.callThrough();
47
+
48
+ const mutationObserverCallback =
49
+ MutationObserver.calls.mostRecent().args[0];
50
+ const mutationRecord = {
51
+ target: element[0],
52
+ attributeName: "test-attribute",
53
+ };
54
+
55
+ element.attr("test-attribute", "newValue");
56
+ element[0].setAttribute("test-attribute", "newValue");
57
+
58
+ mutationObserverCallback([mutationRecord]);
59
+
60
+ expect($scope.myProp).toBe("newValue");
61
+ expect($scope.$digest).toHaveBeenCalled();
62
+ });
63
+
64
+ it("should not trigger digest cycle if the attribute value is unchanged", () => {
65
+ $scope.myProp = "existingValue";
66
+ createDirective("test-attribute", "myProp");
67
+
68
+ spyOn($scope, "$digest").and.callThrough();
69
+
70
+ const mutationObserverCallback =
71
+ MutationObserver.calls.mostRecent().args[0];
72
+ const mutationRecord = {
73
+ target: element[0],
74
+ attributeName: "test-attribute",
75
+ };
76
+
77
+ element.attr("test-attribute", "existingValue");
78
+ element[0].setAttribute("test-attribute", "existingValue");
79
+
80
+ mutationObserverCallback([mutationRecord]);
81
+
82
+ expect($scope.$digest).not.toHaveBeenCalled();
83
+ });
84
+
85
+ it("should disconnect the observer on scope destruction", () => {
86
+ createDirective("test-attribute", "myProp");
87
+
88
+ $scope.$destroy();
89
+
90
+ expect(observerSpy.disconnect).toHaveBeenCalled();
91
+ });
92
+ });
@@ -0,0 +1,9 @@
1
+ import { test, expect } from "@playwright/test";
2
+
3
+ test("unit observer tests contain no errors", async ({ page }) => {
4
+ await page.goto("src/directive/observe/observe.html");
5
+ await page.content();
6
+ await expect(page.locator(".jasmine-overall-result")).toHaveText(
7
+ /0 failures/,
8
+ );
9
+ });