0
votes

I am currently building an ngrx breadcrumb component in angular 5.2.1, using ngrx 4.1.1. This is a work-in-progress, so there are still parts I have to fix.

I am currently getting an error in the change Effect. The error is:

Effect "BreadcrumbEffects.breadcrumbs$" threw an error Source: BreadcrumbEffects

Error: TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.

It is only since adding in the existing state via the "withLatestFrom" statement that I am getting the error. Prior to this, I did not have the withLatestFrom statement and I had a switchMap statement instead of the map statement, and it worked fine. What am I doing wrong?

My effect is declared as follows.

/* Effects handle the actual execution of the action */
import { Injectable } from "@angular/core";
import { BreadcrumbService } from "./breadcrumb.service";
import { Observable } from "rxjs/Observable";
import * as moment from "moment";
import { Action, Store } from "@ngrx/store";
import { Effect, Actions } from "@ngrx/effects";
import { BreadcrumbActionTypes, ChangeBreadcrumbsAction, ChangeBreadcrumbsCompleteAction } from "./breadcrumb.actions";
import * as fromBreadcrumbReducer from "./breadcrumb.reducers";

@Injectable()
export class BreadcrumbEffects {

    crumbs$: Observable<any>;

    constructor(private readonly actions$: Actions,
        private readonly store$: Store<fromBreadcrumbReducer.BreadcrumbState>,
        private readonly breadcrumbService: BreadcrumbService) {

        this.crumbs$ = this.store$.select(fromBreadcrumbReducer.Selectors.getBreadcrumbs);
    }

    @Effect()
    breadcrumbs$: Observable<ChangeBreadcrumbsCompleteAction> =
    this.actions$
        .ofType(BreadcrumbActionTypes.ChangeBreadcrumbs)
        .withLatestFrom(this.crumbs$)
        .map((result: any) => {
            let action: ChangeBreadcrumbsAction, crumbs: any[];
            [action, crumbs] = result;
            /* make a copy of the existing crumbs. */
            /* this code is still being worked on, hence the hardcoded index */
            const newCrumbs = crumbs.slice(0);
            if (crumbs.length > 0) {
                newCrumbs[1] = { ...newCrumbs[1], displayName: action.name }
            }
            return new ChangeBreadcrumbsCompleteAction(newCrumbs);
        });
}
1

1 Answers

2
votes

The problem is that this.crumbs$ is passed to withLatestFrom(this.crumbs$), but it won't be defined until after it has been assigned in the constructor.

You could solve the problem using defer:

import { defer } from "rxjs/observable/defer";
...
.withLatestFrom(defer(() => this.crumbs$))

Or by declaring your effect using a function:

@Effect()
breadcrumbs$(): Observable<ChangeBreadcrumbsCompleteAction> {
  return this.actions$
    .ofType(BreadcrumbActionTypes.ChangeBreadcrumbs)
    .withLatestFrom(this.crumbs$)
    .map((result: any) => {
      let action: ChangeBreadcrumbsAction, crumbs: any[];
      [action, crumbs] = result;
      /* make a copy of the existing crumbs. */
      /* this code is still being worked on, hence the hardcoded index */
      const newCrumbs = crumbs.slice(0);
      if (crumbs.length > 0) {
        newCrumbs[1] = { ...newCrumbs[1], displayName: action.name }
      }
      return new ChangeBreadcrumbsCompleteAction(newCrumbs);
});