ng store doesn't save state when called - javascript

I managed to get data from the server
but it is not possible to save the data in the store
when called, the default value is returned
and in redux Devtols there are no values ​​that could be displayed
what am I doing wrong? I don't understand for 2 days
html document - empty
app.component.ts :
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
book$: Observable<{data: Book} | {}> = this.store$.select(selectData);
constructor(private store$: Store<{book: {data: Book}}>) {}
ngOnInit() {
this.store$.dispatch(GET_DATA());
}
getBook() {
this.book$.subscribe(book => console.log(book));
}
}
store.service.ts:
#Injectable({
providedIn: 'root'
})
export class StoreService {
constructor(private http:HttpClient) {
this.DataLoad();
}
DataLoad():Observable<Book> {
return (this.http.get('https://gutendex.com/books') as Observable<Book>);
}
}
globalStore.action.ts:
import {createAction} from '#ngrx/store';
import { Book } from 'src/app/interface/interface';
export const DATA_KEY = 'book';
export const initialState: {data: Book} = {
data: {
count: 0,
next: '',
previous: null,
results: []
}
};
export const DATA_LOAD = createAction('[DATA] DATA_LOAD', (book:Book) => {
return {data: book}
});
export const GET_DATA = createAction('[DATA] GET_DATA');
globalStore.reducer.ts:
import {ActionReducerMap, createReducer, on} from '#ngrx/store';
import { Book } from 'src/app/interface/interface';
import { DATA_KEY, DATA_LOAD, GET_DATA, initialState } from './globalStore.action';
export const BookReducer = createReducer(
initialState,
on(DATA_LOAD, (state, data:any) => {
return {data: data.data}
}),
on(GET_DATA, (state: {data: Book}) => {
return {data: state.data}
})
);
export interface State {
[DATA_KEY]: any;
}
export const reducers: ActionReducerMap<State> = {
[DATA_KEY]: BookReducer,
};
globalStore.selector.ts:
import { createFeatureSelector, createSelector } from "#ngrx/store";
import { Book } from "src/app/interface/interface";
import { DATA_KEY } from "./globalStore.action";
export const selectDataFeature = createFeatureSelector<{data: Book}>(DATA_KEY);
export const selectData = createSelector(selectDataFeature, (state: {data: Book}) => state);

Please subscribe http request to trigger it, like below
this.DataLoad().subscribe(book => {
this.store$.dispatch(DataLoad(book));
});
It's better to add data fetching inside a effect class, but not in constructor of StoreService. Like below:
createEffect(() => this.actions$.pipe(
ofType('[DATA] GET_DATA'),
mergeMap(() => this.storeService.DataLoad()
.pipe(
map(books => DATA_LOAD(books)),
catchError(() => EMPTY)
))
)
);

Related

Firebase action.payload returns id, but data is undefined

