@@ -38,40 +38,49 @@ export class MutationObserverFactory {
38
38
@Injectable ( { providedIn : 'root' } )
39
39
export class ContentObserver implements OnDestroy {
40
40
/** Keeps track of the existing MutationObservers so they can be reused. */
41
- private _observedElements = new Map < Element , {
41
+ private _observedElements = new Map < Element , Map < string , {
42
42
observer : MutationObserver | null ,
43
43
stream : Subject < MutationRecord [ ] > ,
44
44
count : number
45
- } > ( ) ;
45
+ } > > ( ) ;
46
46
47
47
constructor ( private _mutationObserverFactory : MutationObserverFactory ) { }
48
48
49
49
ngOnDestroy ( ) {
50
- this . _observedElements . forEach ( ( _ , element ) => this . _cleanupObserver ( element ) ) ;
50
+ this . _observedElements . forEach ( ( cache , element ) => {
51
+ cache . forEach ( ( _ , key ) => this . _cleanupObserver ( element , key ) ) ;
52
+ } ) ;
51
53
}
52
54
53
55
/**
54
56
* Observe content changes on an element.
55
57
* @param element The element to observe for content changes.
58
+ * @param options Options that can be used to configure what is being observed,
56
59
*/
57
- observe ( element : Element ) : Observable < MutationRecord [ ] > ;
60
+ observe ( element : Element , options ?: MutationObserverInit ) : Observable < MutationRecord [ ] > ;
58
61
59
62
/**
60
63
* Observe content changes on an element.
61
64
* @param element The element to observe for content changes.
65
+ * @param options Options that can be used to configure what is being observed,
62
66
*/
63
- observe ( element : ElementRef < Element > ) : Observable < MutationRecord [ ] > ;
67
+ observe ( element : ElementRef < Element > , options ?: MutationObserverInit ) :
68
+ Observable < MutationRecord [ ] > ;
64
69
65
- observe ( elementOrRef : Element | ElementRef < Element > ) : Observable < MutationRecord [ ] > {
70
+ observe ( elementOrRef : Element | ElementRef < Element > , options : MutationObserverInit = {
71
+ characterData : true ,
72
+ childList : true ,
73
+ subtree : true
74
+ } ) : Observable < MutationRecord [ ] > {
66
75
const element = coerceElement ( elementOrRef ) ;
67
76
68
77
return new Observable ( ( observer : Observer < MutationRecord [ ] > ) => {
69
- const stream = this . _observeElement ( element ) ;
78
+ const stream = this . _observeElement ( element , options ) ;
70
79
const subscription = stream . subscribe ( observer ) ;
71
80
72
81
return ( ) => {
73
82
subscription . unsubscribe ( ) ;
74
- this . _unobserveElement ( element ) ;
83
+ this . _unobserveElement ( element , options ) ;
75
84
} ;
76
85
} ) ;
77
86
}
@@ -80,47 +89,95 @@ export class ContentObserver implements OnDestroy {
80
89
* Observes the given element by using the existing MutationObserver if available, or creating a
81
90
* new one if not.
82
91
*/
83
- private _observeElement ( element : Element ) : Subject < MutationRecord [ ] > {
84
- if ( ! this . _observedElements . has ( element ) ) {
92
+ private _observeElement ( element : Element , options : MutationObserverInit ) :
93
+ Subject < MutationRecord [ ] > {
94
+
95
+ const observedElements = this . _observedElements ;
96
+ const cacheKey = this . _getCacheKey ( options ) ;
97
+ let elementEntry = observedElements . get ( element ) ;
98
+
99
+ if ( ! elementEntry ) {
100
+ elementEntry = new Map ( ) ;
101
+ observedElements . set ( element , elementEntry ) ;
102
+ }
103
+
104
+ const cachedConfig = elementEntry . get ( cacheKey ) ;
105
+
106
+ if ( cachedConfig ) {
107
+ cachedConfig . count ++ ;
108
+ return cachedConfig . stream ;
109
+ } else {
85
110
const stream = new Subject < MutationRecord [ ] > ( ) ;
86
111
const observer = this . _mutationObserverFactory . create ( mutations => stream . next ( mutations ) ) ;
112
+
87
113
if ( observer ) {
88
- observer . observe ( element , {
89
- characterData : true ,
90
- childList : true ,
91
- subtree : true
92
- } ) ;
114
+ observer . observe ( element , options ) ;
93
115
}
94
- this . _observedElements . set ( element , { observer , stream , count : 1 } ) ;
95
- } else {
96
- this . _observedElements . get ( element ) ! . count ++ ;
116
+
117
+ elementEntry . set ( cacheKey , { observer , stream , count : 1 } ) ;
118
+ return stream ;
97
119
}
98
- return this . _observedElements . get ( element ) ! . stream ;
99
120
}
100
121
101
122
/**
102
123
* Un-observes the given element and cleans up the underlying MutationObserver if nobody else is
103
124
* observing this element.
104
125
*/
105
- private _unobserveElement ( element : Element ) {
106
- if ( this . _observedElements . has ( element ) ) {
107
- this . _observedElements . get ( element ) ! . count -- ;
108
- if ( ! this . _observedElements . get ( element ) ! . count ) {
109
- this . _cleanupObserver ( element ) ;
126
+ private _unobserveElement ( element : Element , options : MutationObserverInit ) {
127
+ const cacheKey = this . _getCacheKey ( options ) ;
128
+ const cachedConfig = this . _getConfig ( element , cacheKey ) ;
129
+
130
+ if ( cachedConfig ) {
131
+ cachedConfig . count -- ;
132
+
133
+ if ( cachedConfig . count < 1 ) {
134
+ this . _cleanupObserver ( element , cacheKey ) ;
110
135
}
111
136
}
112
137
}
113
138
114
139
/** Clean up the underlying MutationObserver for the specified element. */
115
- private _cleanupObserver ( element : Element ) {
116
- if ( this . _observedElements . has ( element ) ) {
117
- const { observer, stream} = this . _observedElements . get ( element ) ! ;
140
+ private _cleanupObserver ( element : Element , cacheKey : string ) {
141
+ const cachedConfig = this . _getConfig ( element , cacheKey ) ;
142
+
143
+ if ( cachedConfig ) {
144
+ const { observer, stream} = cachedConfig ;
145
+
118
146
if ( observer ) {
119
147
observer . disconnect ( ) ;
120
148
}
149
+
121
150
stream . complete ( ) ;
122
- this . _observedElements . delete ( element ) ;
151
+ this . _observedElements . get ( element ) ! . delete ( cacheKey ) ;
152
+
153
+ if ( this . _observedElements . get ( element ) ! . size < 1 ) {
154
+ this . _observedElements . delete ( element ) ;
155
+ }
156
+ }
157
+ }
158
+
159
+ /** Gets the cached config for an element, based on a cache key. */
160
+ private _getConfig ( element : Element , cacheKey : string ) {
161
+ const elementEntry = this . _observedElements . get ( element ) ;
162
+
163
+ if ( elementEntry ) {
164
+ return elementEntry . get ( cacheKey ) ;
123
165
}
166
+
167
+ return undefined ;
168
+ }
169
+
170
+ /** Generates a key for the element cache from a MutationObserver configuration object. */
171
+ private _getCacheKey ( options : MutationObserverInit ) : string {
172
+ return [
173
+ options . attributeFilter ? options . attributeFilter . join ( ',' ) : '' ,
174
+ ! ! options . attributeOldValue ,
175
+ ! ! options . attributes ,
176
+ ! ! options . characterData ,
177
+ ! ! options . characterDataOldValue ,
178
+ ! ! options . childList ,
179
+ ! ! options . subtree
180
+ ] . join ( '|' ) ;
124
181
}
125
182
}
126
183
0 commit comments