Reputation: 223
I need to get access to a state inside a Component. Most of the examples show how to consume the state inside the template, using @select and async pipe but not directly in a Component. The needed state is not supposed to change in the time.
I see 3 solutions :
1/ Use getState()
constructor(private ngRedux: NgRedux) { }
ngOnInit() {
this.prop = this.ngRedux.getState().prop;
// Do what I need with "this.prop"
}
2/ Move this getter into a Action Creator Service
getProp() {
return this.ngRedux.getState().prop;
}
3/ Subscribe to the @select() property
@select('stateProp') stateProp$;
property: string;
constructor(private ngRedux: NgRedux) { }
ngOnInit() {
stateProp$.subscribe(state => this.property = state)
// Do what I need with "this.property"
}
What's the best way to perform that ?
Thx
Upvotes: 2
Views: 4213
Reputation: 23483
There's probably no right or wrong answer to this, but having just added Redux to an Angular app, here's my thoughts.
ngRedux.getState()
returns a snapshot, whereas @select()
sets up a pipeline that responds every time the state fragment changes.
One of the major reasons for using Redux is to reduce complexity with shared state, in which case we want to use @select
in order to respond to changes caused by other components.
Example 3 above is problematic.
Firstly, we can't be sure that the subscription will complete before the rest of the ngOnInit()
code executes, so this.property
may be uninitialized when we don't expect it to be.
Secondly, do we want a snapshot, in which case use getState()
, or do we want a pipeline, in which case move the subsequent code inside the subscribe:
@select('stateProp') stateProp$;
property: string;
constructor(private ngRedux: NgRedux) { }
ngOnInit() {
stateProp$.subscribe(state => {
this.property = state;
// Do what I need with "this.property"
})
}
The same might be said when we use this.property
in other methods.
Seems to me this.property
and stateProp$
refer to the same state, so have one or the other but not both.
Aside from constraining the shape of the code, I've found @select()
to have some other problems.
Pipeline executes before state is initialized.
Depending on how state is initialized, we may receive an undefined
value through the pipeline, e.g
export interface IAppState {
config?: any;
}
export const initialState: IAppState = {}
@select() config$: any;
ngOnInit() {
config$.subscribe(config => {
this.color = config.color; // Error: Cannot read property 'color' of undefined
// May happen if config is obtained async
}
}
A guard can be used:
ngOnInit() {
config$
.filter(data => !!data)
.subscribe(config => {
this.color = config.color;
}
}
or make sure initialState initializes all object levels:
export const initialState: IAppState = { config: {} }
Pipeline continuously executes
If you need to look at existing state before updating it, the subscription will continuously fire. Note, the connection between subscription and dispatch might be subtle (sub-state change, or separation by nested methods):
@select() myDataList$: IDataArray;
myMethod() {
this.myDataList$.subscribe(myDataList => {
// Check something on myDataList
console.log('I will continuously loop');
this.addDataMethod();
}
}
addDataMethod() {
ngRedux.dispatch({type: addSomeDataAction, payload: myData});
}
Using ngRedux.getState()
would be the preferred access method, or limit the subscription with .take(1)
.
Cleaning up subscriptions
Subscriptions in Angular currently need to be unsubscribed in ngOnDestroy()
if not completed with a .take(n)
or similar operator. Without this step, the subscription continues after the component is gone.
Ref: Angular/RxJs When should I unsubscribe from `Subscription`
@select() myData$: IMyData;
private myDataSub;
myMethod() {
this.myDataSub = this.myData$.subscribe(myData => {
// Do something with myData
}
}
ngOnDestroy() {
this.myDataSub.unsubscribe();
}
Upvotes: 3