In my Firebase Cloud Firestore, I have a collection of galleries, and each gallery document has a subcollection of images.
screnshoot Cloud Firestore
screnshoot Cloud Firestore
I was able to retrieve and display the galleries and the images inside each gallery, but when I try to retrieve the single image document, I get only the ID and the data is undefined (so it's like the name and the url of the image don't exist)
Here my image.service.ts
import { Injectable } from '#angular/core';
import {
AngularFirestore,
AngularFirestoreCollection,
AngularFirestoreDocument,
} from '#angular/fire/firestore';
import { AngularFireStorage } from '#angular/fire/storage';
import { finalize } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Image } from '../models/Image';
#Injectable({
providedIn: 'root',
})
export class ImageService {
images: Observable<Image[]>;
image: Observable<any>;
imageCollection: AngularFirestoreCollection<Image>;
imageDoc: AngularFirestoreDocument<Image>;
url!: string;
name: string;
constructor(
private afs: AngularFirestore,
private storage: AngularFireStorage
) {}
getGalleryImages(id: string | any): Observable<Image[]> {
this.imageCollection = this.afs.collection(`galleries/${id}/images`);
this.images = this.imageCollection.snapshotChanges().pipe(
map((changes) => {
return changes.map((action) => {
const data = action.payload.doc.data() as Image;
console.log(data);
const id = action.payload.doc.id;
return { id, ...data };
});
})
);
return this.images;
}
getImageDetail(id: string | any) {
this.imageDoc = this.afs.doc(`/images/${id}`);
this.image = this.imageDoc.snapshotChanges().pipe(
map((action) => {
const data = action.payload.data();
const id = action.payload.id;
console.log(data, id);
return { id, ...data };
})
);
return this.image;
}
}
Here my image-detail.component.ts
import { Component, OnInit } from '#angular/core';
import {
AngularFirestore,
AngularFirestoreCollection,
AngularFirestoreDocument,
} from '#angular/fire/firestore';
import { Router, ActivatedRoute } from '#angular/router';
import { ImageService } from '../../../services/image.service';
import { Image } from '../../../models/Image';
#Component({
selector: 'app-image-detail',
templateUrl: './image-detail.component.html',
styleUrls: ['./image-detail.component.css'],
})
export class ImageDetailComponent implements OnInit {
image: Image | any;
id: string;
name: string | any;
url: string;
constructor(
private route: ActivatedRoute,
private imageService: ImageService,
private afs: AngularFirestore
) {}
ngOnInit(): void {
this.getImage();
}
getImage() {
const id = this.route.snapshot.paramMap.get('id');
this.imageService.getImageDetail(id).subscribe((image) => {
this.image = image;
console.log(image);
return image;
});
}
}
I even tried this but the result is the same
getImageDetail(id: string | any): Observable<Image[]> {
this.imageDoc = this.afs.doc(`/images/${id}`);
this.image = this.imageDoc.snapshotChanges().pipe(
map((action) => {
const data = action.payload.data() as Image
const id = action.payload.id;
console.log(data, id);
return { id, ...data };
})
);
return this.image;
}
Someone is able to tell me what I'm doing wrong?
The two ids must be different:
-the first one should be the id of the gallery doc and
-the second one should be the id of the image doc.
this.imageDoc = this.afs.doc(`/galleries/${galleryId}/images/${id}`);

Javascript push function

I want to create simple social media app.I'am working now on part with groups.But I cant filter only groups where some user is member.The code is following
import { Component, OnInit, OnChanges } from '#angular/core';
import { AngularFireDatabase } from '#angular/fire/database';
import { GroupsService } from '../groups.service';
#Component({
selector: 'app-groups',
templateUrl: './groups.component.html',
styleUrls: ['./groups.component.scss']
})
export class GroupsComponent implements OnInit {
uid = localStorage.getItem('uid')
groups: Array<any>;
mygroups: Array<any>;
sgroups;
constructor(private db: AngularFireDatabase, private _groups: GroupsService) {
}
ngOnInit() {
this._groups.getGroups().subscribe((data) => {
this.groups = data;
})
this.loadGroups()
}
search(e) {
this.sgroups = this.groups.find(gr => gr.name.toLowerCase().indexOf(e.target.value.toLowerCase()) > -1)
}
loadGroups() {
this.groups.map(gr => {
this._groups.getGroupMembers(gr.id).subscribe((data: any) => {
data.map(mem => {
if(mem.uid == this.uid) {
this.mygroups.push(gr); //here is the problem
}
})
})
})
}
scrollnav() {
document.body.scrollTop = 0;
document.documentElement.scrollTop = 0;
}
}
Every help is welcomed.
Thanks a lot!
Problem is in initialization. intialize mygroups like
mygroups: any[] = [];
instead of
mygroups: Array<any>;
You can use forkJoin to fire all of the calls at once and get an array with all the results:
import {forkJoin} from 'rxjs';
...
const requests = this.groups.map(gr => this._groups.getGroupMembers(gr.id));
forkJoin(requests).subscribe((res) => this.mygroups = res);

Cannot read property 'toLowerCase' of undefined in angular when filtering products

