ngxtension / ngxtension-platform

Utilities for Angular
https://ngxtension.netlify.app/
MIT License
529 stars 77 forks source link

rxjs operator proposal: indicateLoading #244

Open zip-fa opened 5 months ago

zip-fa commented 5 months ago

Hi. I often use this simple pipe in my projects to indicate loading state (where i don't need to use *-query or *store libraries):

import type { WritableSignal } from '@angular/core';
import type { Observable } from 'rxjs';
import { defer, finalize } from 'rxjs';

export function prepare<T>(callback: () => void): (source: Observable<T>) => Observable<T> {
  return (source: Observable<T>): Observable<T> => defer(() => {
    callback();

    return source;
  });
}

// any could be better typed here :)
export function indicateLoading<T>(
  indicator: WritableSignal<any>,
  triggerValue: any = true
): (source: Observable<T>) => Observable<T> {
  return (source: Observable<T>): Observable<T> => source.pipe(
    prepare(() => indicator.set(triggerValue)),
    finalize(() => {
      const nullishValue = typeof triggerValue === 'boolean' ? false : null;

      indicator.set(nullishValue);
    })
  );
}

usage:

isLoadingTest = signal<boolean>(true);
isEditingEntity = signal<number | null>(null);

ngOnInit(): void {
this.httpClient.get('/test').pipe(indicateLoading(this.isLoading)).subscribe();
this.httpClient.patch('/entity/1', { ... }).pipe(indicateLoading(this.isEditingEntity, 1)).subscribe();
}
@if (isEditingEntity() === 1) {
patching entity 1...
}

@if (isLoading()) {
loading content...
}
LcsGa commented 5 months ago

Hi, I spotted a few things here:

  1. You could use a tap instead of prepare, as it takes a TapObserver that has a subscribe notification callback that can be used exactly like your home made operator
  2. Instead of (source: Observable<T>) => Observable<T> you can type it MonoTypeOperatorFunction<T> and as the return type is already well set, you do not need to repeat yourself in the returns like return (source: Observable<T>): Observable<T> => defer(() => { => this is enough: return (source) => defer(() => {
  3. The goal of triggerValue is not really clear, what is it for?

About the indicateLoading itself, I would personnaly not use it as I code declaratively and in that case there are better options for that but it may be really well received by many others so why not