The Aurelia Store plugin is a great way to add powerful and easy to use state management into your Aurelia applications. For some, the paradigm shift in how you work can be confusing to work with at first. One such task you might find yourself needing to do is obtaining a singular value from the state.
As you will soon discover, state management in Aurelia Store requires you to subscribe to the entire state and react to all changes. Sometimes you just want to get a single value from the state to reference inside of your view-models.
While I cannot guarantee Vildan (Aurelia core team member and author of Aurelia Store) will approve of my solution, it works 😂
The solution I came up with which fits my needs perfectly is a method to subscribe, get one or more values from the state and then unsubscribe.
export const getStatePropertyOnce = async (...properties: string[]) => { return new Promise(async (resolve, reject) => { const subscription = store.state.pipe(pluck(...properties)).subscribe( (value) => { resolve(value); }, (error) => { reject(error); } ); subscription.unsubscribe(); }); };
The beautiful thing about this method is it not only supports top-level properties, but it also works for nested properties in your state object as well. In a real use case, I have the locality value of the user’s client stored in the state.
async getAppClient() { this.appClient = await getStatePropertyOnce('appClient'); }
There might be some pitfalls I have yet to encounter with the above functionality and you should also make sure your await
is wrapped in a try/catch
block as well to handle errors, but it works for my needs. If the thought of having to use @connectTo
or manually setup a subscriber and then dispose of it within your view-models sounds like a lot of work, this helper method can be a good time saver.
async getValueFromStore() { // Object in state is represented as user.details.email this.someValue = await getStatePropertyOnce('user', 'details', 'email'); }
In the above example, we are using the method to get a property of email
which exists on an object called details
which lives inside of a main user
object. The method handles getting nested properties with ease.
You can use rx first (https://www.learnrxjs.io/operators/filtering/first.html) for this use case, as it gets the first value and automatically subscribes, eg:
this.store.state.pipe(first()).subscribe(action)
We don’t actually use connectTo at all or subscribe directly to the store. We create a class called StateSubscriber that provides a bunch of methods that use various rx operators in ways which make sense for our state structure.
For the example in your article, using typescript keysof operator you can also ensure that the properties exist on both the viewmodel and the state:
set(…keys: (keyof U & keyof T)[]) {
return this.observable.pipe(distinctUntilKeysChanged(…keys))
.subscribe(x => keys.forEach(y => (this.viewModel as any)[y] = x[y]));
}
Other methods include subscribe to only changes for a property and another for projecting an ordered array from an object (we tend to use objects with ids as keys for entities as it makes updating the state easier).
Mistake in comment, should say the first automatically unsubscribes
Jon said it perfectly right. Use operators like first or take(1) which are creating an observable which is going to complete automatically afterwards. Saves you the unsubscribe call and reads much nicer in my opinion.
Whether your approach at all is right/wrong as always depends on the use case but implementation-wise its absolutely fine.
@Jon
Amazing. Thank you for sharing. I am still very much an RxJS newbie, but I love it. Aurelia Store is my first and only introduction to RxJS, I really need to learn it better. Do you have any code publicly available what you have done? It sounds like a good way to go about state changes. Or better yet, creating a custom decorator that adds these methods to a class something like `@useState`.
Knowing RxJS has some methods that automatically unsubscribe is huge. Thanks for that.
@Vildan
Those auto-unsubscribe methods are amazing. The approach works, but now I see it can definitely be improved.