See here instead https://github.com/vsavkin/ngselectors
Say we have a store defined as follows:
export class Store {
users = {1: "victor", 2: "thomas"};
messages = {1: "victor's message", 2: "thomas's message"};
notifications = new Subject<any>();
constructor() {
setTimeout(() => {
this.users['1'] = 'VICTOR';
this.notifications.next(null);
}, 1000);
setTimeout(() => {
this.messages['1'] = "victor's new message";
this.notifications.next(null);
}, 2000);
}
}
The only important part of the store is that it will emit a notification on change.
Let's define two selectors like this.
export function getUser(store: Store) {
return (id) => store.users[id];
}
type GetUser = (id: string) => string;
export function getMessage(store: Store) {
return (id) => store.messages[id];
}
type GetMessage = (id: string) => string;
Next, let's define a utility function markForCheckWhenSelectorChanges
.
function markForCheckWhenSelectorChanges(t: any):any {
return {
provide: t,
useFactory: _markForCheckFactory,
deps: [Store, ChangeDetectorRef, [new Inject(t), new SkipSelf()]]
};
}
function _markForCheckFactory<T>(store: Store, ref: ChangeDetectorRef, t: T): T {
const f = <any>t;
let stored = [];
let flush = false;
const subscription = store.notifications.subscribe(() => {
for (let i = 0; i < stored.length; ++i) {
const s = stored[i];
if (f(...s[0]) !== s[1]) {
flush = true;
}
}
if (flush) {
// hack because of a bug
(<any>ref).internalView.viewChildren[0].changeDetectorRef.markForCheck();
flush = false;
stored = [];
}
});
return <any>((...args) => {
const r = f(...args);
stored.push([args, r]);
return r;
});
}
Basically, markForCheckWhenSelectorChanges
wraps the selector function. It will record all the invocations of the selector function. Then when the store notifies about a change, the wrapped function will check if anything has changed. If it is the case, it'll request a change detection check.
Let's register the selectors in a module
@NgModule({
providers: [
{
provide: getUser,
useFactory: getUser,
deps: [Store]
},
{
provide: getMessage,
useFactory: getMessage,
deps: [Store]
},
// composing two selectors
{
provide: 'userAndMessage',
useFactory: (u, m) => (userId, messageId) => ({user: u(userId), message: m(messageId)}),
deps: [getUser, getMessage]
}
],
...
})
class MyNgModule {}
And finally, let's use them in our component like this:
@Component({
templateUrl: `
<h2>
User: {{ getUser('1') }}
</h2>
<h2>
Message: {{ getMessage('1') }}
</h2>
<h2>
Global User and Message: {{ globalGetUserAndMessage('1', '1') | json }}
</h2>
<h2>
Local User and Message: {{ localGetUserAndMessage('1', '1') | json }}
</h2>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
markForCheckWhenSelectorChanges(getUser),
markForCheckWhenSelectorChanges(getMessage),
markForCheckWhenSelectorChanges('userAndMessage')
]
})
export class MyComponent {
localGetUserAndMessage: Function;
constructor(@Inject(getUser) private getUser: GetUser,
@Inject(getMessage) private getMessage: GetMessage,
@Inject('userAndMessage') private globalGetUserAndMessage: Function
) {
// composing selectors locally
this.localGetUserAndMessage = (userId, messageId) => ({
user: getUser(userId),
messageId: getMessage(messageId)
});
}
}
markForCheckWhenSelectorChanges
is required only because the component uses the OnPush change detection.- In this example the selectors registered separately from where they are used, but they can be registered in the same place.
- Adding memoization is trivial.