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

store-saga

v1.0.0-beta.11

Published

An Rx implementation of redux-saga for @ngrx/store

Downloads

19

Readme

store-saga

Side Effect Model for @ngrx/store

Build Status Code Climate Test Coverage

An Rx implementation of redux-saga for @ngrx/store and Angular 2.

Based on redux-saga-rxjs by Salsita, with inspiration from redux-saga by Yelouafi.

Documentation

  • Utilities - Information on various utility functions
  • SagaRunner - Use the SagaRunner service to run, stop, and pause saga effects dynamically
  • Testing - Learn how to test saga effects using the provided SagaTester service

Usage

store-saga depends on @ngrx/store and Angular 2. After configuring @ngrx/store, install store-saga:

npm install store-saga --save

Write a saga:

import {createSaga} from 'store-saga';

export const increment = createSaga(function(){
  return iteration$ => iteration$
    .filter(iter => iter.action.type === 'DECREMENT')
    .map(() => {
      return { type: 'INCREMENT'}
    });
});

Install the store-saga middleware in the same place you provide ngrx/store:

import {installSagaMiddleware} from 'store-saga';

bootstrap(App, [
  provideStore(reducer, initialState),
  installSagaMiddleware(increment)
]);

Motivation

Angular 2 components can receive visible input from a few different sources:

  • Inputs injected into the constructor using dependency injection
  • Inputs provided by property bindings using the @Input() decorator
  • Events emitted from the template

A pure component has no invisible inputs, instead relying on the above strategies for accepting input.

How can an input be invisible ?

Consider the following simple counter component:

@Component({
  selector: 'counter',
  template: `
    {{ value }}
    <button (click)="add()">Add</button>
  `
})
class Counter {
  value = Math.random() * 30;

  add() {
    this.value = this.value + 1;
  }
}

It receives input from an event binding in the template, but it relies on a hidden input: Math.random(). These hidden inputs can be thought of as side effects. If you use this <counter /> component anywhere in your application, it has unknown consequences. If you write components that have side effects, they are generally harder to test and make it more difficult to reason about the correctness of your application.

A very common source of side effects in apps are Http requests. If a component makes an Http request or injects a service that makes a request, you are introducing side effects into your component. Components with side effects are considered impure. This is because an Http request - even if you are injecting the Http service - has unknown consequences. Before you make the request you have no guarantee if the request will succeed or fail.

If you are using @ngrx/store in your application to manage state, you are already doing a lot of the work necessary to make your components more pure. However @ngrx/store does not provide any way to deal with side effects out-of-the-box. Sagas are just that: a way to isolate side effects in your application so that you can write pure components.

How do they work?

In store-saga, sagas are simply functions that accept a saga$ observable and return a new observable of actions. The saga$ observable emits every time state changes, providing you with the new state and the action that caused state to update. You filter over the saga$ observable to listen for specific actions or state changes in your application, execute side-effect producing code, and return new actions to dispatch.

For example, imagine building a <login-form /> component that accepts a username and password. In order to verify authentication you need to make an Http request to your authentication server. This request is a side-effect and should be isolated from our <login-form /> component. To do this, we will have the component dispatch an AUTH_REQUEST action when the user submits the form:

@Component({
  selector: 'login-form',
  template: `
    <form (submit)="login()">
      <label>
        Username
        <input type="text" [(ngModel)]="username">
      </label>

      <label>
        Password
        <input type="password" [(ngModel)]="password">
      </label>

      <button type="submit">Login</button>
    </form>
  `
})
export class LoginForm {
  constructor(private store: Store<State>) { }

  username = '';
  password = '';

  login() {
    this.store.dispatch({
      type: 'AUTH_REQUESTED',
      payload: {
        username: this.username,
        password: this.password
      }
    });
  }
}

Now we can write a saga that listens for the AUTH_REQUESTED action being dispatched and makes the Http request. If the request succeeds, we will return an AUTH_SUCCESS action and if the request fails we will return an AUTH_FAILURE action (returned action will be dispatched by store-saga middleware):

const loginEffect = createSaga(function loginSagaFactory(http: Http) {

  return function loginSaga(iteration$: Observable<any>) {
    return iteration$
      .filter(iteration => iteration.action.type === 'AUTH_REQUESTED')
      .map(iteration => iteration.action.payload)
      .mergeMap(payload => {
        return http.post('/auth', JSON.stringify(payload))
          .map(res => {
            return {
              type: 'AUTH_SUCCESS',
              payload: res.json()
            }
          })
          .catch(err => {
            return Observable.of({
              type: 'AUTH_FAILURE',
              payload: err.json()
            });
          });
      });
  };

}, [ Http ]);

The last step is to install the saga middleware when our application bootstraps, providing all of the sagas we want to run:

import { installSagaMiddleware } from 'store-saga';

bootstrap(App, [
  provideStore(reducer),
  installSagaMiddleware(loginEffect)
]);

Now our side effect is completely isolated from our <login-form /> component. This also has the benefit of decoupling our asynchronous business logic from our UI, making your code more portable and - with the help of built-in saga testers - easier to test.