/loadoff

🤯 When it comes to loaders, take a load off your mind...

Primary LanguageTypeScriptMIT LicenseMIT


MIT commitizen PRs styled with prettier All Contributors ngneat spectator

When it comes to loaders, take a load off your mind...

Installation

npm install @ngneat/loadoff

Create a Loader

To create a loader, call the loadingFor function and specify the loaders you want to create:

import { loadingFor } from '@ngneat/loadoff';

@Component({
  template: `
    <button>
      Add
      <spinner *ngIf="loader.add.inProgress$ | async"></spinner>
    </button>
    
    <button>
      Edit 
      <spinner *ngIf="loader.edit.inProgress$ | async"></spinner>
    </button>
    
    <button>
      Delete 
      <spinner *ngIf="loader.delete.inProgress$ | async"></spinner>
    </button>
  `
})
class UsersTableComponent {
  loader = loadingFor('add', 'edit', 'delete');

  add() {
    this.service.add().pipe(
      this.loader.add.track()
    ).subscribe();
  }

  edit() {
    this.service.add().pipe(
      this.loader.edit.track()
    ).subscribe();
  }

  delete() {
    this.service.add().pipe(
      this.loader.delete.track()
    ).subscribe();
  }
}

Async State

AsyncState provides a nice abstraction over async observables. You can use the toAsyncState operator to create an AsyncState instance which exposes a loading, error, and res state:

import { AsyncState, toAsyncState } from '@ngneat/loadoff';

@Component({
  template: `
    <ng-container *ngIf="users$ | async; let state">
      <p *ngIf="state.loading">Loading....</p>
      <p *ngIf="state.error">Error</p>
      <p *ngIf="state.res">
        {{ state.res | json }}
      </p>
    </ng-container>
  `
})
class UsersComponent {
  users$: Observable<AsyncState<Users>>;

  ngOnInit() {
    this.users$ = this.http.get<Users>('/users').pipe(
      toAsyncState()
    );
  }

}

You can also use the *subscribe directive instead of *ngIf.

createAsyncState

You can use the createAsyncState to manually create an instance of AsyncState:

import { createAsyncState } from '@ngneat/loadoff';


class UsersComponent {
  state = createAsyncState()
}

The initial state of AsyncState instance is:

{
  error: undefined,
  res: undefined,
  loading: true,
  complete: false,
  success: false,
};

You can always override it by passing a partial object to the createAsyncState function:

import { createAsyncState } from '@ngneat/loadoff';


class UsersComponent {
  state = createAsyncState({ loading: false, complete: true, res: data })
}

createSyncState

Sometimes there could be a more complex situation when you want to return a sync state which means setting the loading to false and complete to true:

import { createSyncState, toAsyncState } from '@ngneat/loadoff';


class UsersComponent {
  ngOnInit() {
    source$.pipe(
      switchMap((condition) => {
        if(condition) {
          return of(createSyncState(data));
        }

        return inner$.pipe(toAsyncState())
      })
    )
  }
}

Helper Functions

import { isSuccess, hasError, isComplete, isLoading } from '@ngneat/loadoff';

class UsersComponent {
  loading$ = combineLatest([asyncState, asyncState]).pipe(someLoading())

  ngOnInit() {
    this.http.get<Users>('/users').pipe(
      toAsyncState()
    ).subscribe(res => {
      if(isSuccess(res)) {}
      if(hasError(res)) {}
      if(isComplete(res)) {}
      if(isLoading(res)) {}
    })
  }

}

retainResponse

Sometimes you want to retain the response while fetching a new value. This can be achieved with the retainResponse operator.

import { toAsyncState, retainResponse } from '@ngneat/loadoff';

@Component({
  template: `
    <ng-container *ngIf="users$ | async; let state">
      <p *ngIf="state.loading">Loading....</p>
      <p *ngIf="state.error">Error</p>
      <p *ngIf="state.res">
        {{ state.res | json }}
      </p>
    </ng-container>
    <button (click)='refresh$.next(true)'>Refresh</button>
  `
})
class UsersComponent {
  users$: Observable<AsyncState<Users>>;
  refresh$ = new BehaviorSubject<boolean>(true);

  ngOnInit() {
    this.users$ = this.refresh$.pipe(
      switchMap(() => this.http.get<Users>('/users').pipe(toAsyncState())),
      retainResponse()
    );
  }
}

The retainResponse operator accepts an optional startWithValue parameter which you can use to initialize the stream with an alternative AsyncState value.

Async Storage State

AsyncStore provides the same functionality as AsyncState, with the added ability of being writable:

import { AsyncState, createAsyncStore } from '@ngneat/loadoff';

@Component({
  template: `
    <ng-container *ngIf="store.value$ | async; let state">
      <p *ngIf="state.loading">Loading....</p>
      <p *ngIf="state.error">Error</p>
      <p *ngIf="state.res">
        {{ state.res | json }}
      </p>
    </ng-container>
    
    <button (click)="updateUsers()">Update Users</button>
  `
})
class UsersComponent {
  store = createAsyncStore<Users>();

  ngOnInit() {
    this.users$ = this.http.get<Users>('/users').pipe(
      this.store.track()
    );
  }
  
  updateUsers() {
    this.store.update((users) => {
      return [];
    });
  }

}

Contributors ✨

Thanks goes to these wonderful people (emoji key):


Netanel Basal

📖 🤔 ⚠️

This project follows the all-contributors specification. Contributions of any kind welcome!

Icons made by Freepik from www.flaticon.com