#ngrx/store with Angular 2: Cannot read property of undefined - javascript

I'm trying to learn #ngrx/store with Angular 2 - RC4. I've got a service that I'm calling from my component, and I'm just trying to console log a list of vehicles whenever it changes.
Vehicle
export interface Vehicle {
...stuff...
}
Vehicle Reducer
import { Vehicle } from '../models';
export interface VehiclesState {
vins: string[];
vehicles: { [vin: string]: Vehicle };
}
const initialState: VehiclesState = {
vins: [],
vehicles: {}
}
export const vehicles = (state: any = initialState, {type, payload}) => {
switch (type) {
case 'LOAD_VEHICLES':
const vehicles: Vehicle[] = payload;
const newVehicles = vehicles.filter(vehicle => !state.vehicles[vehicle.vin]);
const newVehicleVins = newVehicles.map(vehicle => vehicle.vin);
const newVehiclesList = newVehicles.reduce((vehicles: { [vin: string]: Vehicle }, vehicle: Vehicle) => {
return mergeObjects(vehicles, {
[vehicle.vin]: vehicle
});
}, {});
return {
vins: [...state.vins, ...newVehicleVins],
vehicles: mergeObjects({}, state.vehicles, newVehiclesList)
}
}
}
main.ts
import { bootstrap } from '#angular/platform-browser-dynamic';
import { enableProdMode } from '#angular/core';
import { HTTP_PROVIDERS } from '#angular/http';
import { provideStore } from '#ngrx/store'
import { AppComponent, environment } from './app/';
import { vehicles } from './app/shared/reducers/vehicles'
if (environment.production) {
enableProdMode();
}
bootstrap(AppComponent,
[
HTTP_PROVIDERS,
provideStore(vehicles, {
vins: [],
vehicles: {}
})
]
);
VehiclesService
import {Http, Headers} from '#angular/http';
import {Injectable} from '#angular/core';
import {Store} from '#ngrx/store';
import {Observable} from "rxjs/Observable";
import 'rxjs/add/operator/map';
import {Vehicle} from '../models/vehicle.model';
const BASE_URL = 'http://localhost:3000/vehicles/';
const HEADER = { headers: new Headers({ 'Content-Type': 'application/json' }) };
#Injectable()
export class VehiclesService {
vehicles: Observable<Array<Vehicle>>;
constructor(private http: Http, private store: Store<Vehicle[]>) {
this.vehicles = this.store.select('vehicles');
}
loadVehicles() {
this.http.get(BASE_URL)
.map(res => res.json())
.map(payload => ({ type: 'LOAD_VEHICLES', payload: payload }))
.subscribe(action => this.store.dispatch(action));
}
}
AppComponent
import { Component, OnInit, Input } from '#angular/core';
import { Observable } from "rxjs/Observable";
import { Store } from '#ngrx/store';
import { Vehicle, VehiclesService } from './shared';
#Component({
moduleId: module.id,
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.css'],
providers: [VehiclesService]
})
export class AppComponent implements OnInit{
title = 'app works!';
vehicles: Observable<Vehicle[]>;
constructor(private vehiclesService: VehiclesService) {
this.vehicles = vehiclesService.vehicles;
vehiclesService.loadVehicles();
}
ngOnInit() {
console.log(this.vehicles)
this.vehicles.subscribe(vehicles => console.log(vehicles));
}
}
But when it runs, I get a TypeError TypeError: Cannot read property 'vehicles' of undefined
The first console.log returns a Store object, but the subscription seems to fail.
Any idea what I'm doing wrong?

You need to change your provideStore to be provideStore({vehicles: vehicles}).

In my case, the error was the use of different names in the reducer object and the interface:
import { LeaderboardParticipant } from './...';
import * as fromLeaderboard from './...';
export interface IState {
leaderboard: fromLeaderboard.IState;
}
export const reducers = {
leaderboard: fromLeaderboard.reducer // I used to have 'search' instead of 'leaderboard'
};
export function selectChallengeId(state: IState): string {
return state.leaderboard.challengeId;
}
export function selectFilterTerms(state: IState): string {
return state.leaderboard.filterTerms;
}

