Skip to content

Commit bee71da

Browse files
committed
core(initialized): add mixin for initialized
1 parent 7613f98 commit bee71da

File tree

4 files changed

+131
-0
lines changed

4 files changed

+131
-0
lines changed

src/lib/core/common-behaviors/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ export {CanColor, mixinColor, ThemePalette} from './color';
1212
export {CanDisableRipple, mixinDisableRipple} from './disable-ripple';
1313
export {HasTabIndex, mixinTabIndex} from './tabindex';
1414
export {CanUpdateErrorState, mixinErrorState} from './error-state';
15+
export {OnInitialized, mixinInitialized} from './initialized';
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import {mixinInitialized} from './initialized';
2+
import {OnInitialized} from '@angular/material/core';
3+
4+
describe('MixinOnInitialized', () => {
5+
class EmptyClass { }
6+
let instance: OnInitialized;
7+
8+
beforeEach(() => {
9+
10+
const classWithOnInitialized = mixinInitialized(EmptyClass);
11+
instance = new classWithOnInitialized();
12+
});
13+
14+
it('should emit for subscriptions made before the directive was marked as initialized', done => {
15+
// Listen for an event from the initialized stream and mark the test as done when it emits.
16+
instance.initialized.subscribe(() => done());
17+
18+
// Mark the class as initialized so that the stream emits and the test completes.
19+
instance._markInitialized();
20+
});
21+
22+
it('should emit for subscriptions made after the directive was marked as initialized', done => {
23+
// Mark the class as initialized so the stream emits when subscribed and the test completes.
24+
instance._markInitialized();
25+
26+
// Listen for an event from the initialized stream and mark the test as done when it emits.
27+
instance.initialized.subscribe(() => done());
28+
});
29+
30+
it('should emit for multiple subscriptions made before and after marked as initialized', done => {
31+
// Should expect the number of notifications to match the number of subscriptions.
32+
const expectedNotificationCount = 4;
33+
let currentNotificationCount = 0;
34+
35+
// Function that completes the test when the number of notifications meets the expectation.
36+
function onNotified() {
37+
if (++currentNotificationCount === expectedNotificationCount) {
38+
done();
39+
}
40+
}
41+
42+
instance.initialized.subscribe(onNotified); // Subscription 1
43+
instance.initialized.subscribe(onNotified); // Subscription 2
44+
45+
instance._markInitialized();
46+
47+
instance.initialized.subscribe(onNotified); // Subscription 3
48+
instance.initialized.subscribe(onNotified); // Subscription 4
49+
});
50+
});
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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+
9+
import {Constructor} from './constructor';
10+
import {Observable} from 'rxjs/Observable';
11+
import {Subscriber} from 'rxjs/Subscriber';
12+
13+
/**
14+
* Mixin that adds an initialized property to a directive which, when subscribed to, will emit a
15+
* value once markInitialized has been called, which should be done during the ngOnInit function.
16+
* If the subscription is made after it has already been marked as initialized, then it will trigger
17+
* an emit immediately.
18+
* @docs-private
19+
*/
20+
export interface OnInitialized {
21+
/** Stream that emits once during the directive/component's ngOnInit. */
22+
initialized: Observable<void>;
23+
24+
/**
25+
* Sets the state as initialized and must be called during ngOnInit to notify subscribers that
26+
* the directive has been initialized.
27+
* @docs-private
28+
*/
29+
_markInitialized: () => void;
30+
}
31+
32+
/** Mixin to augment a directive with an initialized property that will emits when ngOnInit ends. */
33+
export function mixinInitialized<T extends Constructor<{}>>(base: T):
34+
Constructor<OnInitialized> & T {
35+
return class extends base {
36+
/** Whether this directive has been marked as initialized. */
37+
_isInitialized = false;
38+
39+
/**
40+
* List of subscribers that subscribed before the directive was initialized. Should be notified
41+
* during _markInitialized.
42+
*/
43+
_pendingSubscribers: Subscriber<void>[] = [];
44+
45+
/**
46+
* Observable stream that emits when the directive initializes. If already initialized, the
47+
* subscriber is stored to be notified once _markInitialized is called.
48+
*/
49+
initialized = new Observable<void>(subscriber => {
50+
// If initialized, immediately notify the subscriber. Otherwise store the subscriber to notify
51+
// when _markInitialized is called.
52+
if (this._isInitialized) {
53+
this._notifySubscriber(subscriber);
54+
} else {
55+
this._pendingSubscribers.push(subscriber);
56+
}
57+
});
58+
59+
constructor(...args: any[]) { super(...args); }
60+
61+
/**
62+
* Marks the state as initialized and notifies pending subscribers. Should be called at the end
63+
* of ngOnInit.
64+
* @docs-private
65+
*/
66+
_markInitialized(): void {
67+
this._isInitialized = true;
68+
69+
this._pendingSubscribers.forEach(this._notifySubscriber);
70+
this._pendingSubscribers = [];
71+
}
72+
73+
/** Emits and completes the subscriber stream (should only emit once). */
74+
_notifySubscriber(subscriber: Subscriber<void>): void {
75+
subscriber.next();
76+
subscriber.complete();
77+
}
78+
};
79+
}

tools/package-tools/rollup-globals.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export const rollupGlobals = {
5454
...rollupMatEntryPoints,
5555

5656
'rxjs/BehaviorSubject': 'Rx',
57+
'rxjs/ReplaySubject': 'Rx',
5758
'rxjs/Observable': 'Rx',
5859
'rxjs/Subject': 'Rx',
5960
'rxjs/Subscription': 'Rx',

0 commit comments

Comments
 (0)