npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

ngx-action

v2.0.0

Published

Sync components of Angular application using actions and decorators.

Downloads

14

Readme

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/core | rxjs | | -------------------- | ------------- | ----------- | | >=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');
  }
}