There is not enough information: template app.component.ts is missing in the question.
However, check how do you access the member vehicles in your template. Try operator ?. instead of .. Something like:
{{ yourObject?.vehicles }}

When you declare your Store (Store), I think the right way is:
constructor(private http: Http, private store: Store<VehicleState>)

Related

MergeMap in Ngrx Effect cause types error

i have NgRX effect class which i think is almost the same as in docs.
import { Injectable } from '#angular/core';
import { Actions, createEffect, ofType } from '#ngrx/effects';
import { EMPTY } from 'rxjs';
import { map, mergeMap, catchError } from 'rxjs/operators';
import { SocialLearningFacadeService } from '../../facades/social-learning-facade.service';
import { loadCategories, retreivedCategories } from '../actions/categories';
#Injectable()
export class CategoriesEffects {
loadMovies$: any = createEffect((): any =>
this.actions$.pipe(
ofType(loadCategories),
mergeMap(() =>
this.socialLearningFacadeService.getSocialLearningCategories().pipe(
map((categories) =>
retreivedCategories({ socialLearningCategories: categories })
),
catchError(() => EMPTY)
)
)
)
);
constructor(
private actions$: Actions,
private socialLearningFacadeService: SocialLearningFacadeService
) {}
}
my actions:
import { createAction, props } from '#ngrx/store';
import type { SocialLearningCategory } from '../../../../core/models/socialLearningCategory';
export const loadCategories = createAction('load categories');
export const retreivedCategories = createAction(
'retreived social learning categories',
props<{ socialLearningCategories: any }>()
);
and there is this error on MergeMap:
I have no clue how to interprete this error, espacially that my example is very similar to the docs one and still doesn't work.
EDIT: Social learning facade service
import { Observable } from 'rxjs';
import { Injectable } from '#angular/core';
import { SocialLearningService } from '../services/social-learning.service';
import type { SocialLearningCategory } from '../../../core/models/socialLearningCategory';
import type { SocialLearningCategoryDetails } from '../../../core/models/socialLearningCategoryDetails';
#Injectable({
providedIn: 'root',
})
export class SocialLearningFacadeService {
constructor(private socialLearningService: SocialLearningService) {}
public getSocialLearningCategories(): Observable<SocialLearningCategory[]> {
return this.socialLearningService.getSocialLearningCategories();
}
public getSocialLearningCategoryDetails(
id: number
): Observable<SocialLearningCategoryDetails> {
return this.socialLearningService.getSocialLearningCategoryDetails(id);
}
}

Unable to filter on query parameter for contentful JS SDK methods

I am trying to get back only a certain content_type with by building a dynamic contentful service that could be re-used for different methods in the JS SDK. The filter on the content type - 'product' or sys.id is not working.
Service File:
import { Injectable } from '#angular/core';
import { createClient, Entry} from 'contentful';
import { environment } from '../environments/environment';
import { Observable, from} from 'rxjs';
import { map } from 'rxjs/operators';
export interface QueryObj {
content_type: string;
select?: string;
}
#Injectable({
providedIn: 'root'
})
export class ContentfulService {
private client = createClient({
space: environment.contentful.spaceId,
accessToken: environment.contentful.token
});
queries: QueryObj[];
constructor() {
}
getContentfulEntries(): Observable<QueryObj[]> {
const contentEntries = this.client.getEntries(this.queries);
return from(contentEntries).pipe
(map ((res: any) => res.items));
}
Controller File:
import {Component, OnDestroy, OnInit} from '#angular/core';
import {ContentfulService} from '../services/contentful.service';
import { Subscription} from 'rxjs';
import { QueryObj} from '../services/contentful.service';
#Component({
selector: 'r-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit, OnDestroy {
title = 'Setup Environment';
private entriesSubscription: Subscription;
queries: QueryObj[] = [
{
content_type: 'product',
select: 'sys.id'
}
];
constructor(private contentfulService: ContentfulService) {}
ngOnInit() {
this.entriesSubscription = this.contentfulService.getContentfulEntries()
.subscribe((queries: QueryObj[]) => {
this.queries = queries;
console.log(this.queries);
});
}
ngOnDestroy () {
this.entriesSubscription.unsubscribe();
}
}
I'm not too familiar with Angular and the related TypeScript but are you passing an Array to getEntries? getEntries excepts only a query object. :)
getContentfulEntries(): Observable<QueryObj[]> {
// this.queries is a collection or? 👇🏻
const contentEntries = this.client.getEntries(this.queries);
return from(contentEntries).pipe
(map ((res: any) => res.items));
}