I am having this problem in component Type { data: Product[]; id: string; }[]' is not assignable to type 'Product[].
Type { data: Product[]; id: string; } is missing the following properties from type 'Product': title, price, category, imageUrl. When i am trying to assign the values returned from database to
this.filterdProduct = this.Product = products;
In Constructor...
Here is my code: Interface
export interface Product{
title:string;
price:number;
category:string;
imageUrl:string;
}
Service.Ts:
import { Product } from './../../new-products';
import { Observable } from 'rxjs';
import { AngularFireDatabase } from '#angular/fire/database';
import { Injectable } from '#angular/core';
import { map } from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class ProductService {
constructor(private db: AngularFireDatabase){}
create(product){
return this.db.list('/products').push(product);
}
getAll() {
return this.db.list<Product[]>('/products').snapshotChanges()
.pipe(
map(a =>
a.map(
ac => {
const data= ac.payload.val();
const id = ac.key;
// console.log(data);
// console.log(id)
return {data,id}
} )
)
);
}
Component.ts:
import { Product } from './../../new-products';
import { ProductService } from './../Services/product.service';
import { AngularFireDatabase } from '#angular/fire/database';
import { Component, OnInit, OnDestroy } from '#angular/core';
import { Subscription } from 'rxjs';
#Component({
selector: 'app-admin-products',
templateUrl: './admin-products.component.html',
styleUrls: ['./admin-products.component.css']
})
export class AdminProductsComponent implements OnInit, OnDestroy {
Product:Product []= [];
filterdProduct:any = [];
subscription: Subscription;
constructor(private pd:ProductService){
this.subscription = this.pd.getAll().subscribe(products => {
this.filterdProduct = this.Product = products;
})
}
filter(query:string){
this.filterdProduct = (query) ?
this.Product.filter(p => p.title.toLowerCase().includes(query.toLowerCase())) : this.Product;
console.log(query)
}
ngOnInit(){
}
ngOnDestroy(){
this.subscription.unsubscribe();
}
}
The problem is in your getAll function. You are returning an object of type {data, id}. You should return data as your error signifies that you are expected to return an array of Product.
getAll service function return a object, it's not a Product[], but { Product[], id}
ac => {
const data= ac.payload.val();
const id = ac.key;
// console.log(data);
// console.log(id)
return {data,id} <-- Here
} )
It has to return a Product[].
Product:Product []= [];
in AdminProductsComponent field should be renamed to product and not Product as it clashes with interface name so it should be like this -
product: Array<Product> = [];
What is preventing you from using following code?
const data = ac.payload.val();
data.id = ac.key;
return data;

How can I effectively reset a state using #ngrx/store?

