January 19, 2038
Goal of the project was to replace an existing Java frontend application with a modern, web-based solution approach to fulfill changed requirements and get rid of some technical debt. This allowed us to focus a lot on the technical side, as many features stayed the same. Having a working solution as a blueprint to see what parts might be implemented differently was quite an advantage.
Content of the State
One of the most important questions we had to answer was: what to actually store in the Redux state? We started with a reporting application that allowed so set some filters and show some charts based on them. We stored everything in the state, beginning at the filters that could be set (which where retrieved from the server on application start), in combination with the values that had been entered for them and according autocompletions. And of course the actual chart data being displayed.
What Should be Part of the State
Form states? maybe, maybe not Results of HTTP requests
What Shouldn’t be Part of the State
Internal component state.
How to Organize it
Driven by domain concepts. Interaction only using actions or selectors.
Action Naming
As actions are the only way to modify the state, some thought should be put into their naming. If it’s not obvious, what the semantics of an action are, it’ll get confusing quickly and might result in undesirable things like duplicated or misused actions. Some characteristics that turned out to be helpful, are:
- Present Tense for Commands
- For actions that carry the intent to do something, they should be phrased
in a present tense and imperative style like
loadSomeDataorclearSearchResults. Command actions are often dispatched as a result of a user interaction or during component initialization. - Past Tense for Events
- For actions that carry the information something has already happened,
they should be phrased in a past tense style like
someDataLoadedoruserProfileSaved. Event actions are used mostly for the result of HTTP requests or when a state relevant URL change happens. - Uniqueness
- Any action name should be unique within the project. Think of an action named
loadDatadefined multiple times, e.g. in a “Product” context and in a “ShoppingCart” context. Each time to load the respective data from the server. Depending on where the action is imported from, it might now either load some product list or the users current shopping cart. use more concrete names instead, likeloadProductsandloadShoppingCart. - Same Name for Same Intent
- To make similar intent easily recognizable, use naming pattern for reoccurring objectives.
For example, an action that will directly result in an HTTP call might be called
loadSomeData, whereas the action that’s being dispatched on a successful response, could be namedsomeDataLoaded. So whenever you see theloadorloadedin an action name, it’s obvious, there’s a remote call going on.
Component Design
For our applications components, we decided to stick with a separation
into container and presentational components.
The container components would then connect to the NGRX store, pass it’s data down to the presentational components and dispatch actions based on user interaction within these.
The presentational components aren’t aware of the NGRX store and communicate with the rest of the application only via Angulars @Input() and @Output() bindings.
By diving into these two types, it should be easier to navigate and search through the code, as the number of components that interact with the store is much smaller than it would otherwise be. Also it improves reusability of presentational components, as they aren’t in control of the data they use.
It should be noted though, that not all presentational components have to be reusable. In our adminstrative application for example, we have for every entity it’s own container components, but also specialized presentational components that will display a certain part of it’s information and also allow editing the same. This is also a good example to show that presentational components can also include container components as children. So for adding new relations to an existing entity (e.g. users to a group), there might be a dialog being called from within a presentational component that shows a list of the current users. As adding new users typically includes searching for users, is should access the state directly instead of routing everything through the presentational component and it’s parent container. This is also one of the few use cases we have identified yet for passing data into a container component aside form via the state.
Think About URL as Part of the State
An important feature of NGRX it’s binding to the Angular Router
that can be achieved by using the @ngrx/router-store dependency.
It doesn’t change the way how navigation within the application is achieved,
but allows to access route parameters or query parameters as part of the state using selectors.
what should remain after reload? Use to fetch additional data from the server dont access route directly, only via selectors
Be Careful Using LocalStorage
Don’t can often be replaced by URL
Be Open to Change
Actions aren’t persisted. There is nothing stopping from a large change necessary for new requirements.
Spread Knowledge
Document design concepts Talk about concepts, new or changed by refactoring The whole team should know how things work