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

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.

Related

ng store doesn't save state when called

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

Angular 8 bind ngrx state to component

I am new to Angular and I want to bind some Classnames in my HTML to some values in my states. At first I had only one state, which was only some boolean value and everything worked fine. Now I changed the state to an object, so that I can save more information in it. Although I am using more or less the same approach as before but the classname doesn't change. The state does change. Can someone please tell me what my mistake is and if there is another more appropriate way to do the things I am trying to do please tell me that also.
The angular version is 8.2.14.
This is my code:
reducer.ts:
export type ShoppinCartState = {
CartIsOpen: boolean;
};
export type HamburgerState = {
MenuIsOpen: boolean;
};
export function hamburgerReducer(
state: HamburgerState = { MenuIsOpen: false },
action: Action
) {
switch (action.type) {
case "HAMBURGER.TOGGLE":
console.log("HAMBURGER.Toggle called " + state.MenuIsOpen);
return {
...state,
MenuIsOpen: !state.MenuIsOpen
};
case "HAMBURGER.CLOSE":
return {
...state,
MenuIsOpen: false
};
case "HAMBURGER.OPEN":
return {
...state,
MenuIsOpen: true
};
default:
return state;
}
}
export function shoppingCartReducer(
state: ShoppinCartState = { CartIsOpen: false },
action: Action
) {
switch (action.type) {
case "CART.TOGGLE":
console.log("Tooggle cart called " + state.CartIsOpen);
return {
...state,
CartIsOpen: !state.CartIsOpen
};
default:
return state;
}
}
This is my Component. When the user clicks on the Hamburger Icon one action is dispatched which changes the state. There is also another part which is binded to the state. When the value is true, the classname should be "visible".
<app-hamburger-icon (click)="onHamburgerIconClick()"></app-hamburger-icon>
<div id="Logo"><a (click)="onLinkClick()" routerLink="/">E99-EsAns</a></div>
<ul [ngClass]="{ visible: hamburgerClicked$ | async }">
And that's the component.ts file
import { Store } from "#ngrx/store";
import { Observable } from "rxjs";
import { HamburgerState } from "src/app/reducer";
#Component({
selector: "app-navigationbar",
templateUrl: "./navigationbar.component.html",
styleUrls: ["./navigationbar.component.css"]
})
export class NavigationbarComponent implements OnInit {
hamburgerClicked$: Observable<boolean>;
constructor(private store: Store<HamburgerState>) {
this.hamburgerClicked$ = this.store.select("MenuIsOpen");
}
ngOnInit() {}
onHamburgerIconClick() {
console.log(this.hamburgerClicked$);
this.store.dispatch({ type: "HAMBURGER.TOGGLE" });
}
onLinkClick() {
this.store.dispatch({ type: "HAMBURGER.CLOSE" });
}
onShoppingCartIconClicked() {
this.store.dispatch({ type: "CART.TOGGLE" });
}
}
Some snippet of my app.module.ts
import { hamburgerReducer, shoppingCartReducer } from "./reducer";
...
...
...
imports: [
BrowserModule,
BrowserAnimationsModule,
AppRoutingModule,
HttpClientModule,
StoreModule.forRoot({
hamburgerReducer: hamburgerReducer,
shoppingCartReducer: shoppingCartReducer
})
],
When you inject the Store into a component you operate on the structure of the global state. So the select has to 'select' into this structure to access the correct object.
Only the reducer functions get handed a slice of this global state. We can see that most likely in app.module.ts where you define your store:
StoreModule.forRoot({
hamburgerReducer: hamburgerReducer,
shoppingCartReducer: shoppingCartReducer
})
Best practice is to create a interface State that describes your store structure:
export interface State {
hamburgerReducer: HamburgerState;
shoppingCartReducer: ShoppingCartState;
}
and then use this interface when injecting the store:
constructor(private store: Store<State>)
then you can select the hamburger menu-state via:
this.store.select(state => state.hamburgerReducer.MenuIsOpen)

Object not update by function in the same service Angular

