Scan Operator For Mini Redux Stores
Tags: rxjs, angular, reactiveprogramming
Published: 2019-02-02 20:21:28

---
title: Scan Operator For Mini Redux Stores
published: true
description: Introducing redux to your team without scaring them with a framework.
tags: rxjs, angular, reactive programming
---

![toaster](https://zniup3zx6m0ydqfpv9y6sgtf-wpengine.netdna-ssl.com/wp-content/uploads/2017/01/Toast_hero-hero.jpg)

I was placed on an Angular project that did not have a state management system like Redux or ngrx. I saw this as an opportunity to gently introduce state management using RxJS.

There are `n+1` blog posts about Reactive Programming. In a nutshell, reactive programming concerns itself with async data. This data can come from APIs, or from user events. 

![toast](https://thepracticaldev.s3.amazonaws.com/i/0h31j6ziyfxqjtw0wnsd.gif)

The task was to build a toast notification system. Something similar to Angular Material’s Snackbar. The requirements were:

* each toast notification auto-expires
* each toast notification can be closed ahead of time by a user
* and there can be many toast notifications at once. 

I ended up using the `scan` operator from RxJS to have data persistence. You can think of a scan operator as JavaScript's `reduce` method.

This was accomplished inside an Angular service, however, you can take these concepts in any project that uses RxJS. 

```typescript
// toast.service.ts
// getter so no bad developer uses the store incorrectly.
  get store() { return this._store$; }

// Dispatch "actions" with a subject
  private _action$ = new Subject();

// Create a store which is just an array of 'Toast' 
  private _store$: Observable<Toast[]> = this._action$.pipe(
    map((d: ToastAction) => (!d.payload.id) ? this.addId(d) : d), // add id to toast to keep track of them.
    mergeMap((d: ToastAction) => (d.type !== ToastActionType.Remove) ? this.addAutoExpire(d) : of(d)), // concat a hide toast request with delay for auto expiring
    scan(this.reducer, []) // magic is here!
  );

// dispatch method 
  public dispatch(action: ToastAction): void {
    this._action$.next(action);
  }

// generate ids for the toast
  private addId(d: ToastAction): ToastAction {
    return ({
      type: d.type,
      payload: { ...d.payload, id: this.generateId() }
    });
  }

// If a user does not click on the toast to clear it, then it should auto expire
  private addAutoExpire(d: ToastAction) {
    const signal$ = of(d);
    const hide$ = of({ type: ToastActionType.Remove, payload: d.payload }).pipe(delay(this.config.duration));
    return concat(signal$, hide$);
  }

// generates a random string
  private generateId(): string {
    return '_' + Math.random().toString(36).substr(2, 9);
  }

// The reducer which adds and removes toast messages.
  private reducer(state: Toast[] = [], action: ToastAction): Toast[] {
    switch (action.type) {
      case ToastActionType.Add: {
        return [action.payload, ...state];
      }
      case ToastActionType.Remove: {
        return state.filter((toast: Toast) => toast.id !== action.payload.id);
      }
      default: {
        return state;
      }
    }
  }
}
```

Being able to use scan was perfect for this situation. I could reduce incoming streams of data into an array of objects using the reducer function.


To use the store, you can either subscribe to the `store` or reference it in your Angular template. 

```typescript
// toast.component.ts
export class ToastComponent {
  public state$: Observable<Toast[]> = this.toastService.store;

  constructor(public toastService: ToastService) { }
  public add() {
    this.toastService.dispatch({ type: ToastActionType.Add, payload: { message: 'hi', status: ToastStatus.Info } });
  }
  public remove(payload: Toast) {
    this.toastService.dispatch({
      type: ToastActionType.Remove,
      payload
    });
  }
   ...

```


But is this ideal? Yes it works, however, a developer that comes by shouldn't have to care about `ToastActionType` and construction objects. It's a lot of work. Let's create some helper methods in our ToastService so any developer can call 'enqueueSuccess' or 'enqueueInfo' for the type of toast we want. 

```typescript
// toast.service.ts
@Injectable({
  providedIn: 'root'
})
export class ToastService {

  get store() { return this.store$; }

  private action$: Subject<ToastAction> = new Subject<ToastAction>();
  private store$: Observable<Toast[]> = this.action$.pipe(
    map((d: ToastAction) => (!d.payload.id) ? this.addId(d) : d),
    mergeMap((d: ToastAction) => (d.type !== ToastActionType.Remove) ? this.addAutoExpire(d) : of(d)),
    scan(this.reducer, [])
  );

  constructor() { }

  public enqueueSuccess(message: string): void {
    this.action$.next({
      type: ToastActionType.Add,
      payload: {
        message,
        status: ToastStatus.Success
      }
    });
  }

  public enqueueError(message: string): void {
    this.action$.next({
      type: ToastActionType.Add,
      payload: {
        message,
        status: ToastStatus.Error
      }
    }
    );
  }

  public enqueueInfo(message: string): void {
    this.action$.next({
      type: ToastActionType.Add,
      payload: {
        message,
        status: ToastStatus.Info
      }
    });
  }

  public enqueueWarning(message: string): void {
    this.action$.next({
      type: ToastActionType.Add,
      payload: {
        message,
        status: ToastStatus.Warning
      }
    });
  }

  public enqueueHide(payload: Toast): void {
    this.action$.next({
      type: ToastActionType.Remove,
      payload
    });
  }


  private addId(d: ToastAction): ToastAction {
    return ({
      type: d.type,
      payload: { ...d.payload, id: this.generateId() }
    });
  }

  private addAutoExpire(d: ToastAction) {
    const signal$ = of(d);
    const hide$ = of({ type: ToastActionType.Remove, payload: d.payload }).pipe(delay(this.config.duration));
    return concat(signal$, hide$);
  }

  private generateId(): string {
    return '_' + Math.random().toString(36).substr(2, 9);
  }

  private reducer(state: Toast[] = [], action: ToastAction): Toast[] {
    switch (action.type) {
      case ToastActionType.Add: {
        return [action.payload, ...state];
      }
      case ToastActionType.Remove: {
        return state.filter((toast: Toast) => toast.id !== action.payload.id);
      }
      default: {
        return state;
      }
    }
  }
}

```

Now our component has a much easier time calling our service:

```typescript
// toast.component.ts

export class ToastComponent {
  public state$: Observable<Toast[]> = this.toastService.store;

  constructor(public toastService: ToastService) { }
  public add() {
    this.toastService.enqueueInfo('It works!');
    this.toastService.enqueueWarning('Oops!');
    this.toastService.enqueueError('Uh oh!');
    this.toastService.enqueueSucccess(':D');
  }
   ...

```

```html
<!-- toast.component.html --> 
  <button 
    *ngFor="let toast of (state$ | async)"
    (click)="remove(toast)">
    <span [innerHtml]="toast.message"></span>
  </button>
```

And what you get are many toast notifications with a powerful service with a very nice API.