Style Guide
Below are suggestions for naming and style conventions.
A state should always be suffixed with the word
State
. Prefer: ZooState
Avoid: Zoo
States should have a
.state.ts
suffix for the filenameState interfaces should be named the name of the state followed by the
Model
suffix. If my state were called ZooState
, we would call my state interface ZooStateModel
.Selects should have a
$
suffix. Prefer: animals$
Avoid: animals
Plugins should end with the
Plugin
suffixPlugins file names should end with
.plugin.ts
Global states should be organized under
src/shared/state
. Feature states should live within the respective feature folder structure src/app/my-feature
. Actions can live within the state file but are recommended to be a separate file like: zoo.actions.ts
Actions should NOT have a suffix
Unit tests for the state should be named
my-state-name.state.spec.ts
Actions should NOT deal with view related operations (i.e. showing popups/etc). Use the action stream to handle these types of operations
The objects stored in your state should be immutable and should support serialization and deserialization. It is therefore recommended to store pure object literals in your state. Class based instances are not trivial to serialize and deserialize, and also are generally focused on encapsulating internals and mutating internal state through exposed operations. This does not match the requirement for the data stored in state.
This also applies to the usage of data collections such as Set, Map, WeakMap, WeakSet, etc. Since they are not amenable to deserialization and cannot easily be presented for normalization.
export class Todo {
constructor(public title: string, public isCompleted = false) {}
}
@State<Todo[]>({
name: 'todos',
defaults: []
})
@Injectable()
class TodosState {
@Action(AddTodo)
add(ctx: StateContext<Todo[]>, action: AddTodo): void {
// Avoid new Todo(title)
ctx.setState((state: Todo[]) => state.concat(new Todo(action.title)));
}
}
@Component({
selector: 'app',
template: `
<ng-container *ngFor="let todo of todos$ | async">
{{ todo.isCompleted }}
</ng-container>
`
})
class AppComponent {
@Select(TodosState) todos$: Observable<Todo[]>;
}
It is not recommended to add Class based object instances to your state because this can lead to undefined behavior in the future.
export interface TodoModel {
title: string;
isCompleted: boolean;
}
@State<TodoModel[]>({
name: 'todos',
defaults: []
})
@Injectable()
class TodosState {
@Action(AddTodo)
add(ctx: StateContext<TodoModel[]>, action: AddTodo): void {
ctx.setState((state: TodoModel[]) =>
state.concat({ title: action.title, isCompleted: false })
);
}
}
@Component({
selector: 'app',
template: `
<ng-container *ngFor="let todo of todos$ | async">
{{ todo.isCompleted }}
</ng-container>
`
})
class AppComponent {
@Select(TodosState) todos$: Observable<Todo[]>;
}
The general recommendation for handling hierarchical data in Redux is to normalise it. This would entail flattening it in the same way that you would design relational tables, having keys for references to parent objects.
export interface RowStateModel {
id: number;
}
export interface GridStateModel {
id: number;
rows: Map<number, RowState>;
}
export interface GridCollectionStateModel {
grids: Map<number, GridState>;
}
@State<RowStateModel>({
name: 'row',
defaults: {
id: -1
}
})
@Injectable()
export class RowState {}
@State<GridStateModel>({
name: 'grid',
defaults: {
id: -1,
rows: new Map<number, RowState>()
}
})
@Injectable()
export class GridState {}
@State<GridCollectionStateModel>({
name: 'grid-collection',
defaults: {
grids: new Map<number, GridState>()
}
})
@Injectable()
export class GridCollectionState {}
Note: It is not recommended to use data collections such as Set, Map, WeakMap, WeakSet, etc. Since they are not amenable to deserialization and cannot easily be presented for normalization.
export interface RowStateModel {
id: number;
}
export interface GridStateModel {
id: number;
rows: {
[id: number]: RowStateModel;
};
}
export interface GridCollectionStateModel {
grids: {
[id: number]: GridStateModel;
};
}
@State<RowStateModel>({
name: 'row',
defaults: {
id: -1
}
})
@Injectable()
export class RowState {}
@State<GridStateModel>({
name: 'grid',
defaults: {
id: -1,
rows: {}
}
})
@Injectable()
export class GridState {}
@State<GridCollectionStateModel>({
name: 'grid-collection',
defaults: {
grids: {}
}
})
@Injectable()
export class GridCollectionState {}
Last modified 1yr ago