I create a function (callPlayerData) for replace data in object (playerData) by data from database.
The function and the object is in the same service :
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
interface PlayerData {
name: string,
level: number,
truc: number,
xp: number,
xp_max: number,
vie: number,
vie_max: number
}
#Injectable()
export class PlayerService {
constructor(private http: Http){
}
playerId = null;
playerData: PlayerData = {
name : "unwn",
level : 5,
truc : 36,
xp: 0,
xp_max: 0,
vie: 0,
vie_max: 0
};
callPlayerData(callback) {
let that = this,
loader = document.getElementById("loader");
loader.style.display = "block";
this.http.post(`api/gameData`, {id : that.playerId})
.subscribe(response => {
let gameData = JSON.parse(response.text()),
item = 0;
for (const key in gameData) {
if (that.playerData[key]) {
that.playerData[key] = gameData[key];
}
item++;
if(item === Object.keys(gameData).length) {
//that.playerData = Object.assign(JSON.parse(response.text()));
console.log(that.playerData);
setTimeout(() => {
loader.style.display = "none";
callback();
}, 400);
}
}
}, error => {
console.log(JSON.stringify(error.json()));
});
}
}
The console.log(that.playerData) at the end of the ForIn return the data updated but I have another console.log() in component.
import { Component, OnInit } from '#angular/core';
import { Http } from '#angular/http';
import { Router } from '#angular/router';
import { PlayerService } from '../../player.service';
#Component({
selector: 'stats-section',
templateUrl: './stats.component.html'
})
export class StatsComponent implements OnInit {
constructor(private http: Http, private router: Router, private player: PlayerService) {
}
ngOnInit() {
console.log(this.player.playerData);
setTimeout(() => {
console.log(this.player.playerData);
}, 1000);
}
}
This console.log() return the initial data of the object and not the data updated by the function.
Any ideas how to get the updated data in my component ?
thanks in advance !

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

Where to put service providers in angular 2 hierarchy so that components can talk to each other using the same instance of service?

