store
store copied to clipboard
Encapsulation and @select
This is a...
- [x] usage question
Hi,
I was wondering if there's an alternative approach to implementing selectors that doesn't require us to explicitly expose the shape of the state tree. I feel like having components know about the state shape is breaking encapsulation and will make refactoring a lot more difficult (especially if you're passing strings to @select()).
It seems to me that there's no good reason for components to even know that Redux is being used to manage state in a large application. One way of getting around the issue could be to only use the implement the selection logic in services and simply return observables / values?
That way, in theory, we could potentially switch out Redux without need to make many (any?) changes to components.
Just wondering if I'm missing anything or if there are any gotchas with this approach or if something already exists to handle this problem?
One approach that I have done, is to pull selectors out to their own file(s), for example something like:
// account-feature-selectors
export someSelector = (state) =>
export someSelector1 = (state) =>
export someSelector1 = (state) =>
// in the component
import { someSelector } from './selectors-location';
export class SomeComponent {
@select(someSelector) prop$;
}
Depending on how ignorant you want your components to be of of redux, could do something like:
@Injectable()
export class FeatureStateService {
@select(someSelector) prop$;
}
export class SomeComponent {
prop$;
constructor(private featureStateService: FeatureStateService) { }
ngOnInit() {
this.prop$ = this.featureStateService.prop$;
}
}
This way, if you wanted to stop using redux, or wanted to switch to NgRx instead of angular-redux - your state services would be the main thing needing refactoring.
With either approach though - you do make your components less aware of the shape of your state, and can also get reuse of your selectors across various components.
The idea to put selectors in a service is actually awesome. I've made this one:
import { select } from '@angular-redux/store';
import { Injectable, OnDestroy } from '@angular/core';
import { Observable, Subscription } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class ReduxService implements OnDestroy {
@select(state => state) appState$: Observable<any>;
appState: any = {};
private linkStateToProp: Subscription;
constructor() {
this.linkStateToProp = this.appState$.subscribe((state = {}) => {
this.appState = state;
});
}
ngOnDestroy() {
this.linkStateToProp.unsubscribe();
}
}
This allows me to use {{ appState }} in the template instead of having to use async pipe for every variable.
I created a package for that and I'm using it here: https://github.com/brunolm/angular-how-to/pull/14