The dom is not reflective of the actual value wen using onPush strategy with ngrx store subscription - ngrx

component file:
// Angular and 3rd party libs imports
import { ChangeDetectionStrategy, Component, OnInit } from '#angular/core';
import { Store } from '#ngrx/store';
import { UntilDestroy, untilDestroyed } from '#ngneat/until-destroy';
// Utils
import { ApiLoadInfo, ApiStateEnum } from 'src/app/shared/utils/states';
// Services
import { TestPortalService } from '../../../testportal.service';
import { SharedClient } from 'src/app/shared/services/shared.service';
// Redux
import {
CandidateInstructionsState,
Quiz,
Instruction,
PageEnum,
LandingPageData
} from '../redux/candidate-instructions.state';
import * as instructionActions from '../redux/candidate-instructions.action';
import * as instructionSelects from '../redux/candidate-instructions.selector';
import { ActivatedRoute } from '#angular/router';
#UntilDestroy()
#Component({
selector: 'candidate-instructions-landing',
templateUrl: './instructions-landing.component.html',
styleUrls: ['./instructions-landing.component.scss', '../common.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CandidateInstructionsLandingComponent implements OnInit {
// Exposing constants to html template
ApiStateEnum = ApiStateEnum;
PageEnum = PageEnum;
// Variables
initDataLoadState: ApiLoadInfo;
data: LandingPageData;
constructor(private _store: Store<CandidateInstructionsState>,
private _activatedRoute: ActivatedRoute,
private _testPortalService: TestPortalService,
) {
_store
.select(instructionSelects.selectInitDataLoadState)
.pipe(untilDestroyed(this))
.subscribe((initDataLoadState) => {
console.log('is same ref?:', this.initDataLoadState === initDataLoadState)
this.initDataLoadState = initDataLoadState;
console.log(initDataLoadState)
console.log('----------')
});
_store
.select(instructionSelects.selectLandingData)
.pipe(untilDestroyed(this))
.subscribe((data) => {
this.data = data;
});
}
ngOnInit() {
this.loadInstructions();
}
loadInstructions() {
this._store.dispatch(instructionActions.setInitData()); // sets state to 'loading'
this._testPortalService.getTestInstructions(
this._activatedRoute.snapshot.params.quizOrInviteId,
(error, response) => {
if (error) {
// sets state to 'error'
this._store.dispatch(instructionActions.setInitDataFail({ errmsg: error.toString() }));
} else {
// sets state to 'loaded'
this._store.dispatch(instructionActions.setInitDataSuccess({ instructions: response }));
console.log(response);
}
}
);
}
}
html:
{{ initDataLoadState.state }}
console output:
ui:
I thought when onPush is set, the template will re-render if the variable ref is changed. And since redux store is immutable that is always supposed to happen (confirmed by logging in the console). But still the actual component data is not in sync with the UI ie. component value = "loaded" but value in ui = "loading". Why is it so?

If you don't want to or can't use the pushPipe you could do something like this to subscribe to the store data:
import { Component, OnDestroy, OnInit } from '#angular/core';
import { Subscription } from 'rxjs';
import { Store } from '#ngrx/store';
import { getData } from 'path/to/store';
import { YourType } from 'path/to/type';
#Component({
selector: 'subscribing-component',
templateUrl: './subscribing.component.html'
})
export class SubscribingComponent implements OnInit, OnDestroy {
data: YourType;
dataSubscription: Subscription;
constructor(store: Store) {}
ngOnInit(): void {
this.dataSubscription = this.store.select(getData).subscribe((data) => {
this.data = data;
});
}
// don't forget to unsubscribe
ngOnDestroy(): void {
if (this.dataSubscription) {
this.dataSubscription.unsubscribe();
}
}
}

Related

Fetch data from asp.net into highchart

