How do I get the value from an ngrx store into my template?

Viewed 19760

A total Redux noob here, I am having a little difficulty getting my data out of the store for use within my view. Here are my Actions, Reducers etc.

genre.model.ts

export interface Genre {
    id: string;
    title: string;
    description: string;
    slug: string;
    error: string;
}

export const initialState: Genre = {
    id: '',
    title: '',
    description: '',
    slug: '',
    error: null
};

genre.service.ts

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Http } from '@angular/http';
import {environment} from "../../../environments/environment";
import {Genre} from "./genre.model";

@Injectable()
export class GenreService {

    apiUrl = environment.apiUrl + environment.apiVersion + '/';

    constructor(private http: Http) { }

    /**
     * Gets Genres
     *
     * @returns {Observable<Genre[]>}
     */
    getGenres(): Observable<Genre[]> {
        return this.http.get(this.apiUrl + 'genres/')
            .map(res => res.json());
    }

    /**
     * Gets an individual genre
     *
     * @param {String} slug
     * @returns {Observable<Genre[]>}
     */
    getGenre(slug: String): Observable<Genre[]> {
        return this.http.get(this.apiUrl + 'genres/' + slug)
            .map(res => res.json().genre);
    }
}

genre.actions.ts

import { Action } from '@ngrx/store';
import { Injectable } from '@angular/core';

import { Genre } from '../_shared/genre.model';

@Injectable()
export class GenreActions {

    static LOAD_GENRES = '[Genre] Load Genres';
    loadGenres(): Action {
        return {
            type: GenreActions.LOAD_GENRES
        };
    }

    static LOAD_GENRES_SUCCESS = '[Genre] Load Genres Success';
    loadGenresSuccess(genres): Action {
        return {
            type: GenreActions.LOAD_GENRES_SUCCESS,
            payload: genres
        };
    }

    static GET_GENRE = '[Genre] Get Genre';
    getGenre(slug): Action {
        return {
            type: GenreActions.GET_GENRE,
            payload: slug
        };
    }

    static GET_GENRE_SUCCESS = '[Genre] Get Genre Success';
    getGenreSuccess(genre): Action {
        return {
            type: GenreActions.GET_GENRE_SUCCESS,
            payload: genre
        };
    }
}

genre.reducers.ts

import { Action } from '@ngrx/store';

import {Genre, initialState} from '../_shared/genre.model';

import { GenreActions } from './genre.actions';

export function genreReducer(state: Genre = initialState, action: Action) {
    switch (action.type) {
        case GenreActions.GET_GENRE_SUCCESS: {
            return action.payload;
        }
        case GenreActions.LOAD_GENRES_SUCCESS: {
            return action.payload;
        }
        default: {
            return state;
        }
    }
}

genre.effects.ts

export class GenreEffects {
    constructor (
        private update$: Actions,
        private genreActions: GenreActions,
        private svc: GenreService,
    ) {}

    @Effect() loadGenres$ = this.update$
        .ofType(GenreActions.LOAD_GENRES)
        .switchMap(() => this.svc.getGenres())
        .map(genres => this.genreActions.loadGenresSuccess(genres));

    @Effect() getGenre$ = this.update$
        .ofType(GenreActions.GET_GENRE)
        .map(action => action.payload)
        .switchMap(slug => this.svc.getGenre(slug))
        .map(genre => this.genreActions.getGenreSuccess(genre));
}

genre.detail.component.ts

import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import { ActivatedRoute, Router } from '@angular/router';
import { Component, OnInit } from '@angular/core';

import { Genre } from '../_shared/genre.model';
import { GenreActions } from '../_store/genre.actions';

@Component({
    selector: 'app-genre-detail',
    templateUrl: './genre-detail.component.html',
    styleUrls: ['./genre-detail.component.scss']
})
export class GenreDetailComponent implements OnInit {

    public idSub: Subscription;
    public genre: Observable<any>;

    constructor(
        private store: Store<Genre>,
        private route: ActivatedRoute,
        private genreActions: GenreActions,
        private router: Router
    ) {
        this.genre = store.select('genres');
    }

    ngOnInit() {
        this.idSub = this.route.params.subscribe(params => {
            this.store.dispatch(this.genreActions.getGenre(params['slug']));
            console.log(this.genre);
            });
    }
}

I can see that my API request is being fired, and it is return data, I can see in the Redux devtools that state is being populated but I just can't seem to get the data out and in to my views using the normal {{ genre.title }} I just get [Object object] being thrown back at me?

I am sure that this might be something really easy but like I said I am a total noob and have spent around 5 hours on this trying out different things following different tutorials etc.

Console

2 Answers

Currently, you are getting just the Observable but expecting the list of genre. And, that is the problem. You need to subscribe it to get the value.

Make a different Object for Genre to use the Genre interface in state (as Rahul Singh told already)

export interface AppState {
    genre:Genre
}

constructor(private store: Store<AppState>,
    private route: ActivatedRoute,
    private genreActions: GenreActions, 
    private router: Router) {}

and in ngOnInit subscribe it to obeserve the change.

ngOnInit(): void {
    this.store.select(s => s.genre).subscribe(data => this.genre = data);
}

Note: ngOnInit() is used to make sure that the component's properties (for example: @Input() someId: number) you used are already initialized . So, If you use any component properties you should do it in ngOnInit(). otherwise constructor() is fine. but we should use constructor() to setup Dependency Injection and not much else. ngOnInit() is better place to "start" - it's where/when components' bindings are resolved.

Related