RFC: Add "ngrxOnInitStore" lifecycle method to ComponentStore #3335
Replies: 7 comments 10 replies
-
There are two parts to the potential of this feature. A) The option to have initialization logic outside of the constructor. This can increase testability as we can control both initialization and teardown in automated tests. Perhaps it can assist with some timing issues as well. B) I was looking for a pattern to trigger an effect once, usually without arguments. I didn't want to call the effect in the constructor. I was looking to hook into an observable provided by ComponentStore, say ComponentStore#ngrxOnInit$. It seems that we might use ComponentStore#state$ for this but we have to remember to add a first() or take(1) operator and it's not immediately clear what's going on. Alternatively, I think @nartc has the most beautiful solution for parameterless effects: |
Beta Was this translation helpful? Give feedback.
-
It seems to me that component store is intended to be for single component local state, but the ability to provide it somewhere other than the component (e.g. at a feature level or root level) is a big plus - I also see component store described as a sort of "service with a subject on steroids" so I think it would make sense to support situations where we are dealing with shared state. That seems to be where the need for this init method arose for me. If I am just dealing with state for one component, then I don't see any issue with just using the components own ngOnInit for that. But with multiple components sharing the same store at a feature level that was a bit awkward. The solution I ended up deciding on was having each component be responsible for triggering the init effect in the store, and have the init effect in the store use the This is fine I think - any component that wants to use the store needs to initialise it - but it is more fragile/difficult than if we did have the init method in the store. The solution as you propose Brandon would work perfectly for my situation: export class MyStore extends ComponentStore<T> implements OnInitStore {
loadClients = this.effect(($) =>
$.pipe(
switchMap(() =>
this.clientsService.getClients().pipe(
tap({
next: (clients) => this.patchState({ clients }),
})
)
)
)
);
constructor() {
super();
}
ngrxOnInitStore() {
this.loadClients();
}
} Also, maybe a wacky idea but this just popped into my head, would you imagine this init hook would only ever be used for triggering effects? I have no idea how feasible this would be but would it be possible/desirable to be able to supply an array of effects to trigger on init, rather than having an Maybe something like: constructor() {
super({clients: []}, [this.loadClients])
} |
Beta Was this translation helpful? Give feedback.
-
Additional thoughts on timing of when the lifecycle method is called. I'm thinking it would listen to the second emission from the internal state$ observable(the first emission is a 1), check for the existence of the lifecycle method, and call it. We could separate this into two methods, but the first time state is set it's considered initialized also so I'm not sure we need separate ones there. It depends on if we want to wait for the state to be initialized, or directly after instantiation. If you wanted to init the state lazily there may be the expectation that the method still fires predictably right at the end of the constructor Constructor(init state) -> onInit export class MyStore extends ComponentStore<T> implements OnInitStore {
constructor() {
super(initialState);
}
ngrxOnInitStore() {
// init logic here is called immediately after constructor
}
} Constructor(no state) -> onInit export class MyStore extends ComponentStore<T> implements OnInitStore {
constructor() {
super();
}
ngrxOnInitStore() {
// init logic here is called immediately after constructor
}
} Constructor(no state) -> set state -> onInit export class MyStore extends ComponentStore<T> implements OnInitStore {
constructor() {
super(i);
}
doSomething(initialState) {
this.setState(initialState);
}
ngrxOnInitStore() {
// init logic here is called after first setState()
}
}
@Component({...})
export class MyComponet {
constructor(private myStore: MyStore) {}
ngOnInit() {
this.myStore.doSomething(this.initialState);
}
} |
Beta Was this translation helpful? Give feedback.
-
I was going to suggest the same as @markostanimirovic to keep it as flexible as possible. |
Beta Was this translation helpful? Give feedback.
-
@brandonroberts is there any status on this RFC? |
Beta Was this translation helpful? Give feedback.
-
Draft PR is up #3368 |
Beta Was this translation helpful? Give feedback.
-
An important aspect is finding a way to allow us to control the hook in component store tests. |
Beta Was this translation helpful? Give feedback.
-
The discussion originated from a Discord conversation with @LayZeeDK:
https://discord.com/channels/740557383109050469/740560647967866912/930529968574824518
Also has originated on Twitter also:
https://twitter.com/joshuamorony/status/1499598374269456385
The pattern is that you want to perform some init logic inside a component store. This can be done inside the constructor of the ComponentStore, but is generally discouraged as a place to put init logic. It can also be done by listening to the
state$
property on the ComponentStore for the first emission and performing init logic there.The proposed idea is to add an interface, potentially named
OnInitStore
and angrxOnInitStore
lifecycle method that would be called after the ComponentStore is instantiated, or potentially after the ComponentStore has been initialized which could be done eagerly or lazily.We have some prior art in Effects with the
OnInitEffects
interface andngrxOnInitEffects
lifecycle method.https://ngrx.io/guide/effects/lifecycle#oniniteffects
Beta Was this translation helpful? Give feedback.
All reactions