Reputation: 1575
I know the title of the question is vague, but unfortunately I don't exactly know what is causing my issue so please bear with me.
I just started working on a small Angular 8 task, which uses NgRx/Store and NgRx/Effects as well. I am fetching an array of Book
objects using a getBooks
function. My codes are as follows(Haven't put inports everywhere just to make the code parts shorter)
book.component.ts
export class BookComponent {
constructor(private booksService: BooksService, public store: Store<BooksState>) {
this.books$ = store.pipe(select('books'));
}
books$: Observable<BooksState>;
BooksSubscription: Subscription;
booksList: Book[];
ngOnInit() {
this.BooksSubscription = this.books$
.pipe( map(x => { this.booksList = x.Books; }) ).subscribe();
this.store.dispatch(BooksActions.BeginGetBooksAction())
}
}
book.service.ts
import { Injectable } from '@angular/core';
import booksData from "../../books.json";
@Injectable()
export class BooksService {
constructor() { }
getBooks(){
return booksData;
}
}
booksData
is an array of objects of type Book
which I import from a json
file.
book.action.ts
import { createAction, props } from "@ngrx/store";
import { Book } from "./book";
export const GetBooksAction = createAction("GetBooks");
export const BeginGetBooksAction = createAction("BeginGetBooks");
export const SuccessGetBooksAction = createAction( "SuccessGetBooks", props<{ payload: Book[] }>());
book.reducer.ts
import { Action, createReducer, on } from "@ngrx/store";
import * as BooksActions from "./book.action";
import { Book } from "./book";
export interface BooksState {
Books: Book[];
}
const initialState: BooksState = {
Books: []
};
const booksReducer = createReducer(
initialState,
on(BooksActions.GetBooksAction, state => state))
on(BooksActions.SuccessGetBooksAction, (state: BooksState, { payload }) => {
console.log(payload)
return { ...state, Books: payload };
});
export function reducer(state: BooksState | undefined, action: Action) {
return booksReducer(state, action);
}
book.effect.ts
Now this is where I'm lost. I found a reference code on the NgRx docs based on which I wrote my code but it does not work
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { Action } from '@ngrx/store';
import { Observable } from 'rxjs';
import { map, mergeMap} from 'rxjs/operators';
import * as BooksActions from './book.action';
import { Book } from './book';
import { BooksService } from './book.service';
@Injectable()
export class BooksEffects {
constructor(private action$: Actions, private booksService: BooksService) { }
GetBooks$: Observable<Action> = createEffect(() =>
this.action$.pipe(
ofType(BooksActions.BeginGetBooksAction),
mergeMap(
() => this.booksService.getBooks().pipe(
map((booksData: Book[]) => {
return BooksActions.SuccessGetBooksAction({ payload: booksData });
}))
)
)
)
);
}
I get following errors during compilation. The errors are on the lines where I have pipe functions
error TS2322: Type 'Observable<unknown>' is not assignable to type 'Observable<Action> | ((...args: any[])
=> Observable<Action>)'.
Type 'Observable<unknown>' is not assignable to type 'Observable<Action>'.
Property 'type' is missing in type '{}' but required in type 'Action'.
error TS2339: Property 'pipe' does not exist on type '{ "isbn": string; "title": string; "subtitle": string; "author": string; "published": string; "publisher": string; "pages": number; "description": string; "website": string; }[]'.
Also, I have added StoreModule.forRoot({ books: reducer})
and EffectsModule.forRoot([BooksEffects])
to imports
and added BookService
to Providers
in app.module.ts
Upvotes: 2
Views: 3510
Reputation: 5428
There are two mistakes in your code.
MergeMap used incorrectly
Your this.booksService.getBooks()
seems not to return an Observable
. In this case, you want to use map
instead of mergeMap
:
GetBooks$: Observable<Action> = createEffect(() =>
this.action$.pipe(
ofType(BooksActions.BeginGetBooksAction),
map(() => this.booksService.getBooks()),
map(payload => BooksActions.SuccessGetBooksAction({ payload })
)
);
To elaborate on this, mergeMap
is used when you want to switch from one Observable to another (similiar to switchMap
). The callback is expected to return an Observable to which mergeMap
can then subscribe. The callback for map
is expected to return a value and wraps that value into a new Observable for you. As you getBooks()
does not return an Observable, mergeMap
cannot subscribe to it, TypeScript knows that and throws said errors.
Alternatively, you could also change your getBooks()
to return an Observable instead (this would be the way to go if you later want to fetch these from an HTTP Request, which would return an Observable.
Wrong parenthesis in reducer
Also, on further inspection, your effects and your reducer are actually running. But you have a small mistake with your paranthesis in your reducer. Look at this:
const booksReducer = createReducer(
initialState,
on(BooksActions.GetBooksAction, state => state)) // <---- This ")" is closing your "createReducer" function!
on(BooksActions.SuccessGetBooksAction, (state: BooksState, { payload }) => {
console.log(payload)
return { ...state, Books: payload };
});
so if you format this to reflect what it's doing you wrote:
const booksReducer = createReducer(
initialState,
on(BooksActions.GetBooksAction, state => state)
)
// You "on" function is actually outside "createReducer" effectively NEVER being triggered (but this is syntactically correct so a very tricky error)
on(BooksActions.SuccessGetBooksAction, (state: BooksState, { payload }) => {
console.log(payload)
return { ...state, Books: payload };
});
So you just need to fix that one:
const booksReducer = createReducer(
initialState,
on(BooksActions.GetBooksAction, state => state),
on(BooksActions.SuccessGetBooksAction, (state: any, { payload }) => {
console.log('Reducer is running, value is:', payload)
return { ...state, Books: payload };
})
);
Here is a working Stackblitz. Look into the console to see everything being triggered.
Upvotes: 5
Reputation: 101142
pipe
is a function of Observable
, but this.booksService.getBooks()
returns a plain Array instead.
So either don't call pipe
on the resulting Array or let your service return an Observable
. An easy way is to return of(
booksData)
; the of
function creates an Observable
from the given value.
Upvotes: 1