2.0.0 • Published 12 months ago

ngx-action v2.0.0

Weekly downloads
-
License
MIT
Repository
github
Last release
12 months ago

npm downloads total npm downloads monthly

Description

Lightweight (around 1KB gzipped) library allowing to sync components, directives and services of Angular application.

Installation

npm install ngx-action
# or
yarn add ngx-action

Compatibility

Library version@angular/corerxjs
>=2.0.0>=16>=7 && < 8
>=1.2.2>=13>=7 && < 8
>=1.2.0 && <= 1.2.1>=13 && <17>=7 && < 8
\<1.2.0>=13 && <16>=7 && < 8

Usage

1. Create actions

Action must be a class, as the library uses the instanceof operator under the hood.

class SignOut {}
class DetailsSuccess {
  constructor(public payload: any) {}
}
class ListSuccess {
  constructor(public payload: any) {}
}
abstract class LoggableAction {}
class ShowErrorNotification extends LoggableAction {
  constructor(public text: string) {}
}

2. Subscribe to actions

2.1 Actions.onAction() method:

import { Actions } from "ngx-action";

Actions.onAction(DetailsSuccess, ListSuccess)
  .pipe(takeUntilDestroyed())
  .subscribe((action: DetailsSuccess | ListSuccess) => {
    console.log(action.payload);
  });

2.2 @ActionHandler() or @AsyncActionHandler() decorator:

import { signal } from "@angular/core";
import { BehaviorSubject, EMPTY } from 'rxjs';
import { ActionHandler, AsyncActionHandler, initActionHandlers } from 'ngx-action';

@Component(...) // or @Directive(...), or @Injectable()
export class MyComponent {
  payloadSignal = signal<any>(null);
  payload$ = new BehaviorSubject<any>(null);
  payload = null;

  constructor(
    private httpClient: HttpClient,
    private cdr: ChangeDetectorRef,
    private notifications: Notifications,
  ) {
    initActionHandlers(this); // <-- !!! call helper method to activate subscriptions
  }

  @ActionHandler(ShowErrorNotification) // handle action synchronously
  showErrorNotificationHandler(action: ShowErrorNotification): void {
    this.notifications.error(action.text);
  }

  @ActionHandler(LoggableAction) // handle abstract action
  loggableActionHandler(action: LoggableAction): void {
    console.log(action);
  }

  @ActionHandler(DetailsSuccess, ListSuccess) // handle multiple actions
  successHandler(action: DetailsSuccess | ListSuccess): void {
    // update signal value
    payloadSignal.set(action.payload);

    // update subject value
    this.payload$.next(action.payload);

    // update plain value - may require change detection triggering
    this.payload = action.payload;
    this.cdr.markForCheck();
  }

  @AsyncActionHandler(SignOut) // handle action asynchronously
  signOutHandler(handle$: Observable<SignOut>): Observable<unknown> {
    return handle$.pipe(
      mergeMap((action: SignOut) => {
        return this.httpClient.post('/sign-out').pipe(
          // optionally handle result
          tap((response) => {
            console.log(response);
          }),
          // don't forget to handle errors,
          // or action handler will be stopped after the first one occurs
          catchError((error) => {
            console.error(error);
            return EMPTY;
          }),
        );
      })
    );
  }
}

Decorators use takeUntilDestroyed() operator under the hood and will unsubscribe on destroy (all pending async actions, like http requests, will be cancelled).

3. Dispatch Actions

3.1 Actions.dispatch() method:

import { Actions } from "ngx-action";

Actions.dispatch(new SignOut());
Actions.dispatch(new DetailsSuccess("details"), new ListSuccess("list"));

3.2 rxjs operators:

dispatchOnSuccess - dispatch actions when source observable emits.

Actions.onAction(Action).pipe(
  mergeMap((action: Action) => this.httpCLient.get(...).pipe(
    dispatchOnSuccess((result) => new ActionSuccess(result.data)),
    // dispatchOnSuccess((result) => [new ActionSuccess(result.data), new ActionSuccess()]),
  )),
  takeUntilDestroyed(),
).subscribe();

dispatchOnError - dispatch actions when source observable throws an error. It won't rethrow the error, so use catchError instead if you want to add custom error handling.

Actions.onAction(Action).pipe(
  mergeMap((action: Action) => this.httpCLient.get(...).pipe(
    dispatchOnError((error: unknown) => new LogError(error)),
    // dispatchOnError((result) => [new LogError(error), new LogError(error)]),
  )),
  takeUntilDestroyed(),
).subscribe();

Techniques

1. Prevent services tree-shaking

If you extract some action handlers into services, and don't inject those services anywhere in the app, Angular compiler will remove them from the bundle. Provide such services using ActionsModule to avoid this, or directly inject services when above technique not applicable.

import { ActionHandler, AsyncActionHandler, initActionHandlers } from 'ngx-action';

@Injectable()
export class TreeShakeableService {
  constructor() {
      initActionHandlers(this);
  }

  @ActionHandler(...)
  // ...
}

1.1 At NgModule level

import { ActionsModule } from "ngx-action";

@NgModule({
  providers: [
    // use ActionsModule.provide() to prevent tree-shaking
    ActionsModule.provide([TreeShakeableService]),
  ],
})
export class FeatureModule {}

1.2 At Component level

ActionsModule not applicable here (neither with standard nor with standalone components), so inject them directly.

@Component({
  providers: [TreeShakeableService], // <-- provide service
})
export class FeatureComponent {
  constructor(
    public service: TreeShakeableService, // <-- inject service to prevent tree-shaking
  ) {}
}

1.3 When providing via route provider

import { importProvidersFrom } from "@angular/core";
import { ActionsModule } from "ngx-action";

export const routes: Routes = [
  {
    path: "",
    component: SomeComponent,
    // use importProvidersFrom() with ActionsModule.provide() to prevent tree-shaking
    providers: [importProvidersFrom(ActionsModule.provide([TreeShakeableService]))],
  },
];

2. Inheritance

Action handlers inheritance works similar to native inheritance: child methods will override parent methods with the same name (but you can call them using super).

@Directive()
abstract class Parent {
  @ActionHandler(Action)
  parentOne() {
    console.log('parent 1');
  }

  @ActionHandler(Action)
  parentTwo() {
    console.log('parent 2');
  }

  @ActionHandler(Action)
  parentThree() {
    console.log('parent 3');
  }
}

@Component(...)
class Child extends Parent {
  constructor() {
    super();
    initActionHandlers(this);

    Actions.dispatch(new Action());
    // parent 3 // parent parentThree()
    // child 1 // child parentOne()
    // parent 2 // child super.parentTwo()
    // child 2 // child parentTwo()
    // child 3 // child childThree()
  }

  @ActionHandler(Action)
  parentOne() {
    console.log('child 1');
  }

  @ActionHandler(Action)
  parentTwo() {
    super.parentTwo(); // super call
    console.log('child 2');
  }

  @ActionHandler(Action)
  childThree() {
    console.log('child 3');
  }
}
1.2.2

1 year ago

2.0.0

12 months ago

1.2.0

2 years ago

1.2.1

2 years ago

1.1.0

3 years ago

1.0.0

3 years ago