I seem to have gotten stuck on this matter for the last couple of days.
We're working on an Angular 2 application, and I need to create a wizard for users to fill out a form.
I've successfully managed to make the data flow through each step of the wizard, and save it in order to freely move back and forth. However, I can't seem to be able to reset it once the form is submitted.
I should add that each component is behind a wall. Maybe a better solution would be a singleton service injected directly at the AppModule. But I can't seem to make it work.
Here's my code so far:
Step 1
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
import { Router } from '#angular/router';
import { EventOption } from '../../../events/shared/event-option.model';
import { Store } from '#ngrx/store';
import { NewEventService } from '../shared/new-event.service';
import { Event } from '../../../events/shared/event.model';
import { FriendService } from '../../../friends/shared/friend.service';
#Component({
selector: 'app-upload-images',
templateUrl: './upload-images.component.html',
styleUrls: ['../../../events/new-event/new-event.component.css']
})
export class UploadImagesComponent implements OnInit {
form: FormGroup;
private event;
private images = [];
constructor(
private _store: Store<any>,
private formBuilder: FormBuilder,
private router: Router,
private newEventService: NewEventService,
private friendService: FriendService
) {
_store.select('newEvent').subscribe(newEvent => {
this.event = newEvent;
})
}
ngOnInit() {
this.initForm(this.event);
if (this.event.counter === 0) {
let friends = this.friendService.getFriends('58aaf6304fabf427e0acc08d');
for (let friend in friends) {
this.event.userIds.push(friends[friend]['id']);
}
}
}
initForm(event: Event) {
this.images.push({ imageUrl: 'test0', voteCount: 0 });
this.images.push({ imageUrl: 'test1', voteCount: 0 });
this.images.push({ imageUrl: 'test2', voteCount: 0 });
this.images.push({ imageUrl: 'test3', voteCount: 0 });
this.form = this.formBuilder.group({
firstImage: [this.event.length > 0 ? this.event.eventOption[0].imageUrl : null],
secondImage: [this.event.length > 0 ? this.event.eventOption[1].imageUrl : null],
thirdImage: [this.event.length > 0 ? this.event.eventOption[2].imageUrl : null],
fourthImage: [this.event.length > 0 ? this.event.eventOption[3].imageUrl : null],
})
}
next() {
this.event.eventOptions = this.images;
this.newEventService.updateEvent(this.event);
this.router.navigate(['events/new-event/choose-friends']);
}
}
Step 2
import { Component, OnInit, Input } from '#angular/core';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
import { Router } from '#angular/router';
import { EventOption } from '../../../events/shared/event-option.model';
import { Store } from '#ngrx/store';
import { Event } from '../../shared/event.model';
import { NewEventService } from '../shared/new-event.service';
import { FriendService } from '../../../friends/shared/friend.service';
import { SearchPipe } from '../../../core/search.pipe';
#Component({
selector: 'app-choose-friends',
templateUrl: './choose-friends.component.html',
styleUrls: ['../../../events/new-event/new-event.component.css', './choose-friends.component.css']
})
export class ChooseFriendsComponent implements OnInit {
private searchTerm = '';
private event;
private friends = [];
private friendsError = false;
constructor(
private _store: Store<any>,
private formBuilder: FormBuilder,
private router: Router,
private newEventService: NewEventService,
private friendService: FriendService
) {
_store.select('newEvent').subscribe(newEvent => {
this.event = newEvent;
})
}
ngOnInit() {
this.friends = this.friendService.getFriends('58aaf6304fabf427e0acc08d');
}
selectedFriend(friendId: string) {
return this.friendService.selectedFriend(friendId, this.event.userIds);
}
toggleFriend(friendId: string) {
return this.friendService.toggleFriend(friendId, this.event.userIds);
}
toggleAllFriends() {
return this.friendService.toggleAllFriends(this.friends, this.event.userIds);
}
submit() {
if (this.event.userIds.length > 0) {
this.newEventService.resetEvent();
this.router.navigate(['events/vote-events']);
} else {
this.friendsError = true;
}
}
back() {
this.newEventService.updateEvent(this.event);
this.router.navigate(['events/new-event/upload-images']);
}
}
Event Service
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { Store, Action } from '#ngrx/store';
import { Event } from '../../../events/shared/event.model';
import { EventOption } from '../../../events/shared/event-option.model';
import { newEvent, newEventModel } from './new-event.reducer';
import 'rxjs/add/operator/take';
import 'rxjs/add/operator/find';
import { Subject } from 'rxjs/Subject';
#Injectable()
export class NewEventService {
public newEvent$: Observable<newEventModel>;
constructor(private store: Store<newEventModel>) {
this.newEvent$ = this.store.select('newEvent');
}
getEvent(event) {
return this.store.dispatch({
type: 'GET_EVENT',
payload: event
})
}
updateEvent(event) {
return this.store.dispatch({
type: 'UPDATE_EVENT',
payload: event
})
}
resetEvent() {
return this.store.dispatch({
type: 'RESET_EVENT',
})
}
}
Event Reducer
import { EventOption } from '../../shared/event-option.model';
import { EventType } from '../../shared/event-type.model';
import { ActionReducer, Action } from '#ngrx/store';
import { Event } from '../../shared/event.model';
import { FriendService } from '../../../friends/shared/friend.service';
export interface newEventModel {
eventOptions: EventOption[];
eventTypeId: number,
duration: number,
comment: string,
privacyId: number,
isGlobal: boolean,
id: string,
userIds: string[],
counter: number
}
let blankState: newEventModel = {
eventOptions: [],
eventTypeId: null,
duration: 1440,
comment: '',
privacyId: 0,
isGlobal: false,
id: '',
userIds: [],
counter: 0
}
let initialState: newEventModel = {
eventOptions: [],
eventTypeId: null,
duration: 1440,
comment: '',
privacyId: 0,
isGlobal: false,
id: '',
userIds: [],
counter: 0
}
export const newEvent: ActionReducer<newEventModel> = (state: newEventModel = initialState, action: Action) => {
// return new state
switch (action.type) {
case 'GET_EVENT':
return state;
case 'UPDATE_EVENT':
action.payload.counter = action.payload.counter + 1;
return action.payload;
case 'RESET_EVENT':
return Object.assign({}, state, {
eventOptions: [],
eventTypeId: null,
duration: 1440,
comment: '',
privacyId: 0,
isGlobal: false,
id: '',
userIds: [],
counter: 0
});
default:
return state;
}
}
I could provide a working plunkr if needed, but I need to create it first.
TLDR: How can I reset the state on #ngrx/store?
Thanks for any help provided!
Noy Levi had the right thinking in her answer to this question, which assigns initialState back into state, however, there is a way to assign initialState for each reducer automatically.
The key concept to understand is that if the value of 'state' passed into a reducer is 'undefined' (not 'null', it needs to be 'undefined') then the reducer will automatically assign into 'state' the initialState provided to the reducer when it was created. Because of this default behavior, you can create a 'metareducer' that recognizes an action, say 'logout', and then passes a state of 'undefined' into all the subsequent reducers called.
This behavior is described well in this article about redux, this article about NgRx, and also in this answer about NgRx.
The relevant code would look like this:
export function logoutClearState(reducer) {
return function (state, action) {
if (action.type === ActionTypes.LOGOUT) {
state = undefined;
}
return reducer(state, action);
};
}
#NgModule({
imports: [
StoreModule.forRoot(reducers, { metaReducers: [logoutClearState] }),
],
declarations: [],
providers: [],
})
You can reset the state to initialState in your reducer by using Object.assign to copy all properties of initialState to a new object.
export const newEvent: ActionReducer<newEventModel> = (state: newEventModel = initialState, action: Action) => {
// return new state
switch (action.type) {
// ...
case 'RESET_EVENT':
return Object.assign({}, initialState);
// ...
}
}
A note on the reducer
The reducer should be a pure function, so should not modify the arguments. Your UPDATE_EVENT requires a little tweak:
case 'UPDATE_EVENT':
let counter = { counter: action.payload.counter + 1 };
return Object.assign({}, action.payload, counter);
The pattern to follow is Object.assign({}, source1, source2, ...) where source1, source2 etc contain properties to be assigned. Properties in source1 are overwritten by duplicate properties in source2 etc.
there is much easier way, you just need to set the initialState instead of state:
const reducer = createReducer(initialState,
on(proofActions.cleanAdditionalInsuredState, (state, action) => ({
...initialState
})),
I'm assuming your RESET_EVENT is suppose to return a fresh object. Though you are filling in the object with your state data and another object:
case 'RESET_EVENT':
return Object.assign({}, state, {
eventOptions: [],
eventTypeId: null,
duration: 1440,
comment: '',
privacyId: 0,
isGlobal: false,
id: '',
userIds: [],
counter: 0
});
The syntax for Object.assign is Object.assign(target, ...sources) and your providing two items as sources: state and the object containing eventOptions, eventTypeId, etc.
Instead you'll want to return Object.assign({}, initialState);
sorry, I took a day off in order to study for some exams. I ended up "solving" it by doing the following:
....
case 'RESET_EVENT':
action.payload.eventOptions = blankState.eventOptions;
action.payload.eventTypeId = blankState.eventTypeId;
action.payload.duration = blankState.duration;
action.payload.comment = blankState.comment;
action.payload.privacyId = blankState.privacyId;
....
return action.payload;
....
It might not be the prettiest or best solution, but at least it works. Thanks for all the help #iblamefish and everyone.

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

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>)

Categories

Resources