I often see code describing the state of the user interface that needs to be simplified.
Let's see code that outputs a list of users.
<ng-container *ngIf="isLoading && !error">Loading...</ng-container>
<ul *ngIf="users && users.length && !error">
<li *ngFor="let user of users">{{user.name}}</li>
</ul>
<ng-container *ngIf="!error && !loading && users && !users.length">Nothing found</ng-container>
<ng-container *ngIf="!isLoading && error">{{error.message}}</ng-container>
This code is just awful. It is difficult to read and maintain.
I prefer another way. I used to read about the theory of finite-state machines. The state machine has a finite set of states, and it is in one of these states at each moment.
We have four states of the user's list:
- Loading
- Users loaded
- Users were loaded with errors
- Users were not founded
Let's describe the state with a discriminated union.
type State =
| { status: 'loading' }
| { status: 'success', data: IUser[] }
| { status: 'failed', error: Error }
| { status: 'not-founded' }
Let's rewrite view logic.
<ng-container *ngIf="state.status === 'loading'">Loading...</ng-container>
<ul *ngIf="state.status === 'success'">
<li *ngFor="let user of state.data">{{user.name}}</li>
</ul>
<ng-container *ngIf="state.status === 'not-found'">Nothing found</ng-container>
<ng-container *ngIf="state.status === 'failed'">{{state.error.message}}</ng-container>
You can make the state type universal by using generics.
type State<TSuccessData> =
| { status: 'loading' }
| { status: 'success', data: TSuccessData }
| { status: 'failed', error: Error }
| { status: 'not-founded' }
type UsersListState = State<IUser[]>;
This code is more reading and self-documenting. Presently your IDE gives better hints for you.
Your team will be grateful to you.
P.S. Sorry for my English. It is my first article in English.
Top comments (0)