observers.mjs 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. import * as i0 from '@angular/core';
  2. import { Injectable, inject, NgZone, ElementRef, EventEmitter, booleanAttribute, Directive, Output, Input, NgModule } from '@angular/core';
  3. import { Observable, Subject } from 'rxjs';
  4. import { map, filter, debounceTime } from 'rxjs/operators';
  5. import { c as coerceNumberProperty, a as coerceElement } from './element-x4z00URv.mjs';
  6. // Angular may add, remove, or edit comment nodes during change detection. We don't care about
  7. // these changes because they don't affect the user-preceived content, and worse it can cause
  8. // infinite change detection cycles where the change detection updates a comment, triggering the
  9. // MutationObserver, triggering another change detection and kicking the cycle off again.
  10. function shouldIgnoreRecord(record) {
  11. // Ignore changes to comment text.
  12. if (record.type === 'characterData' && record.target instanceof Comment) {
  13. return true;
  14. }
  15. // Ignore addition / removal of comments.
  16. if (record.type === 'childList') {
  17. for (let i = 0; i < record.addedNodes.length; i++) {
  18. if (!(record.addedNodes[i] instanceof Comment)) {
  19. return false;
  20. }
  21. }
  22. for (let i = 0; i < record.removedNodes.length; i++) {
  23. if (!(record.removedNodes[i] instanceof Comment)) {
  24. return false;
  25. }
  26. }
  27. return true;
  28. }
  29. // Observe everything else.
  30. return false;
  31. }
  32. /**
  33. * Factory that creates a new MutationObserver and allows us to stub it out in unit tests.
  34. * @docs-private
  35. */
  36. class MutationObserverFactory {
  37. create(callback) {
  38. return typeof MutationObserver === 'undefined' ? null : new MutationObserver(callback);
  39. }
  40. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MutationObserverFactory, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
  41. static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MutationObserverFactory, providedIn: 'root' });
  42. }
  43. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MutationObserverFactory, decorators: [{
  44. type: Injectable,
  45. args: [{ providedIn: 'root' }]
  46. }] });
  47. /** An injectable service that allows watching elements for changes to their content. */
  48. class ContentObserver {
  49. _mutationObserverFactory = inject(MutationObserverFactory);
  50. /** Keeps track of the existing MutationObservers so they can be reused. */
  51. _observedElements = new Map();
  52. _ngZone = inject(NgZone);
  53. constructor() { }
  54. ngOnDestroy() {
  55. this._observedElements.forEach((_, element) => this._cleanupObserver(element));
  56. }
  57. observe(elementOrRef) {
  58. const element = coerceElement(elementOrRef);
  59. return new Observable((observer) => {
  60. const stream = this._observeElement(element);
  61. const subscription = stream
  62. .pipe(map(records => records.filter(record => !shouldIgnoreRecord(record))), filter(records => !!records.length))
  63. .subscribe(records => {
  64. this._ngZone.run(() => {
  65. observer.next(records);
  66. });
  67. });
  68. return () => {
  69. subscription.unsubscribe();
  70. this._unobserveElement(element);
  71. };
  72. });
  73. }
  74. /**
  75. * Observes the given element by using the existing MutationObserver if available, or creating a
  76. * new one if not.
  77. */
  78. _observeElement(element) {
  79. return this._ngZone.runOutsideAngular(() => {
  80. if (!this._observedElements.has(element)) {
  81. const stream = new Subject();
  82. const observer = this._mutationObserverFactory.create(mutations => stream.next(mutations));
  83. if (observer) {
  84. observer.observe(element, {
  85. characterData: true,
  86. childList: true,
  87. subtree: true,
  88. });
  89. }
  90. this._observedElements.set(element, { observer, stream, count: 1 });
  91. }
  92. else {
  93. this._observedElements.get(element).count++;
  94. }
  95. return this._observedElements.get(element).stream;
  96. });
  97. }
  98. /**
  99. * Un-observes the given element and cleans up the underlying MutationObserver if nobody else is
  100. * observing this element.
  101. */
  102. _unobserveElement(element) {
  103. if (this._observedElements.has(element)) {
  104. this._observedElements.get(element).count--;
  105. if (!this._observedElements.get(element).count) {
  106. this._cleanupObserver(element);
  107. }
  108. }
  109. }
  110. /** Clean up the underlying MutationObserver for the specified element. */
  111. _cleanupObserver(element) {
  112. if (this._observedElements.has(element)) {
  113. const { observer, stream } = this._observedElements.get(element);
  114. if (observer) {
  115. observer.disconnect();
  116. }
  117. stream.complete();
  118. this._observedElements.delete(element);
  119. }
  120. }
  121. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ContentObserver, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
  122. static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ContentObserver, providedIn: 'root' });
  123. }
  124. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ContentObserver, decorators: [{
  125. type: Injectable,
  126. args: [{ providedIn: 'root' }]
  127. }], ctorParameters: () => [] });
  128. /**
  129. * Directive that triggers a callback whenever the content of
  130. * its associated element has changed.
  131. */
  132. class CdkObserveContent {
  133. _contentObserver = inject(ContentObserver);
  134. _elementRef = inject(ElementRef);
  135. /** Event emitted for each change in the element's content. */
  136. event = new EventEmitter();
  137. /**
  138. * Whether observing content is disabled. This option can be used
  139. * to disconnect the underlying MutationObserver until it is needed.
  140. */
  141. get disabled() {
  142. return this._disabled;
  143. }
  144. set disabled(value) {
  145. this._disabled = value;
  146. this._disabled ? this._unsubscribe() : this._subscribe();
  147. }
  148. _disabled = false;
  149. /** Debounce interval for emitting the changes. */
  150. get debounce() {
  151. return this._debounce;
  152. }
  153. set debounce(value) {
  154. this._debounce = coerceNumberProperty(value);
  155. this._subscribe();
  156. }
  157. _debounce;
  158. _currentSubscription = null;
  159. constructor() { }
  160. ngAfterContentInit() {
  161. if (!this._currentSubscription && !this.disabled) {
  162. this._subscribe();
  163. }
  164. }
  165. ngOnDestroy() {
  166. this._unsubscribe();
  167. }
  168. _subscribe() {
  169. this._unsubscribe();
  170. const stream = this._contentObserver.observe(this._elementRef);
  171. this._currentSubscription = (this.debounce ? stream.pipe(debounceTime(this.debounce)) : stream).subscribe(this.event);
  172. }
  173. _unsubscribe() {
  174. this._currentSubscription?.unsubscribe();
  175. }
  176. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkObserveContent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
  177. static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "19.2.6", type: CdkObserveContent, isStandalone: true, selector: "[cdkObserveContent]", inputs: { disabled: ["cdkObserveContentDisabled", "disabled", booleanAttribute], debounce: "debounce" }, outputs: { event: "cdkObserveContent" }, exportAs: ["cdkObserveContent"], ngImport: i0 });
  178. }
  179. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkObserveContent, decorators: [{
  180. type: Directive,
  181. args: [{
  182. selector: '[cdkObserveContent]',
  183. exportAs: 'cdkObserveContent',
  184. }]
  185. }], ctorParameters: () => [], propDecorators: { event: [{
  186. type: Output,
  187. args: ['cdkObserveContent']
  188. }], disabled: [{
  189. type: Input,
  190. args: [{ alias: 'cdkObserveContentDisabled', transform: booleanAttribute }]
  191. }], debounce: [{
  192. type: Input
  193. }] } });
  194. class ObserversModule {
  195. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ObserversModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
  196. static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.6", ngImport: i0, type: ObserversModule, imports: [CdkObserveContent], exports: [CdkObserveContent] });
  197. static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ObserversModule, providers: [MutationObserverFactory] });
  198. }
  199. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ObserversModule, decorators: [{
  200. type: NgModule,
  201. args: [{
  202. imports: [CdkObserveContent],
  203. exports: [CdkObserveContent],
  204. providers: [MutationObserverFactory],
  205. }]
  206. }] });
  207. export { CdkObserveContent, ContentObserver, MutationObserverFactory, ObserversModule };
  208. //# sourceMappingURL=observers.mjs.map