Component Store
MiniRx supports "local" state management with Component Store. Component Store allows you to manage state independently of the global state object (which is used by Store and Feature Store).
Key Principles of Component Store
- Component Store has the same simple API as Feature Store
- Component Store state is independent of the global state object
- Component Store is destroyable
Use-cases
- Local component state:
- State which is bound to a component
- State which has the lifespan of a component
- State which can exist multiple times (if the corresponding component exists multiple times)
- Frequent create/destroy: Creating and destroying Component Stores is fast
- Very frequent state changes could lead to performance issues when using
Store
orFeatureStore
(both update the global state object using actions and reducers, which means more overhead)
Component Store is great for the mentioned use-cases. However, in most other cases you will be better off using MiniRx Feature Store:
- Better debugging experience: Inspect Feature Store state with Redux DevTools
- Feature Store state can be more easily shared with other interested components/services (with
store.select()
) - Feature Store automatically uses the Store extensions (provided via
configureStore
orStoreModule.forRoot
in Angular). - It is even possible to manage local state with Feature Stores (see Local Component State with Feature Store).
But don't worry, your Component Store can be easily migrated to a Feature Store and vice versa!
What's Included
The MiniRx ComponentStore
API:
setState()
update the statesetInitialState()
initialize state lazilyselect()
select state as RxJS Observableeffect()
run side effects like API calls and update stateundo()
easily undo setState actions (requires the UndoExtension)destroy()
clean up all internal Observable subscriptions (e.g. from effects)tapResponse
operator: handle the response in Component Storeeffect
consistently and with less boilerplate
Since the API of ComponentStore
is identical to FeatureStore
, please refer to the
Feature Store docs for more details.
Create a Component Store
There are 2 Options to create a new Component Store.
Option 1: Extend ComponentStore
import { Observable } from 'rxjs';
import { ComponentStore } from 'mini-rx-store';
interface CounterState {
count: number;
}
const initialState: CounterState = {
count: 42,
};
export class CounterStore extends ComponentStore<CounterState> {
count$: Observable<number> = this.select((state) => state.count);
constructor() {
super(initialState);
}
increment() {
this.setState(state => ({ count: state.count + 1 }));
}
decrement() {
this.setState(state => ({ count: state.count - 1 }));
}
}
Option 2: Functional creation method
We can create a Component Store with createComponentStore
.
import { ComponentStore, createComponentStore } from 'mini-rx-store';
const counterCs: ComponentStore<CounterState> = createComponentStore<CounterState>(initialState);
Destroy
If you manage local component state with Component Store..., please make sure to destroy the Component Store when the corresponding component is destroyed!
You can destroy a Component Store with the destroy
method. The destroy
method will unsubscribe all internal RxJS subscriptions (e.g. from effects).
The Component Store destroy
method follows the same principles as the destroy
method of Feature Store. Read more in the Feature Store "Destroy" docs.
Extensions
You can use most of the MiniRx extensions with the Component Store.
Extensions with Component Store support:
- Immutable Extension: Enforce state immutability
- Undo Extension: Undo state changes from
setState
- Logger Extension: console.log the current "setState" action and updated state
It's possible to configure the Component Store extensions globally or individually for each Component Store instance.
Global extensions setup
Configure extensions globally for every Component Store with the configureComponentStores
function:
import {
configureComponentStores,
} from 'mini-rx-store';
configureComponentStores({
extensions: [new ImmutableStateExtension()]
});
Now every Component Store instance will have the ImmutableStateExtension.
Local extensions setup
Configure extensions individually via the Component Store configuration object:
import { ComponentStore, LoggerExtension } from 'mini-rx-store';
export class CounterStore extends ComponentStore<CounterState> {
constructor() {
super(initialState, {
extensions: [new LoggerExtension()]
});
}
}
"Local" extensions are merged with the (global) extensions from configureComponentStores
.
Therefore, every CounterStore
instance will have the LoggerExtension (from the local extension setup) and the
ImmutableStateExtension (from the configureComponentStores
extensions).
If an extension is defined globally and locally, then only the local extension is used.
It makes sense to add the ImmutableStateExtension to configureComponentStores
("Global extensions setup").
Like this, every Component Store can benefit from immutable state.
The LoggerExtension can be added to individual Component Stores for debugging purposes ("Local extensions setup").
Regarding the undo
API: It is recommended to add the UndoExtension to the Component Stores which need the undo functionality ("Local extensions setup").
Memoized selectors
Of course, you can use memoized selectors also with Component Store!
createComponentStateSelector
You can use createComponentStateSelector
together with createSelector
to create your selector functions.
Example:
// Memoized Selectors
const getComponentState = createComponentStateSelector<TodoState>();
const getTodos = createSelector(
getComponentState,
state => state.todos
);
const getSelectedTodoId = createSelector(
getComponentState,
state => state.selectedTodoId
)
const getSelectedTodo = createSelector(
getTodos,
getSelectedTodoId,
(todos, id) => todos.find(item => item.id === id)
)
class TodoStore extends ComponentStore<TodoState> {
// State Observables
todoState$: Observable<TodoState> = this.select(getComponentState);
todos$: Observable<Todo[]> = this.select(getTodos);
selectedTodo$: Observable<Todo> = this.select(getSelectedTodo);
constructor() {
super(initialState)
}
}