I'm trying to make an application with highchart and asp.net and I followed several tutorials and applications because it's a new thing for me.
But I don't know how to fetch data from asp.net into highcharts.js. I tried to do a getChartData function following one video and then I realised that is for chart.js.Can you give me a idea?
This is my code:
bar-chart.ts
import { Component, OnInit } from '#angular/core';
import * as Highcharts from 'highcharts';
import { SalesDataService } from '../../services/sales-data.service';
import * as moment from 'moment';
#Component({
selector: 'app-bar-chart',
templateUrl: './bar-chart.component.html',
styleUrls: ['./bar-chart.component.css']
})
export class BarChartComponent implements OnInit {
constructor(private _salesDataServices:SalesDataService ){}
orders:any;
orderLabels:string[];
orderData:number[];
public barChartLabels:string[];
public barChartData:any[];
title = 'myHighChartsApp';
highcharts = Highcharts;
public options: any ={
chart : {
type: "column"
},
title: {
text: "Monthly Sales Chart Department Wise"
},
xAxis:{
},
yAxis: {
title:{
text:"Sales in Million $"
}
},
series: []
}
ngOnInit() {
}
getChartData(res:Response){
this.orders=res['page']['data'];
const data=this.orders.map(o =>o.total);
const formattedOrders=this.orders.reduce((r,e) => {
r.push([moment(e.placed).format('YY-MM-DD'),e.total]);
return r;
},[]);
const p=[];
console.log('formattedOrders:',formattedOrders);
const chartData=formattedOrders.reduce((r,e)=>{
const key=e[0];
if(!p[key]){
p[key]=e;
r.push(p[key]);
}else {
p[key][1]+=e[1];
}
return r;
},[]);
return chartData;
}
}
sales-data-service.ts
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import 'rxjs/add/operator/map';
import { Observable } from 'rxjs';
import { Order } from '../shared/order';
#Injectable({
providedIn:'root'
})
export class SalesDataService {
constructor(private _http: Http) { }
getOrders( pageIndex: number, pageSize: number
) {
return this._http.get('http://localhost:5103/api/order/'+ pageIndex + '/' + pageSize
)
.map(res => res.json());
}
}

working with FirestoreDocument

I am working with FirestoreDocument and I am trying to retrieving document data.I am getting the post undefined.Below is my code for service and Component
PostService.ts
import { Injectable } from '#angular/core';
import { AngularFirestore,AngularFirestoreCollection,AngularFirestoreDocument} from 'angularfire2/firestore'
import {Post} from './post';
import { Observable } from 'rxjs';
import 'rxjs/add/operator/map';
#Injectable()
export class PostService {
postsCollection: AngularFirestoreCollection<Post>
postDoc: AngularFirestoreDocument<Post>
constructor(private afs:AngularFirestore) {
this.postsCollection = this.afs.collection('posts',ref => ref.orderBy('published','desc'));
}
getPosts(){
return this.postsCollection.snapshotChanges().map(actions=>{
return actions.map(a=>{
const data=a.payload.doc.data() as Post
const id=a.payload.doc.id
return {id, ...data}
})
})
}
getPostData(id: string){
this.postDoc=this.afs.doc<Post>(`posts/${id}`)
return this.postDoc.valueChanges()
}
}
PostDetailComponent.ts
import { PostService } from './../post.service';
import { Component, OnInit } from '#angular/core';
import {ActivatedRoute} from '#angular/router';
import {Post} from '../post';
#Component({
selector: 'app-post-detail',
templateUrl: './post-detail.component.html',
styleUrls: ['./post-detail.component.css']
})
export class PostDetailComponent implements OnInit {
post: Post
constructor(
private route:ActivatedRoute,
private postService:PostService
) { }
ngOnInit() {
this.getPost();
console.log(this);
}
getPost(){
const id= this.route.snapshot.paramMap.get('id')
return this.postService.getPostData(id).subscribe(data=>this.post=data)
}
}
PostDetailComponent from console
PostDetailComponent {route: ActivatedRoute, postService: PostService}
post:undefined
postService:PostService {afs: AngularFirestore, postsCollection: AngularFirestoreCollection, postDoc: AngularFirestoreDocument}
route:ActivatedRoute {url: BehaviorSubject, params: BehaviorSubject, queryParams: BehaviorSubject, fragment: BehaviorSubject, data: BehaviorSubject, …}
__proto__:Object
My post from PostDetailComponent is undefined.
The rest of your code looks right, I think it's just that your console.log(this) is in the wrong spot.
ngOnInit() {
this.getPost(); <----- your getPost() function is asynchronous
console.log(this); <----- meaning this console.log runs before you get data from Firebase
so when this runs, posts is still undefined.
If you would console.log(this) *AFTER* you get data
from Firebase, then you should see the values OK.
}
getPost(){
const id= this.route.snapshot.paramMap.get('id')
return this.postService.getPostData(id).subscribe(data=>this.post=data)
^^^^^^^^^^^
ASYNC portion is right here
}
To fix it, move your console.log inside your subscription:
ngOnInit() {
this.getPost();
<--- From here
}
getPost(){
const id= this.route.snapshot.paramMap.get('id')
return this.postService.getPostData(id).subscribe(data=> {
this.post=data;
console.log(this); <--- To here
});
}
EDIT - Additional troubleshooting ref comments below.
getPost(){
const id= this.route.snapshot.paramMap.get('id')
console.log('id from route params is: ' + id); <---- log the *id* too
return this.postService.getPostData(id).subscribe(data=> {
this.post=data;
console.log(data); <--- log *data* instead
});
}
Since angular 6 there were changes on rxjs
// replace import 'rxjs/add/operator/map';
with import { map } from 'rxjs/operators';
Then include .pipe() inside PostService.ts file in getPosts() function
like this:
getPosts() {
return this.postCollection.snapshotChanges().pipe(map(actions => {
return actions.map( a => {
const data = a.payload.doc.data() as Post
const id = a.payload.doc.id
return { id, ...data }
})
})
)
}
Home someone helps, cheers !!!

