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

ngrx-typed-actions

v8.0.4

Published

Actions and Reducer Utility Library for NGRX

Downloads

286

Readme

NGRX Actions

Actions/reducer utility for NGRX. It provides a handful of functions to make NGRX/Redux more Angular-tastic.

  • @Store(MyInitialState): Decorator for default state of a store.
  • @Action(...MyActionClass: Action[]): Decorator for a action function.
  • @Effect(...MyActionClass: Action[]): Decorator for a effect function.
  • ofAction(MyActionClass): Lettable operator for NGRX Effects
  • createReducer(MyStoreClass): Reducer bootstrap function
  • @Select('my.prop'): Select decorator

Inspired by redux-act and redux-actions for Redux.

See changelog for latest changes.

Why it's a fork

It's a fork from a project created by amcdnl, he made a great work but the project recently haven't been mainteded, that's why we decided to continue his great work.

Whats this for?

This is sugar to help reduce boilerplate when using Redux patterns. That said, here's the high level of what it provides:

  • Reducers become classes so its more logical organization
  • Automatically creates new instances so you don't have to handle spreads everywhere
  • Enables better type checking inside your actions
  • Reduces having to pass type constants by using type checking

Its dead simple (<100LOC) and you can pick and choose where you want to use it.

Getting Started

To get started, lets install the package thru npm:

npm i ngrx-typed-actions -S

Reducers

Next, create an action just like you do with NGRX today:

export class MyAction {
  readonly type = 'MyAction';
  constructor(public payload: MyObj) {}
}

then you create a class and decorate it with a Store decorator that contains the initial state for your reducer. Within that class you define methods decorated with the Action decorator with an argument of the action class you want to match it on.

import { Store, Action } from 'ngrx-actions';

@Store({
    collection: [],
    selections: [],
    loading: false
})
export class MyStore {
    @Action(Load, Refresh)
    load(state: MyState, action: Load) {
        state.loading = true;
    }

    @Action(LoadSuccess)
    loadSuccess(state: MyState, action: LoadSuccess) {
        state.collection = [...action.payload];
    }

    @Action(Selection)
    selection(state: MyState, action: Selection) {
        state.selections = [...action.payload];
    }

    @Action(DeleteSuccess)
    deleteSuccess(state: MyState, action: DeleteSuccess) {
        const idx = state.collection.findIndex(r => r.myId === action.payload);
        if (idx === -1) {
          return state;
        }
        const collection = [...state.collection];
        collection.splice(idx, 1);
        return { ...state, collection };
    }
}

You may notice, I don't return the state. Thats because if it doesn't see a state returned from the action it inspects whether the state was an object or array and automatically creates a new instance for you. If you are mutating deeply nested properties, you still need to deal with those yourself.

You can still return the state yourself and it won't mess with it. This is helpful for if the state didn't change or you have some complex logic going on. This can be seen in the deleteSuccess action.

Above you may notice, the first action has multiple action classes. Thats because the @Action decorator can accept single or multiple actions.

To hook it up to NGRX, all you have to do is call the createReducer function passing your store. Now pass the myReducer just like you would a function with a switch statement inside.

import { createReducer } from 'ngrx-actions';
export function myReducer(state, action) { return createReducer(MyStore)(state, action); }

In the above example, I return a function that returns my createReducer. This is because AoT complains stating Function expressions are not supported in decorators if we just assign the createReducer method directly. This is a known issue and other NGRX things suffer from it too.

Next, pass that to your NGRX module just like normal:

@NgModule({
   imports: [
      StoreModule.forRoot({
         pizza: pizzaReducer
      })
   ]
})
export class AppModule {}

Optionally you can also provide your store directly to the NgrxActionsModule and it will handle creating the reducer for you and also enables the ability to use DI with your stores. So rather than describing in forRoot or forFeature with StoreModule, we call them on NgrxActionsModule.

@NgModule({
   imports: [
      NgrxActionsModule.forRoot({
         pizza: PizzaStore
      })
   ],
   providers: [PizzaStore]
})
export class AppModule {}

Effects

If you want to use NGRX effects, I've created a lettable operator that will allow you to pass the action class as the argument like this:

import { ofAction } from 'ngrx-actions';

@Injectable()
export class MyEffects {
    constructor(
        private update$: Actions,
        private myService: MyService
    ) {}

    @Effect()
    Load$ = this.update$.pipe(
        ofAction(Load),
        switchMap(() => this.myService.getAll()),
        map(res => new LoadSuccess(res))
    );
}

In 3.x, we introduced a new decorator called @Effect that you can define in your store to perform async operations.

@Store({ delievered: false })
export class PizzaStore {
    constructor(private pizzaService: PizzaService) {}

    @Action(DeliverPizza)
    deliverPizza(state) {
        state.delivered = false;
    }

    @Effect(DeliverPizza)
    deliverPizzaToCustomer(state, { payload }: DeliverPizza) {
        this.pizzaService.deliver(payload);
    }
}

Effects are always run after actions.

Selects

We didn't leave out selectors, there is a Select decorator that accepts a (deep) path string. This looks like:

@Component({ ... })
export class MyComponent {
    // Functions
    @Select((state) => state.color) color$: Observable<string>;

    // Array of props
    @Select(['my', 'prop', 'color']) color$: Observable<string>;

    // Deeply nested properties
    @Select('my.prop.color') color$: Observable<string>;

    // Implied by the name of the member
    @Select() color: Observable<string>;

    // Remap the slice to a new object
    @Select(state => state.map(f => 'blue')) color$: Observable<string>;
}

This can help clean up your store selects. To hook it up, in the AppModule you do:

import { NgrxActionsModule } from 'ngrx-actions';

@NgModule({
    imports: [NgrxActionsModule]
})
export class AppModule {}

And you can start using it in any component. It also works with feature stores too. Note: The Select decorator has a limitation of lack of type checking due to TypeScript#4881.

Debug information

We introduced a new ActionsService which allows to dispatch actions with extra information about the component which triggered the action. This helps a lot when you use the same action in multiple places and have to debug from where the action has been triggered. Let's say that we trigger an action from a HeaderComponent, in the Redux DevTools we will see "[HeaderComponent] MyAction". Of course you can still dispatch actions in a standard way, this is only an option if you need more debug information. This looks like:

// just import the ActionsService 
private actionsService: ActionsService,
// sample action declaration
export class MyAction {
  readonly type = 'MyAction';
  constructor(public payload: MyObj) {}
}
// let's say we trigger the action from e.g. HeaderComponent, 
// in the Redux DevTools you will see:
// [HeaderComponent] MyAction
this.actionsService.dispatch(this, MyAction, payload); 

We also added debug information to effects, to use it, just add at the end of the pipe: debugTypeMap('name$'):

    @Effect()
    Load$ = this.update$.pipe(
        ofAction(Load),
        switchMap(() => this.myService.getAll()),
        map(res => new LoadSuccess(res)),
        debugTypeMap('Load$'),
    );

Common Questions

  • What about composition? Well since it creates a normal reducer function, you can still use all the same composition fns you already use.
  • Will this work with normal Redux? While its designed for Angular and NGRX it would work perfectly fine for normal Redux. If that gets requested, I'll be happy to add better support too.
  • Do I have to rewrite my entire app to use this? No, you can use this in combination with the tranditional switch statements or whatever you are currently doing.
  • Does it support AoT? Yes but see above example for details on implementation.
  • Does this work with NGRX Dev Tools? Yes, it does.
  • How does it work with testing? Everything should work the same way but don't forget if you use the selector tool to include that in your test runner though.

Community