Concepts and best practices
Component shell and folder
@Input
bindings are setters@Output
bindings are state derivations- The state is injected over the constructor
- The state is displayed over a pipe in the template
- The UI interaction is implemented over
Subjects
- use
*rxLet
over*ngIf
Bad:
<ng-container *ngIf="obj$ | async as obj"> {{ obj }} </ng-container>
Good:
<ng-container *rxLet="obj$ as obj"> {{ obj }} </ng-container>
Component implementation approach
Defining the interface
In a first step you want to setup the state interface. A property that should change the view of your component should find its place in the interface.
View bindings and triggers, which in turn mutate your state, should be Subjects
.
In the best case, you keep your state normalized.
Derived state should be handled separately.
Example view interface:
interface MyState {
items: string[];
listExpanded: boolean;
sortKey: string;
isAsc: boolean;
}
interface MyView {
click$: Subject<MouseEvent>();
expanded$: boolean;
vm$: Observable<MyState>; // ViewModel
}
Setup view interactions
@Component({
selector: 'app-stateful-component',
template: ` <div>{{ vm$ | async | json }}</div> `,
changeDetection: ChangeDetection.OnPush,
providers: [RxState],
})
export class StatefulComponent implements MyView {
readonly vm$: Observable<MyState> = this.state.select();
readonly click$ = new Subject<MouseEvent>();
readonly expanded$ = this.click$.pipe(); // map it
@Input('items') set items(items: string[]) {
this.state.set({ items });
}
constructor(private state: RxState<MyState>) {}
}
- Hook up
@Input
bindings
@Input()
set task(task: Task) {
this.state.setState({task})
}
- Hook up UI state
vm$ = this.state.select();
<ng-container *rxLet="obj$; let obj"> {{obj}} </ng-container>
- Hook up UI interaction
<button (click)="btnReset$.next($event)">Reset</button>
Hook up
@Output
bindings@Output()
taskChanges = this.state.$.pipe(distinctUntilKeyChanged('prop'));
Observables and projection functions are named in a way that gives us information about the returned data structure.
Think in Source => transform => state/effect
Bad:
getSearchResults() => this.inputChange$.pipe(
switchMap((q) => fetchData(q))
)
this.inputChange$.pipe(
this.toSearchResult
)
toSearchResults(o) => o.pipe(
map((data) => {
switchMap((q) => fetchData(q))
})
)