Related question:
Observable do not receive the next value in angular2
No provider for service error in angular2, why do I need to inject it in it's parent component?
Using observable talk to other component in angular2, not receiving coming value
I have a PagesService that has a setCurrentPlaylists function, this function will be triggered from other component, it will receive an value of Playlists type, and will console log this value, using the next function pass to other component( I intent to).
My entire code for pages service is:
import { Injectable } from '#angular/core';
import { ApiService } from '../../apiService/api.service';
import { Platform } from '../../platforms/shared/platform.model';
import { Page } from './page.model';
import { Playlists } from '../shared/playlists.model';
import { Subject, BehaviorSubject } from 'rxjs/Rx';
#Injectable()
export class PagesService {
private currentPlaylists: Subject<Playlists> = new BehaviorSubject<Playlists>(new Playlists());
constructor(private service: ApiService) {
this.currentPlaylists.subscribe((v) => console.log(v, 'subscriber from pages service is printing out the incoming value'));
}
getPages(platform: Platform) {
return this.service.getPages(platform.value);
}
setCurrentPage(page: Page) {
this.service.setCurrentPage(page.pageId);
}
getCurrentPage():string {
return this.service.getCurrentPage();
}
getCurrentPlaylists() {
return this.currentPlaylists;
}
setCurrentPlaylists(playlists: Playlists) {
console.log("Pages Service receive an value of playlists:", playlists);
this.currentPlaylists.next(playlists);
}
}
My code for page component is:
import { Component, OnInit, Input, Output, OnChanges, EventEmitter, Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { Platform } from '../platforms/shared/platform.model';
import { Page } from './shared/page.model';
import { Playlists } from './shared/playlists.model';
import { PagesService } from './shared/pages.service';
import { PlaylistService } from '../playlist/shared/playlist.service';
import { Subject,BehaviorSubject } from 'rxjs/Rx';
#Component({
selector: 'pages',
styleUrls: ['app/pages/pages.css'],
templateUrl: 'app/pages/pages.html',
providers: [PagesService, PlaylistService]
})
export class PagesComponent {
#Input() platform: Platform;
#Output() onPlaylistsChange: EventEmitter<Playlists>;
currentPageName: string;
currentPage: Page;
pages: Array<Page>;
playlists: Playlists;
constructor(private pageServer: PagesService, private playlistService: PlaylistService) {
this.pages = [];
this.currentPage = new Page();
this.pageServer.setCurrentPage(this.currentPage);
this.playlists = new Playlists();
this.onPlaylistsChange = new EventEmitter<Playlists>();
}
ngOnInit() {
this.pageServer.getCurrentPlaylists().subscribe((playlists) => {
console.log('subscriber in pages component is printing out the incoming value', playlists);
this.playlists = playlists;
}, error => {
console.log(error);
});
}
getPages(platform: Platform): void {
this.pageServer.getPages(platform)
.subscribe(
res => {
if (res.pages.length > 0) {
this.pages = [];
for (let page of res.pages) {
if (page.pageName !== "Shows" && page.pageName !== "All Shows" && page.pageName !== "Moives" && page.pageName !== "All Movies") {
this.pages.push(page);
}
}
this.currentPage = this.pages[0];
this.pageServer.setCurrentPage(this.currentPage);
this.currentPageName = this.pages[0].pageName;
this.getPlaylist(this.currentPage, this.platform);
} else {
this.pages = [];
this.currentPage = new Page();
this.pageServer.setCurrentPage(this.currentPage);
this.playlists = new Playlists();
this.onPlaylistsChange.emit(this.playlists);
}
},
error => console.log(error)
);
}
getPlaylist(page: Page, platform: Platform): void {
this.currentPage = page;
this.pageServer.setCurrentPage(this.currentPage);
this.playlistService.getPlaylist(page, platform)
.subscribe(
res => {
if (res.hasOwnProperty('pages') && res.pages.length > 0) {
if (res.pages[0].hasOwnProperty('bodyPlaylists') && res.pages[0].hasOwnProperty('headerPlaylists')) {
this.playlists.bodyPlaylists = res.pages[0].bodyPlaylists || [];
this.playlists.headerPlaylists = res.pages[0].headerPlaylists || [];
} else {
this.playlists.bodyPlaylists = [];
this.playlists.headerPlaylists = [];
this.playlists.wholePlaylists = res.pages[0].playlists || [];
}
this.onPlaylistsChange.emit(this.playlists);
} else {
this.playlists = new Playlists();
this.onPlaylistsChange.emit(this.playlists);
}
},
error => console.error(error)
);
}
ngOnChanges() {
// Get all Pages when the platform is set actual value;
if (this.platform.hasOwnProperty('value')) {
this.getPages(this.platform);
}
}
}
When I trigger the setCurrentPlaylists function, the playlists didn't passed to pages component. I need to use that passed value to update pages component's playlists.
This is the console output after I trigger the setCurrentPlaylsts function. No message from pages components.
Any suggestions are appreciated!
I call setCurrentPlaylists function from this component
/// <reference path="../../../typings/moment/moment.d.ts" />
import moment from 'moment';
import { Component, ViewChild, ElementRef, Input, Output, EventEmitter } from '#angular/core';
import { CORE_DIRECTIVES } from '#angular/common';
import { Http, Response } from '#angular/http';
import { MODAL_DIRECTVES, BS_VIEW_PROVIDERS } from 'ng2-bootstrap/ng2-bootstrap';
import {
FORM_DIRECTIVES,
REACTIVE_FORM_DIRECTIVES,
FormBuilder,
FormGroup,
FormControl,
Validators
} from '#angular/forms';
import { PagesService } from '../../pages/shared/pages.service';
import { ApiService } from '../../apiService/api.service';
#Component({
selector: 'assign-playlist-modal',
providers: [PagesService],
exportAs: 'assignModal',
directives: [MODAL_DIRECTVES, CORE_DIRECTIVES, FORM_DIRECTIVES, REACTIVE_FORM_DIRECTIVES ],
viewProviders: [BS_VIEW_PROVIDERS],
styleUrls: ['app/channel/shared/assignPlaylist.css'],
templateUrl: 'app/channel/modals/assignPlaylistModal.html'
})
export class AssignPlaylistModalComponent {
#ViewChild('assignPlaylistModal') modal: any;
private addPlaylistForm: FormGroup;
private playlistType: string;
private currentPage: string;
private editDate: string;
constructor(private apiService: ApiService, private pagesService: PagesService, fb: FormBuilder) {
this.currentPage = '';
this.editDate = this.apiService.getDate();
this.addPlaylistForm = fb.group({
'longPlaylistName': ['', Validators.required],
'shortPlaylistName': ['', Validators.required],
'startOn': ['', Validators.compose([
Validators.required, this.validTimeFormat
])],
'expireOn': ['', Validators.compose([
Validators.required, this.validTimeFormat
])],
'isExpire': ['']
});
this.addPlaylistForm.controls['startOn'].valueChanges.subscribe((value: string) => {
if (moment(value, 'YYYY-MM-DDThh:mm').isValid()) {
if (this.playlistType == 'dynamic') {
this.apiService.setGlobalStartTime(moment(value).format("YYYYMMDDHHmm"));
}
}
});
this.addPlaylistForm.controls['expireOn'].valueChanges.subscribe((value: string) => {
if (moment(value, 'YYYY-MM-DDThh:mm').isValid()) {
if (this.playlistType == 'dynamic') {
this.apiService.setGlobalEndTime(moment(value).format("YYYYMMDDHHmm"));
}
}
});
}
showModal(type: string) {
this.playlistType = type;
this.currentPage = this.apiService.getCurrentPage();
this.modal.show();
}
validTimeFormat(control: FormControl): { [s: string]: boolean} {
if (!moment(control.value, 'YYYY-MM-DDThh:mm').isValid()) {
return { invalidTime: true};
}
}
setCloseStyle() {
let styles = {
'color': 'white',
'opacity': 1
}
return styles;
}
createNewPlaylist(stDate: string, etDate: string, playlistTitle: string, shortTitle: string, callback?: any):any {
this.apiService.createNewPlaylist(stDate, etDate, playlistTitle, shortTitle)
.subscribe(
data => {
let playlistId = data[0].id;
this.apiService.addPlaylistToPage(playlistId, stDate, etDate, this.apiService.getGlobalRegion(), callback)
.subscribe(
data => {
if (this.apiService.g_platform == 'DESKTOP') {
this.apiService.getPlaylist(this.apiService.getCurrentPage(), 'true' )
.subscribe(
res => {
if (res.hasOwnProperty('pages') && res.pages.length > 0) {
if (res.pages[0].hasOwnProperty('bodyPlaylists') && res.pages[0].hasOwnProperty('headerPlaylists')) {
this.apiService.getCurrentPlaylists().bodyPlaylists = res.pages[0].bodyPlaylists || [];
this.apiService.getCurrentPlaylists().headerPlaylists = res.pages[0].headerPlaylists || [];
console.log('assign playlist component is calling the pages service setCurrentPlaylists function.');
this.pagesService.setCurrentPlaylists(this.apiService.getCurrentPlaylists());
} else {
this.apiService.getCurrentPlaylists().bodyPlaylists = [];
this.apiService.getCurrentPlaylists().headerPlaylists = [];
this.apiService.getCurrentPlaylists().wholePlaylists = res.pages[0].playlists || [];
console.log('assign playlist component is calling the pages service setCurrentPlaylists function.');
this.pagesService.setCurrentPlaylists(this.apiService.getCurrentPlaylists());
}
}
}
);
} else {
this.apiService.getPlaylist(this.apiService.getCurrentPage(), 'false' )
.subscribe(
res => {
if (res.hasOwnProperty('pages') && res.pages.length > 0) {
this.apiService.getCurrentPlaylists().bodyPlaylists = [];
this.apiService.getCurrentPlaylists().headerPlaylists = [];
this.apiService.getCurrentPlaylists().wholePlaylists = res.pages[0].playlists || [];
console.log('assign playlist component is calling the pages service setCurrentPlaylists function.');
this.pagesService.setCurrentPlaylists(this.apiService.getCurrentPlaylists());
}
}
);
}
}
);
},
error => console.log(error)
);
}
onSubmit(form: FormGroup) {
// get start time, the format from input will be like 2016-06-07T00:05
let startTime = moment(form.value.startOn).format("YYYYMMDDHHmm");
let expireTime = moment(form.value.expireOn).format("YYYYMMDDHHmm");
let playlistTitle = form.value.longPlaylistName;
let shortTitle = form.value.shortPlaylistName;
if (this.playlistType == 'smart' || this.playlistType == 'new') {
this.createNewPlaylist(startTime, expireTime, playlistTitle, shortTitle);
}
}
}
This is my component tree:
I am assuming your components tree is as follow:
AssignPlaylistModalComponent (Parent or higher level than PagesComponent in the tree)
PagesComponent (lowest level child as it does not import any directive)
Issue
You should only put your service in the top level (parent) components provider. Though all components still need to do the import and constructor.
Putting the service in a component's provider will create a new copy of the service and share along the component tree downward, not upward.
In the code in question, PagesComponent, as the lowest level child in the tree, with its own provider line, is actually initiating its own copy of PagesService, PlaylistService. So each instance of PagesComponent is basically listening to itself only. It won't receive any messages from others.
Fix
#Component({
selector: 'pages',
styleUrls: ['app/pages/pages.css'],
templateUrl: 'app/pages/pages.html',
providers: [PagesService, PlaylistService] // <--- Delete this line
})
export class PagesComponent {
#Input() platform: Platform;
#Output() onPlaylistsChange: EventEmitter<Playlists>;
Where to put providers
Assume following component tree:
Component A1 (root component)
Component B1
Component C1
Component C2
Component B2
Component C3
Component C4
The easiest way is to put it in A1 providers, all components will be sharing the same service instance, and able to message each other.
If you put it in B1 providers, then only B1, C1 and C2 can talk to each other.
Base on lastest update, the root component of the project is AppComponent.ts. providers should be added in it.
From the code you provided, I cannot see when this method
setCurrentPlaylists(playlists: Playlists) {
console.log(playlists, 'i am here');
this.currentPlaylists.next(playlists);
}
is called. Therefore, your list is empty.
Doing this
this.pageServer.getCurrentPlaylists().subscribe((playlists) => {
console.log(playlists, 'new playlists coming');
this.playlists = playlists;
}, error => {
console.log(error);
});
only creates a subscription to the observable. You need to publish data from somewhere.
In addition, it'd better to move this code
this.pageServer.getCurrentPlaylists().subscribe((playlists) => {
console.log(playlists, 'new playlists coming');
this.playlists = playlists;
}, error => {
console.log(error);
});
to ngOnInit()

Categories

Resources