Angular state management using services built with ez-state

export interface EzState<T> {
value: T;
loading?: boolean;
loaded?: boolean;
loadError?: any;
saving?: boolean;
saved?: boolean;
saveError?: any;
updating?: boolean;
updated?: boolean;
updateError?: any;
deleting?: boolean;
deleted?: boolean;
deleteError?: any;
}
export class UsersService {
private usersCache = new EzCache<User[]>();
users$ = this.usersCache.value$; loading$ = this.usersCache.loading$; constructor(private http: MockHttpService) {} load() {
if (!this.usersCache.value) {
this.usersCache.load(this.http.get<User[]>('user'));
}
}
}
@Component({
selector: 'app-users',
templateUrl: './users.component.html',
styleUrls: ['./users.component.css'],
})
export class UsersComponent {
users$ = this.usersService.users$;
loading$ = this.usersService.loading$; constructor(private usersService: UsersService) {
usersService.load();
}
}
<h1>Users</h1><app-spinner *ngIf="loading$ | async; else loaded">loading</app-spinner><ng-template #loaded>
<table *ngIf="users$ | async as users">
<thead>
<tr *ngIf="users.length">
<th>Name</th>
<th>Email</th>
</tr>
<tr *ngIf="users.length === 0">
<th>No users</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let user of users">
<td>{{ user.firstname }} {{ user.lastname }}</td>
<td>{{ user.email }}</td>
</tr>
</tbody>
</table>
</ng-template>
delete(user: User) {
this.usersCache.delete(
this.http
.delete(`user/${user.id}`)
.pipe(
map((_) =>
this.usersCache.value.filter((u) => u.id !== user.id))
)
);
}
delete(user: User) {
this.usersService.delete(user);
}
<td><button (click)="delete(user)">delete</button></td>
saving$ = this.usersCache.saving$;saved$ = this.usersCache.saved$;error$ = this.usersCache.error$;
save(user: User) {
if (user.id) {
this.usersCache.save(
this.http
.put<User>(`user/${user.id}`, user)
.pipe(
map((_) =>
this.usersCache.value.map(
(u) => (u.id === user.id ? user : u)
)
)
)
);
} else {
this.usersCache.save(
this.http
.post<User>('user', user)
.pipe(
map((id) =>
[...this.usersCache.value, { ...user, id: id }]
)
)
);
}
}
resetState() {
this.usersCache.setState();
}
@Injectable({
providedIn: 'root',
})
export class RefDataService {
private observables: { [key: string]: Observable<SelectItem[]> } = {};
constructor(private http: MockHttpService) {} get titles$(): Observable<SelectItem[]> {
return (
this.observables.titles$ ||
this.create('titles$', this.http.get<SelectItem[]>('titles'))
);
}
private create(
property: string,
source$: Observable<SelectItem[]>
) {
const cache = new EzCache<SelectItem[]>();
cache.load(source$);
return (this.observables[property] = cache.value$);
}
}
@Component({
selector: 'app-user',
templateUrl: './user.component.html',
styleUrls: ['./user.component.css'],
})
export class UserComponent {
user$ = combineLatest([
this.usersService.users$,
this.route.paramMap.pipe(map((p) => p.get('id'))),
]).pipe(
map(([users, id]) =>
id
? users.find((u) => u.id === parseInt(id))
: {
id: null,
title: null,
firstname: '',
lastname: '',
email: '',
}
)
);
saving$ = this.usersService.saving$; saved$ = this.usersService.saved$; error$ = this.usersService.error$; titles$ = this.refDataService.titles$; constructor(
private usersService: UsersService,
private refDataService: RefDataService,
private route: ActivatedRoute
) {
usersService.resetState();
}
save(user: User) {
this.usersService.save(user);
}
}
<h1>User</h1><ng-container *ngIf="!(saved$ | async); else saved">
<div class="error" *ngIf="error$ | async as error">
{{ error }}
</div>
<form *ngIf="user$ | async | clone as user" (submit)="save(user)">
<label for="title">Title</label><br />
<select id="title" name="title" [(ngModel)]="user.title">
<option [ngValue]="null">Please select</option>
<option *ngFor="let title of titles$ | async"
[value]="title.value">
{{ title.label }}
</option>
</select><br />
<label for="firstName">Firstname</label><br />
<input
type="text"
id="firstname"
name="firstname"
[(ngModel)]="user.firstname" /><br />
<label for="lastName">Lastname</label><br />
<input
type="text"
id="lastname"
name="lastname"
[(ngModel)]="user.lastname" /><br />
<label for="email">Email</label><br />
<input
type="text"
id="email"
name="email"
[(ngModel)]="user.email" /><br />
<app-spinner *ngIf="saving$ | async; else saveButton">
saving
</app-spinner>
<ng-template #saveButton>
<button>Save</button>
</ng-template>
</form>
<a routerLink="/users">Cancel</a>
</ng-container>
<ng-template #saved>
Update successful <a routerLink="/users">Return to users</a>
</ng-template>

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store