observers.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. /**
  2. * @license
  3. * Copyright Google LLC All Rights Reserved.
  4. *
  5. * Use of this source code is governed by an MIT-style license that can be
  6. * found in the LICENSE file at https://angular.io/license
  7. */
  8. import { coerceBooleanProperty, coerceNumberProperty, coerceElement } from '@angular/cdk/coercion';
  9. import { Directive, ElementRef, EventEmitter, Injectable, Input, NgModule, NgZone, Output, ɵɵdefineInjectable, ɵɵinject } from '@angular/core';
  10. import { Observable, Subject } from 'rxjs';
  11. import { debounceTime } from 'rxjs/operators';
  12. /**
  13. * @fileoverview added by tsickle
  14. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  15. */
  16. /**
  17. * Factory that creates a new MutationObserver and allows us to stub it out in unit tests.
  18. * \@docs-private
  19. */
  20. class MutationObserverFactory {
  21. /**
  22. * @param {?} callback
  23. * @return {?}
  24. */
  25. create(callback) {
  26. return typeof MutationObserver === 'undefined' ? null : new MutationObserver(callback);
  27. }
  28. }
  29. MutationObserverFactory.decorators = [
  30. { type: Injectable, args: [{ providedIn: 'root' },] },
  31. ];
  32. /** @nocollapse */ MutationObserverFactory.ngInjectableDef = ɵɵdefineInjectable({ factory: function MutationObserverFactory_Factory() { return new MutationObserverFactory(); }, token: MutationObserverFactory, providedIn: "root" });
  33. /**
  34. * An injectable service that allows watching elements for changes to their content.
  35. */
  36. class ContentObserver {
  37. /**
  38. * @param {?} _mutationObserverFactory
  39. */
  40. constructor(_mutationObserverFactory) {
  41. this._mutationObserverFactory = _mutationObserverFactory;
  42. /**
  43. * Keeps track of the existing MutationObservers so they can be reused.
  44. */
  45. this._observedElements = new Map();
  46. }
  47. /**
  48. * @return {?}
  49. */
  50. ngOnDestroy() {
  51. this._observedElements.forEach((/**
  52. * @param {?} _
  53. * @param {?} element
  54. * @return {?}
  55. */
  56. (_, element) => this._cleanupObserver(element)));
  57. }
  58. /**
  59. * @param {?} elementOrRef
  60. * @return {?}
  61. */
  62. observe(elementOrRef) {
  63. /** @type {?} */
  64. const element = coerceElement(elementOrRef);
  65. return new Observable((/**
  66. * @param {?} observer
  67. * @return {?}
  68. */
  69. (observer) => {
  70. /** @type {?} */
  71. const stream = this._observeElement(element);
  72. /** @type {?} */
  73. const subscription = stream.subscribe(observer);
  74. return (/**
  75. * @return {?}
  76. */
  77. () => {
  78. subscription.unsubscribe();
  79. this._unobserveElement(element);
  80. });
  81. }));
  82. }
  83. /**
  84. * Observes the given element by using the existing MutationObserver if available, or creating a
  85. * new one if not.
  86. * @private
  87. * @param {?} element
  88. * @return {?}
  89. */
  90. _observeElement(element) {
  91. if (!this._observedElements.has(element)) {
  92. /** @type {?} */
  93. const stream = new Subject();
  94. /** @type {?} */
  95. const observer = this._mutationObserverFactory.create((/**
  96. * @param {?} mutations
  97. * @return {?}
  98. */
  99. mutations => stream.next(mutations)));
  100. if (observer) {
  101. observer.observe(element, {
  102. characterData: true,
  103. childList: true,
  104. subtree: true
  105. });
  106. }
  107. this._observedElements.set(element, { observer, stream, count: 1 });
  108. }
  109. else {
  110. (/** @type {?} */ (this._observedElements.get(element))).count++;
  111. }
  112. return (/** @type {?} */ (this._observedElements.get(element))).stream;
  113. }
  114. /**
  115. * Un-observes the given element and cleans up the underlying MutationObserver if nobody else is
  116. * observing this element.
  117. * @private
  118. * @param {?} element
  119. * @return {?}
  120. */
  121. _unobserveElement(element) {
  122. if (this._observedElements.has(element)) {
  123. (/** @type {?} */ (this._observedElements.get(element))).count--;
  124. if (!(/** @type {?} */ (this._observedElements.get(element))).count) {
  125. this._cleanupObserver(element);
  126. }
  127. }
  128. }
  129. /**
  130. * Clean up the underlying MutationObserver for the specified element.
  131. * @private
  132. * @param {?} element
  133. * @return {?}
  134. */
  135. _cleanupObserver(element) {
  136. if (this._observedElements.has(element)) {
  137. const { observer, stream } = (/** @type {?} */ (this._observedElements.get(element)));
  138. if (observer) {
  139. observer.disconnect();
  140. }
  141. stream.complete();
  142. this._observedElements.delete(element);
  143. }
  144. }
  145. }
  146. ContentObserver.decorators = [
  147. { type: Injectable, args: [{ providedIn: 'root' },] },
  148. ];
  149. /** @nocollapse */
  150. ContentObserver.ctorParameters = () => [
  151. { type: MutationObserverFactory }
  152. ];
  153. /** @nocollapse */ ContentObserver.ngInjectableDef = ɵɵdefineInjectable({ factory: function ContentObserver_Factory() { return new ContentObserver(ɵɵinject(MutationObserverFactory)); }, token: ContentObserver, providedIn: "root" });
  154. /**
  155. * Directive that triggers a callback whenever the content of
  156. * its associated element has changed.
  157. */
  158. class CdkObserveContent {
  159. /**
  160. * @param {?} _contentObserver
  161. * @param {?} _elementRef
  162. * @param {?} _ngZone
  163. */
  164. constructor(_contentObserver, _elementRef, _ngZone) {
  165. this._contentObserver = _contentObserver;
  166. this._elementRef = _elementRef;
  167. this._ngZone = _ngZone;
  168. /**
  169. * Event emitted for each change in the element's content.
  170. */
  171. this.event = new EventEmitter();
  172. this._disabled = false;
  173. this._currentSubscription = null;
  174. }
  175. /**
  176. * Whether observing content is disabled. This option can be used
  177. * to disconnect the underlying MutationObserver until it is needed.
  178. * @return {?}
  179. */
  180. get disabled() { return this._disabled; }
  181. /**
  182. * @param {?} value
  183. * @return {?}
  184. */
  185. set disabled(value) {
  186. this._disabled = coerceBooleanProperty(value);
  187. this._disabled ? this._unsubscribe() : this._subscribe();
  188. }
  189. /**
  190. * Debounce interval for emitting the changes.
  191. * @return {?}
  192. */
  193. get debounce() { return this._debounce; }
  194. /**
  195. * @param {?} value
  196. * @return {?}
  197. */
  198. set debounce(value) {
  199. this._debounce = coerceNumberProperty(value);
  200. this._subscribe();
  201. }
  202. /**
  203. * @return {?}
  204. */
  205. ngAfterContentInit() {
  206. if (!this._currentSubscription && !this.disabled) {
  207. this._subscribe();
  208. }
  209. }
  210. /**
  211. * @return {?}
  212. */
  213. ngOnDestroy() {
  214. this._unsubscribe();
  215. }
  216. /**
  217. * @private
  218. * @return {?}
  219. */
  220. _subscribe() {
  221. this._unsubscribe();
  222. /** @type {?} */
  223. const stream = this._contentObserver.observe(this._elementRef);
  224. // TODO(mmalerba): We shouldn't be emitting on this @Output() outside the zone.
  225. // Consider brining it back inside the zone next time we're making breaking changes.
  226. // Bringing it back inside can cause things like infinite change detection loops and changed
  227. // after checked errors if people's code isn't handling it properly.
  228. this._ngZone.runOutsideAngular((/**
  229. * @return {?}
  230. */
  231. () => {
  232. this._currentSubscription =
  233. (this.debounce ? stream.pipe(debounceTime(this.debounce)) : stream).subscribe(this.event);
  234. }));
  235. }
  236. /**
  237. * @private
  238. * @return {?}
  239. */
  240. _unsubscribe() {
  241. if (this._currentSubscription) {
  242. this._currentSubscription.unsubscribe();
  243. }
  244. }
  245. }
  246. CdkObserveContent.decorators = [
  247. { type: Directive, args: [{
  248. selector: '[cdkObserveContent]',
  249. exportAs: 'cdkObserveContent',
  250. },] },
  251. ];
  252. /** @nocollapse */
  253. CdkObserveContent.ctorParameters = () => [
  254. { type: ContentObserver },
  255. { type: ElementRef },
  256. { type: NgZone }
  257. ];
  258. CdkObserveContent.propDecorators = {
  259. event: [{ type: Output, args: ['cdkObserveContent',] }],
  260. disabled: [{ type: Input, args: ['cdkObserveContentDisabled',] }],
  261. debounce: [{ type: Input }]
  262. };
  263. class ObserversModule {
  264. }
  265. ObserversModule.decorators = [
  266. { type: NgModule, args: [{
  267. exports: [CdkObserveContent],
  268. declarations: [CdkObserveContent],
  269. providers: [MutationObserverFactory]
  270. },] },
  271. ];
  272. /**
  273. * @fileoverview added by tsickle
  274. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  275. */
  276. /**
  277. * @fileoverview added by tsickle
  278. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  279. */
  280. export { MutationObserverFactory, ContentObserver, CdkObserveContent, ObserversModule };
  281. //# sourceMappingURL=observers.js.map