Concepts and best practices
Component shell and folder
@Inputbindings are setters@Outputbindings 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
*rxLetover*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
@Inputbindings
@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
@Outputbindings@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))
})
)