A State Manager For Angular That Makes Sense, At Least For Me :)
Tags: angular, redux, typescript, ngrx
Published: 2019-10-11 09:29:34


![To Redux or Not To Redux](https://thepracticaldev.s3.amazonaws.com/i/9e8bjo97s94cxyh4kemf.jpg)

Forewords: In the following post I give an example of how to use @microphi/store for managing state in angular apps. The project is just a PoC at the time of writing. You can find more here https://github.com/microph1/microphi/tree/master/projects/store.
The code snippets you find below are part of an example app that can be found at https://github.com/microph1/ticket-store-example



I wanted to get my head around state management in angular. So I went for ngrx. Watched some videos, read some tutorials. A big mess in my head and not really sure what I was doing. Boilerplate code? Yeah, sad days!

Then I gave a look at Reduxjs. Much better. Less boilerplate. Much less documentation to go through. But still some boilerplate and also the fact that you need to write "pure" functions in an environment where you thought yourself to use classes and OOP didn't make much sense in my head. Yeah maybe it's easier to test but still.

So I started to write a state manager myself. Happy days!

Let's suppose you've got your brand new angular app with a service to handle tickets. 

```typescript
// ticket.service.ts
import { Injectable } from '@angular/core';
import { Observable, of, throwError } from 'rxjs';
import { delay, map, tap } from 'rxjs/operators';
import { Ticket, User } from './ticket.interface';

function randomDelay() {
  return Math.random() * 4000;
}

@Injectable()
export class TicketService {
  storedTickets: Ticket[] = [
    {
      id: 0,
      description: 'Install a monitor arm',
      assigneeId: 111,
      completed: false
    },
    {
      id: 1,
      description: 'Move the desk to the new location',
      assigneeId: 111,
      completed: false
    },
    {
      id: 2,
      description: 'This ticket assignee has been deleted',
      assigneeId: 112,
      completed: false
    }
  ];

  storedUsers: User[] = [{ id: 111, name: 'Victor' }];

  lastId = 1;

  private findTicketById = id => this.storedTickets.find(ticket => ticket.id === +id);
  private findUserById = id => this.storedUsers.find(user => user.id === +id);

  tickets() {
    return of(this.storedTickets).pipe(delay(randomDelay()));
  }

  ticket(id: number): Observable<Ticket> {
    return of(this.findTicketById(id)).pipe(delay(randomDelay()));
  }

  users() {
    return of(this.storedUsers).pipe(delay(randomDelay()));
  }

  user(id: number) {
    const user = this.findUserById(id);
    return user ? of(user).pipe(delay(randomDelay())) : throwError('User not found');
  }

  newTicket(payload: { description: string }) {
    const newTicket: Ticket = {
      id: ++this.lastId,
      description: payload.description,
      assigneeId: null,
      completed: false
    };

    return of(newTicket).pipe(
      delay(randomDelay()),
      tap((ticket: Ticket) => this.storedTickets.push(ticket))
    );
  }

  assign(ticketId: number, userId: number) {
    const foundTicket = this.findTicketById(+ticketId);
    const user = this.findUserById(+userId);

    if (foundTicket && user) {
      return of(foundTicket).pipe(
        delay(randomDelay()),
        map((ticket: Ticket) => {
          ticket.assigneeId = +userId;
          return ticket;
        })
      );
    }

    return throwError(new Error('ticket or user not found'));
  }

  complete(ticketId: number, completed: boolean) {
    const foundTicket = this.findTicketById(+ticketId);
    if (foundTicket) {
      return of(foundTicket).pipe(
        delay(randomDelay()),
        map((ticket: Ticket) => {
          ticket.completed = completed;
          return ticket;
        })
      );
    }

    return throwError(new Error('ticket not found'));
  }
}

```

Now what we want to achieve is to add a state manager on top of this service. Let's start:

```
npm i --save @microphi/store
```

Create some interfaces that will be useful later: 

```typescript
export interface User {
  id: number;
  name: string;
}

export interface Ticket {
  id: number;
  description: string;
  assigneeId: number;
  completed: boolean;
  assignee?: User;
}

export type TicketWithState = Ticket & { isLoading?: boolean };

export interface TicketsState {
  tickets: TicketWithState[];
}


```


Now let's create an enum with all the actions our store will handle.

```typescript
export enum TicketActions {
  FIND_ALL, // will retrive all tickets
  FIND_ONE, // will retrive one ticket
  CHANGE_STATUS // will change the status done/undone of a ticket
}

```


Let's create our store

```typescript
// ticket.store.ts

@Store({
  name: 'ticketStore',
  initialState: { tickets: [] },
  actions: TicketActions
})
@Injectable()
export class TicketStore extends BaseStore<TicketsState> {
  public tickets$ = this.store$.pipe(
    map((state) => state.tickets)
  );

  constructor(private ticketService: TicketService) {
    super();
  }

[...]

```

Within the `@Store` decorator we define the `name` of the store, it's initial state and we provide the enum of our actions.

One thing to notice here is that `this.store$` is an observable on which internally every and each new store state will be `next`ed. It's defined in the parent `BaseStore` class and there it's protected so that we always need to `map` it somehow when we want to expose it. And of course we're gonna need the TicketService.


Before we go further a bit of philosophy. Bear with me though because what follows is not about Redux/Flux neither about ngrx. It is instead my interpretation of the above to make it easier to digest and to use.

The approach is as follows:

```

somewhere in our app -> action -> effect -> stateupdate

```

Simply somewhere in our app we dispatch an action which will trigger an (optional) effect which will trigger a state change. That state change will be "visible" to everyone that subscribed to the `tickets$` observable, for example. 

Bear in mind that asynchronous stuff needs to be handled in an `effect`.

Right, now: before we start writing any effect let's set up our component so that we get the `tickets$`.

```typescript

@Component({
  selector: 'app-tickets',
  templateUrl: './tickets.component.html'
})
export class TicketsComponent implements OnInit {

  public tickets$ = this.store.tickets$;

  constructor(private store: TicketStore) { }


  public ngOnInit(): void {
    this.store.dispatch(TicketActions.FIND_ALL);
  }
}


```

What we need now to create an effect to retrive the list of tickets from our backend.

```typescript
// ticket.store.ts


  @Effect(TicketActions.FIND_ALL)
  private getTickets(state: Ticket[], payload) {
    let numberOfTickets = 0;

    return this.ticketService.tickets().pipe(
      switchMap((tickets) => {
        console.log('parsing tickets', tickets);
        numberOfTickets = tickets.length;
        return from(tickets);
      }),
      tap((ticket) => {
        console.log('parsing ticket', ticket);
      }),
      mergeMap((ticket: Ticket) => {

        return this.ticketService.user(ticket.assigneeId).pipe(
          map((user) => {
            ticket.assignee = user;
            return ticket;
          }),
          catchError(err => {
            console.error(err);
            // silently fail
            ticket.assignee = {
              name: `unable to find user with id ${ticket.assigneeId}`,
              id: ticket.assigneeId
            };

            return of(ticket);
          })
        );
      }),
      bufferCount(numberOfTickets)
    );
  }

```

Define a method decorated with `@Effect(actionName)`. This function will be invoked whenever the given `actionName` is dispatched. 

Methods decorated with `@Effect` must return an observable!

`tickest()` returns an array of tickets which is `switchMap`ed into each element. Then we `mergeMap` it with the user associated with the `assigneeId` field if it exists; if not just silently fail. Each element is `bufferCount`ed so that the observable returns an array.

In case of error we swallow it and return the original ticket with a convenience user attached to it.

Of course it can be arguable to fetch the tickets in a different way depending of what you actually want to achieve so bear with this code for demonstration purpose.


At this point we dispatched the `FIND_ALL` event internally the `getTickets` method is invoked and it's return value gets subscribed to. Once a value gets through we will next the `actions$` observable again this time with a "complementary" action which will trigger the reducing method associated with `FIND_ALL`.

Let's write our reducer:

```typescript
// ticket.store.ts

  @Reduce(TicketActions.FIND_ALL)
  private onResponse(state, payload: Ticket[]) {

// REM: 
// initial state would be { tickets: [] }
// the payload is the data coming through from the associated @Effect[ed] method

    this.state.tickets.push(...payload);

    return state;
  }

```


Internally the framework checks for the method associated to this action, i.e.: `onResponse`, invokes it and its return value is set into the `_state` private property through a setter which will trigger a `store$.next` with it.

And now you only need to use an async pipe in your view in order to see the tickets magially appear on the screen.

```htlm
<pre>
  {{tickets$ | async | json}}
</pre>

```

Hint: if you're running the example app from the repo try to open the console and type: `localStorege.debug = 'microphi:*'` and you'll see some of the magic that is happening.


> Cool, but what about loading state?


The store has a public property called `loading$` that is is nexted with and event containing three fields `type`, `payload` and `status`. Obviously `status` will tell us whether loading is in process being `true` or `false`. `type` will contain information about the event being loading and the payload is the payload we passed to the `dispatch` method.

So for example if you want to catch any loading event going on within your effects you can just do.

```typescript
// tickets.component.ts
  constructor(private ticketStore: TicketStore) {
    this.ticketStore.loading$.subscribe((status) => {

      // we can't use an async pipe for this as it would subscribe to the observable after ngOnInit hence we miss
      // the loading start event;
      this.loading = status.status;

    });

```

Or we may want to filter out only the loading for a specific event

```typescript

  constructor(private authStore: AuthStore, private ticketStore: TicketStore) {
    this.ticketStore.loading$.pipe(
      filter((event) => {
        return event.type === this.ticketStore.getRequestFromAction(TicketActions.FIND_ALL);
      })
    ).subscribe((status) => {

      // we can't use an async pipe for this as it would subscribe 
      // to only after ngOnInit hence we miss the loading 
      // start event
      this.loadingTickets = status.status;

    });

```

Here `getRequestFromAction` is an helper function to map the `FIND_ALL` action to its "complementary" action.

That's all for now folk.


Let me know what you think in the comment below.