createAsyncState

Imagine you're tasked with building a user list in your UI. Traditionally, this might involve defining variables like loading, error, and users, then assigning values within your API subscribe method. However, as your page requires multiple API requests, managing multiple loading and error variables becomes challenging, leading to scattered variables throughout your component. This makes maintenance cumbersome and lacks cohesion with your subscribe method.

The createAsyncState operator is a handy RxJS operator for simplifying the handling of asynchronous operations. It is meticulously crafted to encapsulate API responses within a standardized structure, streamlining the process of subscribing to observables. With this approach, integrating data seamlessly into HTML components becomes not only convenient but also ensures a more organized and maintainable codebase.

This operator transforms an observable of type T into an observable of AsyncState<T>. The AsyncState object encapsulates the following key states:

  • loading: Indicates that the asynchronous operation is in progress.
  • error: Represents any encountered errors during the operation.
  • data: Holds the successful result of the operation.
export interface AsyncState<T, E = HttpErrorResponse> {
  loading: boolean;
  error: E | null;
  data: T | null;
}

The createAsyncState operator accepts 2 parameters. The first optional parameter, observerOrNextForOrigin?: Partial<TapObserver<T>> | ((value: T) => void), has the same type as RxJS tap. This allows you to pass callbacks for success and error scenarios if needed. The second optional initialValue parameter specifies the initial state of the asynchronous operation. The default initialValue is { loading: true, error: null, data: null }, meaning that the observable starts in a loading state.

import {createAsyncState} from 'ngx-lift';

this.userService.getUsers().pipe(
  createAsyncState({
    next: (res) => console.log(res), // success callback
    error: (error) => console.error(error), // error callback
  }),
).subscribe();

By using this operator, you can easily manage and respond to these different states in your application. The resulting observable emits AsyncState objects, allowing you to handle loading spinners, display error messages, and render content based on the received data.

import {createAsyncState} from 'ngx-lift';
// ... other imports

@Component({
  template: `
    <ng-container *ngIf="usersState$ | async as usersState">
      <cll-spinner *ngIf="usersState.loading"></cll-spinner>

      <cll-alert *ngIf="usersState.error as error" [error]="error"></cll-alert>

      <div class="card-grid" *ngIf="usersState.data as users">
        <app-user-card *ngFor="let user of users" [user]="user"></app-user-card>
      </div>
    </ng-container>
  `,
})
export class UserCardListComponent {
  usersState$ = inject(UserService).getUsers({results: 9}).pipe(createAsyncState());
}
thumbnail
Name
درسا نجاتی
Gender
female
thumbnail
Name
Tverdislava Ternoviy
Gender
female
thumbnail
Name
مهرسا محمدخان
Gender
female
thumbnail
Name
Ellie Neal
Gender
female
thumbnail
Name
Chloe Wang
Gender
female
thumbnail
Name
Ece Berberoğlu
Gender
female
thumbnail
Name
Eren Tokatlıoğlu
Gender
male
thumbnail
Name
Emilia Kauppi
Gender
female
thumbnail
Name
Riley Johnson
Gender
female

Provide initialValue

Suppose there is a users table which contains comprehensive user information. When you click on a specific user within this table, it will direct you to the user detail page. During this navigation process, the user's information will be passed as route state. On the user detail page, you can initially showcase the data obtained from the state. Subsequently, you can initiate a call to the user API. Once the API call is completed successfully, the user information can be refreshed without having to display a loading spinner. You can set the initialValue in the following manner: {loading: false, error: null, data: this.location.getState()}. This configuration allows for a smooth presentation of user details and seamless updates when new data is fetched from the API, enhancing the overall user experience on the detail page.

import {createAsyncState} from 'ngx-lift';
import { Location } from '@angular/common';
import {noop} from 'rxjs';

import {User} from '../../models/user.model';

@Component({
  selector: 'app-user-detail',
  standalone: true,
  imports: [SpinnerComponent, AlertComponent],
  templateUrl: './user-detail.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserDetailComponent {
  private userService = inject(UserService);
  private location = inject(Location);

  userState$ = this.userService
    .getUserById(1)
    .pipe(createAsyncState<User>(noop, {loading: false, error: null, data: this.location.getState()}));
}