In a single page application (SPA) like Aurelia, shared state is one of those important things every developer mostly encounters. Sharing the current state of a logged in user or sharing a shopping cart’s contents throughout the app.
Fortunately, Aurelia makes it easy to build applications with shared state.
By default a class in Aurelia is a singleton. If you create a service called UserService
and inject it throughout different parts of your app, provided you’re injecting it without telling Aurelia to do anything non-standard, it will be a singleton.
As you know objects in Javascript are passed by reference. This is what a singleton class is: a shared object. Any other class in your app that asks for it gets the same copy as every other part. If you change something, it changes everywhere.
The standard way to have shared state is singletons, but another is also a server-side data store or even using client-side storage mechanisms like; sessionstorage, localstorage and IndexedDB.
My personal choice for most apps I have used Aurelia for is a shared singleton service class. If you come from an Angular background, shared services are probably something you used often.
Although, there might be times when storing data using local storage might be a better choice, especially if you want to survive manual page reloads which the router does not catch or preventing multiple server requests.
A user service
Here is a basic example of a user service which handles getting user data and storing it (including the current logged in user).
export class UserService {
_currentUserLoggedIn = false;
_userStore = {};
_users = [];
getCurrentUser() {
return this._userStore;
}
findUser(username) {
// Do something server side and return the user
}
findUsers(criteria) {
// Will find one or more users and then return them
// They will also be stored on this class in the _users variable
}
}
Now if we inject this same class using the standard @inject(UserService)
decorator, into multiple classes in our app, they all get the same version. They get any populated class variables, the current user login status and more.
Some ideas for extending this with redundancy include using client-side storage mechanisms like local or session storage to survive page reloads and server requests.
Conclusion
There is very little to explain. A class by default in Aurelia is a singleton and unless you specify otherwise using a decorator like @transient()
then that is guaranteed.
As always if you need further explanation, want to discuss shared state strategies or correct an error: hit the comments section.
What exactly do you mean by “shared state?” I’m asking because it’s quite confusing what actually happens in RL. Take the following class:
import {inject} from ‘aurelia-framework’;
import {Router} from ‘aurelia-router’;
import {List} from ‘./list’;
@inject(Router, List)
export class Add {
constructor(router, list) {
this.pageTitle = “Add item”;
this.title = null;
this.description = null;
this.router = router;
this.list = list;
}
add() {
// Add item to parent list
this.list.addItem({
‘title’: this.title,
‘description’: this.description
});
// navigate to list
this.router.navigateToRoute(‘list’);
}
}
and the List class:
export class List {
items = [];
construct() {
this.pageTitle = “All items”;
}
addItem(item) {
this.items.push(item);
}
}
After going to the “add” state and adding an item, when getting back to the “list” state via the navigateToRoute method of the router, the items property is still empty, even though the addItem method is properly called and looks like the values are retained, but they are definitely not.
I have the same problem.
@Vali “construct” under “List” class should be “constructor” and I would put “items = [];” into constructor function with “this.items = [];”.
I think “items = [];” works with Typescript, and could work with certain transpiling options, but not sure.