Skip to content

Commit 45cc979

Browse files
committed
address comments
1 parent 492e9fb commit 45cc979

File tree

2 files changed

+65
-20
lines changed

2 files changed

+65
-20
lines changed

src/cdk/observers/observe-content.spec.ts

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ describe('ContentObserver injectable', () => {
124124
describe('basic usage', () => {
125125
let callbacks: Function[];
126126
let invokeCallbacks = (args?: any) => callbacks.forEach(callback => callback(args));
127+
let contentObserver: ContentObserver;
127128

128129
beforeEach(fakeAsync(() => {
129130
callbacks = [];
@@ -149,25 +150,51 @@ describe('ContentObserver injectable', () => {
149150
TestBed.compileComponents();
150151
}));
151152

152-
it('should trigger the callback when the content of the element changes',
153-
fakeAsync(inject([ContentObserver], (co: ContentObserver) => {
154-
let fixture = TestBed.createComponent(UnobservedComponentWithTextContent);
155-
fixture.detectChanges();
153+
beforeEach(inject([ContentObserver], (co: ContentObserver) => {
154+
contentObserver = co;
155+
}));
156+
157+
it('should trigger the callback when the content of the element changes', fakeAsync(() => {
158+
const spy = jasmine.createSpy('content observer');
159+
const fixture = TestBed.createComponent(UnobservedComponentWithTextContent);
160+
fixture.detectChanges();
161+
162+
contentObserver.observe(fixture.componentInstance.contentEl.nativeElement)
163+
.subscribe(() => spy());
156164

165+
expect(spy).not.toHaveBeenCalled();
166+
167+
fixture.componentInstance.text = 'text';
168+
invokeCallbacks();
169+
170+
expect(spy).toHaveBeenCalled();
171+
}));
172+
173+
it('should only create one MutationObserver when observing the same element twice',
174+
fakeAsync(inject([MutationObserverFactory], (mof: MutationObserverFactory) => {
157175
const spy = jasmine.createSpy('content observer');
176+
spyOn(mof, 'create').and.callThrough();
177+
const fixture = TestBed.createComponent(UnobservedComponentWithTextContent);
178+
fixture.detectChanges();
158179

159-
const subscription = co.observe(fixture.componentInstance.contentEl.nativeElement)
180+
const sub1 = contentObserver.observe(fixture.componentInstance.contentEl.nativeElement)
181+
.subscribe(() => spy());
182+
contentObserver.observe(fixture.componentInstance.contentEl.nativeElement)
160183
.subscribe(() => spy());
161184

162-
expect(spy).not.toHaveBeenCalled();
185+
expect(mof.create).toHaveBeenCalledTimes(1);
163186

164187
fixture.componentInstance.text = 'text';
165-
fixture.detectChanges();
166188
invokeCallbacks();
167189

168-
expect(spy).toHaveBeenCalled();
190+
expect(spy).toHaveBeenCalledTimes(2);
191+
192+
spy.calls.reset();
193+
sub1.unsubscribe();
194+
fixture.componentInstance.text = 'text text';
195+
invokeCallbacks();
169196

170-
subscription.unsubscribe();
197+
expect(spy).toHaveBeenCalledTimes(1);
171198
})));
172199
});
173200
});
@@ -186,7 +213,7 @@ class ComponentWithTextContent {
186213
doSomething() {}
187214
}
188215

189-
@Component({ template: `<div (cdkObserveContent)="doSomething()"><div>{{text}}<div></div>` })
216+
@Component({ template: `<div (cdkObserveContent)="doSomething()"><div>{{text}}</div></div>` })
190217
class ComponentWithChildTextContent {
191218
text = '';
192219
doSomething() {}

src/cdk/observers/observe-content.ts

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@ export class MutationObserverFactory {
3333
}
3434

3535

36-
/** A factory that creates ContentObservers. */
36+
/** An injectable service that allows watching elements for changes to their content. */
3737
@Injectable({providedIn: 'root'})
38-
export class ContentObserver {
38+
export class ContentObserver implements OnDestroy {
39+
/** Keeps track of the existing MutationObservers so they can be reused. */
3940
private _observedElements = new Map<Element, {
4041
observer: MutationObserver | null,
4142
stream: Subject<MutationRecord[]>,
@@ -44,19 +45,30 @@ export class ContentObserver {
4445

4546
constructor(private _mutationObserverFactory: MutationObserverFactory) {}
4647

47-
observe(element: Element, debounce?: number): Observable<MutationRecord[]> {
48+
ngOnDestroy() {
49+
this._observedElements.forEach((_, element) => this._cleanupObserver(element));
50+
}
51+
52+
/**
53+
* Observe content changes on an element.
54+
* @param element The element to observe for content changes.
55+
*/
56+
observe(element: Element): Observable<MutationRecord[]> {
4857
return Observable.create(observer => {
4958
const stream = this._observeElement(element);
50-
const subscription =
51-
(debounce ? stream.pipe(debounceTime(debounce)) : stream).subscribe(observer);
59+
const subscription = stream.subscribe(observer);
5260

5361
return () => {
5462
subscription.unsubscribe();
5563
this._unobserveElement(element);
56-
}
64+
};
5765
});
5866
}
5967

68+
/**
69+
* Observes the given element by using the existing MutationObserver if available, or creating a
70+
* new one if not.
71+
*/
6072
private _observeElement(element: Element): Subject<MutationRecord[]> {
6173
if (!this._observedElements.has(element)) {
6274
const stream = new Subject<MutationRecord[]>();
@@ -75,14 +87,20 @@ export class ContentObserver {
7587
return this._observedElements.get(element)!.stream;
7688
}
7789

90+
/**
91+
* Un-observes the given element and cleans up the underlying MutationObserver if nobody else is
92+
* observing this element.
93+
*/
7894
private _unobserveElement(element: Element) {
7995
if (this._observedElements.has(element)) {
80-
if (!--this._observedElements.get(element)!.count) {
96+
this._observedElements.get(element)!.count--;
97+
if (!this._observedElements.get(element)!.count) {
8198
this._cleanupObserver(element);
8299
}
83100
}
84101
}
85102

103+
/** Clean up the underlying MutationObserver for the specified element. */
86104
private _cleanupObserver(element: Element) {
87105
if (this._observedElements.has(element)) {
88106
const {observer, stream} = this._observedElements.get(element)!;
@@ -149,9 +167,9 @@ export class CdkObserveContent implements AfterContentInit, OnDestroy {
149167

150168
private _subscribe() {
151169
this._unsubscribe();
152-
this._currentSubscription =
153-
this._contentObserver.observe(this._elementRef.nativeElement, this.debounce)
154-
.subscribe(mutations => this.event.next(mutations));
170+
const stream = this._contentObserver.observe(this._elementRef.nativeElement);
171+
this._currentSubscription = (this.debounce ? stream.pipe(debounceTime(this.debounce)) : stream)
172+
.subscribe(mutations => this.event.next(mutations));
155173
}
156174

157175
private _unsubscribe() {

0 commit comments

Comments
 (0)