How to test effects in ngrx?

I just want to get my getUser effect below.
I'm using angular5, typescript and ngrx.
I'm open to alternative examples to what I have below.
This is my effect:
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/catch';
import { Effect, Actions } from '#ngrx/effects';
import { HttpErrorResponse } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { map, switchMap } from 'rxjs/operators';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import * as fromUser from '../actions/user-state.actions';
import { UserService } from '../services/user-state.service';
import { UserStateState } from '../models/user-state.interfaces';
#Injectable()
export class UserStateEffects {
#Effect({ dispatch: true })
getUser$: Observable<any> = this.actions$
.ofType(fromUser.GET_USER)
.pipe(
switchMap(() => this.userService.getUser()),
map((result: any) => new fromUser.UserSuccess(result)))
.catch((error: HttpErrorResponse) => of(new fromUser.UserFailure(error)));
constructor(
private actions$: Actions,
private userService: UserService
) {}
}
This is my spec for the effect test:
import 'rxjs/add/observable/throw';
import { Actions } from '#ngrx/effects';
import { cold, hot } from 'jasmine-marbles';
import { empty } from 'rxjs/observable/empty';
import { HttpClientTestingModule } from '#angular/common/http/testing';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { TestBed } from '#angular/core/testing';
import * as actions from '../actions/user-state.actions';
import { UserService } from '#tradingapplication/user-state/src/state/services/user-state.service';
import { UserStateEffects } from '#tradingapplication/user-state/src/state/effects/user-state.effects';
import { UserStateState } from '../models/user-state.interfaces';
import { UserStateConstants } from '../../user-state.constants';
export class TestActions extends Actions {
constructor(){
super(empty());
}
set stream(source: Observable<any>){
this.source = source
}
}
export function getActions() {
return new TestActions();
}
fdescribe('UserStateEffects', function () {
let actions$: TestActions;
let service: UserService;
let effects: UserStateEffects;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ HttpClientTestingModule ],
providers: [
UserService,
UserStateConstants,
UserStateEffects,
{ provide: Actions, useFactory: getActions },
],
});
actions$ = TestBed.get(Actions);
service = TestBed.get(UserService);
effects = TestBed.get(UserStateEffects);
spyOn(service, 'getUser').and.returnValue(of(null));
});
it('should return user from GetUser', () => {
const action = actions.GET_USER;
const completion = new actions.UserSuccess({});
actions$.stream = hot('-a', { a: action });
const expected = cold('-b', { b: completion });
expect(effects.getUser$).toBeObservable(expected);
});
});
You might want to check out the NGRX example app from the NGRX/Google team. It utilizes Jasmine-Marbles for testing streams, as used in NGRX Effects. This way you can test the streams for being closed or not after execution and how many elements passed through them before the expected value arrived. And all of that with just a few dashes (ticks).

invalidpipeargument: '[object object']: error showing

So I am relatively new to Ionic and Firebase, and I am trying to display user data that already exists in Firebase.
Here is the error that I am getting:
invalidpipeargument: '[object object']: error showing.
I do know what the issue is after looking at other questions similar to mine. It seems I cannot assign the async function as database.object does not return an observable. So I have tried to add .valueChanges(); to make it an observable, but I believe this is clashing with the angularfireobject as that is already assigned to the variable profile.data so this would not work.
I have also tried this:
// get (): AngularFireObject<any[]>{
//return this.afDatabase.list`/profile/${data.uid}`;
//}
If you point me in the right direction that would be great.
Here is my code:
.ts file
import { Component } from '#angular/core';
import { NavController, ToastController } from 'ionic-angular';
import { AngularFireAuth } from 'angularfire2/auth';
import { AngularFireDatabase, AngularFireObject} from 'angularfire2/database';
import { Profile } from '../../model/profile';
import { DEFAULT_INTERPOLATION_CONFIG } from '#angular/compiler';
#Component({
selector: 'page-about',
templateUrl: 'about.html'
})
export class AboutPage {
profileData: AngularFireObject<Profile>
// get (): AngularFireObject<any[]>{
//return this.afDatabase.list`/profile/${data.uid}`;
//}
constructor(private afAuth:AngularFireAuth, private afDatabase: AngularFireDatabase,
public navCtrl: NavController, private toast: ToastController) {
}
ionViewDidLoad() {
this.afAuth.authState.take(1).subscribe(data =>{
if (data && data.email && data.uid){
this.toast.create({
message: `Welcome to MatchMyFighter ${data.email}`,
duration: 3000,
}).present();
// this.profileData = this.afDatabase.object(`profile/${data.uid}`)
this.profileData = this.afDatabase.object(`/profile/${data.uid}`).valueChanges();;
}
else{
this.toast.create({
message:`could not autenticate`,
duration:3000
}).present;
}
})
}
}
.html
<p>
Username: {{(profileData | async)?.username}}
</p>
The call to valueChanges() converts the AngularFireObject<Profile> to an observable.
You have to change the type to Observable<Profile> like so:
profileData: Observable<Profile>;
constructor(private db: AngularFireDatabase) { } // other stuff emitted ...
ionViewDidLoad() {
// ...
this.profileData = this.db.object<Profile>(`/profile/${data.uid}`).valueChanges();
// ...
}