Angular 5 component expecting an argument

Im trying a simple profile app, and all the sudden Im getting error TS2554
ERROR in /app/components/profile/profile.component.ts(25,3): error TS2554: Expected 1 arguments, but got 0.
import { Component, OnInit } from '#angular/core';
import { AuthService } from '../../services/auth.service';
import { FlashMessagesService } from 'angular2-flash-messages';
import { Router } from '#angular/router';
#Component({
selector: 'app-profile',
templateUrl: './profile.component.html',
styleUrls: ['./profile.component.css']
})
export class ProfileComponent implements OnInit {
user: Object;
constructor(
private auth: AuthService,
private flashMsg: FlashMessagesService,
private router: Router
) {
}
ngOnInit() {
this.auth.getProfile().subscribe( profile => {
this.user = profile.user;
},
err => {
return false;
});
}
}
auth.service.ts
import { Injectable } from '#angular/core';
import { Http, Headers } from '#angular/http';
import 'rxjs/add/operator/map';
import { tokenNotExpired } from 'angular2-jwt';
#Injectable()
export class AuthService {
authToken: any;
user: any;
constructor(
private http: Http
) {
}
getProfile(user) {
let headers = new Headers();
this.loadToken();
headers.append('Authorization', this.authToken);
headers.append('Content-Type','application/json');
return this.http.get('http://localhost:3000/users/profile', {headers:headers})
.map(res => res.json());
}
loadToken() {
const token = localStorage.getItem('id_token');
this.authToken = token;
}
}
Your getProfile is expecting an argument named user but you are not passing it from the component
You need to pass an argument as follows,
this.auth.getProfile(user).subscribe( profile => {
this.user = profile.user;
},
err => {
return false;
});
or if you don't need an argument , remove it from your service method.

(Angular2) JSON data (http.get()) is undefined, and data is not updated in the component

