3d cartoon hands holding a phone

Unlock full course by purchasing a membership

Lesson 4

Refactoring a Complex State Service

Dealing with previous state with connect

EXTENDED

Refactoring a Complex State Service

I mentioned at the end of the last lesson that the first example we looked at for implementing connect was a bit of an ideal case that ends up looking super clean. It is still realistic, and often our state will end up looking like this (in fact, the example we looked at was a snippet from the next application we will be building).

However, things are complicated a little when we also need to access the previous state in order to set our new state. Our Quicklists application does that a lot, so to see how this works we will refactor the services in the Quicklists application.

Again, if you do not already have the completed Quicklists application on your computer you can find it here.

IMPORTANT: Make sure that you have installed ngxtension in the application:

ng add ngxtension

Refactoring the Checklist Service

Let’s start by refactoring the ChecklistService. The only thing we typically need to touch when doing these refactors to use connect is our reducers — our sources and everything else can remain the same.

This is what the reducers look like for our ChecklistService without connect:

  constructor() {
    // reducers
    this.checklistsLoaded$.pipe(takeUntilDestroyed()).subscribe({
      next: (checklists) =>
        this.state.update((state) => ({
          ...state,
          checklists,
          loaded: true,
        })),
      error: (err) => this.state.update((state) => ({ ...state, error: err })),
    });

    this.add$.pipe(takeUntilDestroyed()).subscribe((checklist) =>
      this.state.update((state) => ({
        ...state,
        checklists: [...state.checklists, this.addIdToChecklist(checklist)],
      }))
    );

    this.remove$.pipe(takeUntilDestroyed()).subscribe((id) =>
      this.state.update((state) => ({
        ...state,
        checklists: state.checklists.filter((checklist) => checklist.id !== id),
      }))
    );

    this.edit$.pipe(takeUntilDestroyed()).subscribe((update) =>
      this.state.update((state) => ({
        ...state,
        checklists: state.checklists.map((checklist) =>
          checklist.id === update.id
            ? { ...checklist, title: update.data.title }
            : checklist
        ),
      }))
    );

    // effects
    effect(() => {
      if (this.loaded()) {
        this.storageService.saveChecklists(this.checklists());
      }
    });
  }

We will be refactoring all of this, but the bit specifically that we are focusing on is stuff like this:

    this.add$.pipe(takeUntilDestroyed()).subscribe((checklist) =>
      this.state.update((state) => ({
        ...state,
        checklists: [...state.checklists, this.addIdToChecklist(checklist)],
      }))
    );

This source emits the new checklist to add. However, in order to set the new checklists state we first need to access all of the existing checklists in the previous state with ...state.checklists and then add our new checklist to those.

This is the way we used connect in the last lesson:

  constructor() {
    // reducers
    const nextState$ = merge(
      this.userAuthenticated$.pipe(map(() => ({ status: 'success' as const }))),
      this.login$.pipe(map(() => ({ status: 'authenticating' as const }))),
      this.error$.pipe(map(() => ({ status: 'error' as const })))
    );

    connect(this.state).with(nextState$);
  }

There is no previous state here — we just map our sources to whatever we want the next state to be.

However, and again you may remember this from the lesson where we broke down exactly how connect works, we can also optionally supply a reducer function to connect.

We will update the entire ChecklistService in just a moment, but dealing with the add$ source in isolation would look like this:

    connect(this.state)
      .with(this.add$, (state, checklist) => ({
        checklists: [...state.checklists, this.addIdToChecklist(checklist)],
      }))

Instead of just supplying the source, we also supply a reducer function. This reducer function will be given the previous state (state in this example) and whatever our source emits (checklist) in this example. We then just have our reducer return whatever it is we want to set in the state signal — in this case, we want to overwrite the checklists property with our new checklists array.

EXTENDED
Key

Thanks for checking out the preview of this lesson!

You do not have the appropriate membership to view the full lesson. If you would like full access to this module you can view membership options (or log in if you are already have an appropriate membership).