Angular-Redux Epics

Getting the following error message in console when using the angular-redux library. Also, Redux won't catch or listen for actions after the error occurs. I've searched, including the documentation but nothing points out to fix the error.
Am I missing something?
Error
core.js:1427 ERROR Error: Actions must be plain objects. Use custom middleware for async actions.
at Object.performAction (<anonymous>:3:2312)
at liftAction (<anonymous>:2:27846)
at dispatch (<anonymous>:2:31884)
at eval (createEpicMiddleware.js:67)
at SafeSubscriber.dispatch [as _next] (applyMiddleware.js:35)
at SafeSubscriber.__tryOrUnsub (Subscriber.js:240)
at SafeSubscriber.next (Subscriber.js:187)
at Subscriber._next (Subscriber.js:128)
at Subscriber.next (Subscriber.js:92)
at SwitchMapSubscriber.notifyNext (switchMap.js:127)
Here's code
Component
import { Component, OnInit } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { select } from '#angular-redux/store';
import { ScheduleActions } from '../store/actions'
#Component({
selector: 'app-page2',
templateUrl: './page2.component.html',
styleUrls: ['./page2.component.css']
})
export class Page2Component implements OnInit {
#select(['schedule', 'scheduleList']) values: any;
constructor(public actions: ScheduleActions) { }
ngOnInit() {
this.actions.loadSchedule();
}
}
Actions
//schedule-actions.ts
import { Injectable } from '#angular/core';
import { NgRedux } from '#angular-redux/store';
import { Schedule } from '../../model/schedule.model';
#Injectable()
export class ScheduleActions {
static readonly LOAD_SCHEDULE = 'LOAD_SCHEDULE';
static readonly LOAD_SCHEDULE_SUCCESS = 'LOAD_SCHEDULE_SUCCESS';
constructor(private ngRedux: NgRedux<any>){}
loadSchedule(){
this.ngRedux.dispatch({
type: ScheduleActions.LOAD_SCHEDULE
});
}
}
Reducer
//schedule-reducer.ts
import { ScheduleActions } from '../actions';
export interface SCHEDULE_STATE {
scheduleList: any,
scheduleDetail: any
}
const initialState: SCHEDULE_STATE = {
scheduleList: [],
scheduleDetail: {}
}
export const ScheduleReducer = (state: SCHEDULE_STATE = initialState, action): SCHEDULE_STATE => {
switch(action.type){
case ScheduleActions.LOAD_SCHEDULE_SUCCESS:
return {...state, scheduleList: action.payload };
case ScheduleActions.LOAD_SCHEDULE_DETAIL_SUCCESS:
return {...state, scheduleList: action.payload };
case ScheduleActions.CREATE_SCHEDULE_SUCCESS:
return {...state, scheduleDetail: action.payload };
default:
return state;
}
}
Epics
//schedule-epic.ts
import { Injectable } from '#angular/core';
import { ActionsObservable, ofType } from 'redux-observable';
import { ScheduleService } from '../services';
import { ScheduleActions } from '../actions';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class ScheduleEpic {
constructor(private service: ScheduleService,
private actions: ScheduleActions
){}
loadScheduleEpic = (action$: ActionsObservable<any>) => {
return action$.ofType(ScheduleActions.LOAD_SCHEDULE)
.mergeMap(action => {
return this.service.loadSchedule().map(result => {
this.actions.loadScheduleSuccess(result)
})
})
}
}
Service
//schedule-service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Schedule } from '../../model/schedule.model';
#Injectable()
export class ScheduleService {
private API_URL: String = "http://mockserver.io/v2";
constructor(private http: HttpClient){}
loadSchedule(){
return this.http.get(this.API_URL + '/5a6225153100004f2bde7f27').map(res => res)
}
}
That error means you dispatched something that was not an action--in this case, your epic emitted something that wasn't an action.
Thankfully, it's an easy fix! You're just missing a return statement in your map
return this.service.loadSchedule().map(result => {
this.actions.loadScheduleSuccess(result)
})
// change it to this:
return this.service.loadSchedule().map(result => {
return this.actions.loadScheduleSuccess(result)
})

Resources