My http-data.service accepts json for output in the component template. Initially, the console shows that the first few calls are given undefined, and the following calls are already taking json, but also if you check the component, then the component shows that the method that outputs the data to the component is called only once and since the data has not yet arrived it writes undefined , But not updated after the arrival of json. Help please understand why? Thank you
My http-data.service:
import {Injectable} from '#angular/core';
import {Http} from '#angular/http';
import {Response} from '#angular/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';
#Injectable()
export class HttpService{
constructor(private http: Http) {}
getDataOrganizations(): Observable<any[]>{
return this.http.get('http://localhost:3010/data')
.map((resp:Response)=>{
let dataOrganizations = resp.json().organization;
return dataOrganizations;
});
}
getDataModules(): Observable<any[]> {
return this.http.get('http://localhost:3010/data')
.map((resp: Response)=> {
let dataModules = resp.json().modules;
return dataModules;
});
}
getDataPresets(): Observable<any[]> {
return this.http.get('http://localhost:3010/data')
.map((resp: Response)=> {
let dataPresets = resp.json().presets;
return dataPresets;
});
}
getDataModuleItems(): Observable<any[]> {
return this.http.get('http://localhost:3010/data')
.map((resp: Response)=> {
let dataModuleItems = resp.json().module_items;
return dataModuleItems;
});
}
}
My data-all.service
import { Injectable, EventEmitter } from '#angular/core';
import {Response} from '#angular/http';
import { ModuleModel } from './model-module';
import { ModuleItemsModel } from './model-module-items';
import data from '../data/data-all';
import { PriceService } from './price.service';
import { HttpService } from './http-data.service';
#Injectable()
export class ModuleDataService {
constructor(private priceService: PriceService, private httpService: HttpService){
this.dataMinMaxSum = {minSum: 0, maxSum: 0}
}
private currentPopupView: EventEmitter<any> = new EventEmitter<any>();
private dataModules: ModuleModel[] = this.getDataModules();
private dataMinMaxSum: {};
private dataCalculateVariationOrg: any[];
private dataChangeExecutor: any[];
subscribe(generatorOrNext?: any, error?: any, complete?: any) {
this.currentPopupView.subscribe(generatorOrNext, error, complete);
}
calculte(){
return this.priceService.getDataPrice();
}
getDataModules(){
this.httpService.getDataModules().subscribe(((modules)=>{this.dataModules = modules; console.log(this.dataModules);}));
console.log('dataModules');
console.log(this.dataModules);
return this.dataModules;
}
---------------------------------------------------------------------------
}
My left-block.component
import { Component, OnInit} from '#angular/core';
import { ModuleDataService } from '../../service/data-all.service';
import { ModuleModel } from '../../service/model-module';
#Component({
moduleId: module.id,
selector: 'modules-left-block',
templateUrl: './modules-left-block.html',
styleUrls: ['modules-left-block.css']
})
export class ModuleLeft implements OnInit{
modules: ModuleModel[];
constructor(private modulesAll: ModuleDataService){}
ngOnInit(){
this.modules = this.modulesAll.getDataModules();
console.log("view");
console.log(this.modulesAll.getDataModules());
}
onToggle(module: any){
this.modulesAll.toggleModules(module);
}
}
My left-block.component.html
<div class="modules-all">
<div class="modules-all-title">Все модули</div>
<div class="module-item" *ngFor="let module of modules" [ngClass]="{ 'active': module.completed }" (click)="onToggle(module)">{{module?.title}}</div>
</div>
In the component this.modulesAll.getDataModules () method is why it is executed only once without updating (write in console => undefined), if there are any thoughts, write, thanks.
This behaviour is due to the .subscribe() method does not wait for the data to arrive and I'm guessing you already know this. The problem you're facing is because, you have .subscribe to the getDataModules() service in the wron place. You shouldn't subscribe to a service in another service (at leat in this case). Move the subscribe method to the left-block.component and it should work.
getDataModules() {
this.httpService.getDataModules().subscribe(((modules) => {
this.dataModules = modules;
console.log(this.dataModules);
}));
console.log('dataModules');
console.log(this.dataModules);
return this.dataModules;
}
It should look somethig like this:
#Component({
moduleId: module.id,
selector: 'modules-left-block',
templateUrl: './modules-left-block.html',
styleUrls: ['modules-left-block.css']
})
export class ModuleLeft implements OnInit {
modules: ModuleModel[] = new ModuleModel();
constructor(private modulesAll: ModuleDataService, private httpService: HttpService) {}
ngOnInit() {
this.getDataModles();
//this.modules = this.modulesAll.getDataModules();
console.log("view");
//console.log(this.modulesAll.getDataModules());
}
onToggle(module: any) {
this.modulesAll.toggleModules(module);
}
getDataModules(): void {
this.httpService.getDataModules().subscribe(((modules) => {
this.modules = modules;
console.log(this.dataModules);
}));
}
}

Restricting List of Items by User Account ID

Using Angular 4, Ngrx Store & AngularFire2
I am having real problems understanding how I can restrict a list of items from Firebase based on the currently logged in user account id.
I am using ngrx as well including ngrx effects.
The steps I need to follow are:
• Get Current Users UID – Auth Object
• Get User Object based on the UID from step above
• Get Company List based on the User Account ID Above
My problem is that because I am calling firebase as an Observable the call to company list is being made before I complete the first two steps.
The code is as per below, if someone can assist that would be appreciated:
The problem is in the getEntityList Method on the generic firebase service - I have marked where the problem is
Company Component
import { Component, OnInit, ChangeDetectionStrategy } from '#angular/core';
import { Observable } from 'rxjs/Rx';
import { Store } from '#ngrx/store';
import { AppState } from './../../../core/models/index';
import { CompanyModel } from './../../../core/models/index';
import { getCompanies} from './../../../core/store/actions/company.actions';
#Component({
selector: 'mj-company',
templateUrl: './company.component.html',
styleUrls: ['./company.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CompanyComponent implements OnInit {
entityList$: Observable<CompanyModel[]>;
constructor(private store: Store<AppState>) {
this.entityList$ = this.store.select(state => state.companies);
}
ngOnInit() { this.store.dispatch(getCompanies()); }
}
Company Actions
import { CompanyModel } from './../../models';
import { Action } from '#ngrx/store';
export const ActionTypes = {
GET_COMPANIES: 'GET_COMPANIES',
GET_COMPANIES_SUCCESS: 'GET_COMPANIES_SUCCESS',
GET_COMPANIES_ERROR: 'GET_COMPANIES_ERROR'
};
export function getCompanies() {
return {
type: ActionTypes.GET_COMPANIES,
entityRef: 'companys'
}
}
}
Company Reducer
import { ActionReducer, Action } from '#ngrx/store';
import { ActionTypes } from '../actions/company.actions';
import { CompanyModel } from '../../models';
export function companyReducer(state = [<CompanyModel>{}], action: Action) {
switch (action.type) {
case ActionTypes.GET_COMPANIES:
return action.payload;
case ActionTypes.GET_COMPANIES_SUCCESS:
return action.payload;
case ActionTypes.GET_COMPANIES_ERROR:
return action.payload;
default:
return state;
}
};
Company Effect
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Rx';
import { ActionTypes } from '../actions/company.actions';
import { Actions, Effect } from '#ngrx/effects';
import { FirebaseDataService } from './../../services/firebase-data.service';
#Injectable()
export class CompanyEffects {
constructor(
private actions$: Actions,
private firebaseDataService: FirebaseDataService
) { }
// tslint:disable-next-line:member-ordering
#Effect() getCompanies$ = this.actions$
.ofType(ActionTypes.GET_COMPANIES)
.switchMap(action =>
this.firebaseDataService.getEntityList(action.entityRef)
.map(companies => ({ type: ActionTypes.GET_COMPANIES_SUCCESS, payload: companies }))
.catch(() => Observable.of({ type: ActionTypes.GET_COMPANIES_ERROR })));
Firebase Generic Data Service
import { Injectable } from '#angular/core';
import { AngularFireDatabase, FirebaseListObservable, FirebaseObjectObservable } from 'angularfire2/database';
import { Observable } from 'rxjs/Rx';
import { AuthService } from './auth.service';
import { FirebaseUtilityService } from './../../core/services/firebase-utility.service';
import { UserModel, CompanyModel } from './../models/index';
#Injectable()
export class FirebaseDataService {
$key: string;
loginId: string;
currentUser: any;
constructor(private db: AngularFireDatabase,
private authService: AuthService,
private firebaseService: FirebaseUtilityService) { }
// Return an observable list with optional query
getEntityList(firebaseRef: string, query = {}): FirebaseListObservable<any[]> {
this.loginId = this.authService.currentUserId;
// I get this instantly which is good
console.log('logId: ', this.loginId);
this.currentUser = this.db.object('users/' + this.loginId);
console.log('accountId: ', this.currentUser.accountId);
// This is where the problem is because at this stage the subscription above is not complete so accountId is undefined.
return this.db.list(firebaseRef, {
query: {
orderByChild: 'accountId',
equalTo: this.currentUser.accountId
}
});
// return this.db.list(firebaseRef, query);
}
// Return a single observable item
getEntity(firebaseRef: string, key: string): FirebaseObjectObservable<any> {
const itemPath = `${firebaseRef}/${key}`;
return this.db.object(itemPath)
}
// Default error handling for all actions
private handleError(error) {
console.log(error)
}
}
Problem solved - using switchMap
return this.currentUser.switchMap(user => {
return this.db.list(firebaseRef, {
query: {
orderByChild: 'accountId',
equalTo: user.accountId
}
});
})

Categories

Resources