infinite loop when i dispatch action after getting data w/subscribe - angular6

i'm new in angular 6 and ngrx store. I try to dispatch action after get data subscribe from store but it make infinite loop and crash browser? What i was wrong . Some solution i find it using do/tap operator of rxjs but still not working. And when i use {{(feedState | async).loading}} for example , it alway return undefined .
my component:
ngOnInit() {
this.store.dispatch(new FeedActions.GetFeedCategories());
this.feedSubscription = this.store
.pipe(
select('feed'),
map(data => {
this.feedState = data;
return data.categories;
}),
tap(data =>
this.store.dispatch(
new FeedActions.GetFeedItems({
cat_id: data[this.selectedIndex],
page: 0
})
)
)
)
.subscribe(data => {});
}

The select operator will create an observable which emits every time the state of 'feed' is updated. This will fire the first time when you do your FeedActions.GetFeedCategories() but it will also fire again when the result of FeedActions.GetFeedItems(...) is added to the state, which will cause FeedActions.GetFeedItmes(...) to be executed again, and again, and again...
The simple solution is to add a take(1) into the pipe, so you only get a single fire of the map and tap operators:
ngOnInit() {
this.store.dispatch(new FeedActions.GetFeedCategories());
this.feedSubscription = this.store
.pipe(
select('feed'),
take(1),
map(data => {
this.feedState = data;
return data.categories;
}),
tap(data =>
this.store.dispatch(
new FeedActions.GetFeedItems({
cat_id: data[this.selectedIndex],
page: 0
})
)
)
)
.subscribe(data => {});
}
However, it may be worth considering splitting the concerns here - you've mixed the job of preparing the state with the job of selecting the state for display. A better solution may be something like this:
ngOnInit() {
this.store.dispatch(new FeedActions.GetFeedCategories());
this.store.pipe(
select('feed'),
take(1),
map(data => data.categories),
tap(data =>
this.store.dispatch(
new FeedActions.GetFeedItems({
cat_id: data[this.selectedIndex],
page: 0
})
)
)
)
.subscribe(() => {});
this.feedState = this.store.pipe(
select('feed')
);
}
... then in your template, you can use {{feedState | async}}?.loading or whatever as needed.
The async pipe does the subscription for you and expects an observable, not a raw data field. In your example, this.feedState should be of type Observable<FeedState>, but it looks to be a raw data type (e.g. FeedState instead of Observable) from the code provided.

Related

Apply search columns and export mat table to excel

I am trying to implement a reports feature in my application which pulls country wise reports for a person logged in. I am getting data from backend and displaying this in a mat table. I want to have different filters based on country, sectors, industries on this data and it should also have the feature to export it.. For the filtering i dont want to run a backend query everytime so i want to filter it in the frontend and export the given data. I tried the table_to_sheet option of xlsx but that only exports the first page of the table. My code:
Object:
export interface Reports {
projectId: string;
projectName: string;
industry: string;
sector: string;
}
my table looks like this
I want to update the data array every time I apply a filter and then export the same. Can anyone help.
I am assigning the table in ngonInit
ngOnInit(): void {
this.service.getReports(this.loggedUser, this.role).subscribe
(
(res: any) => {
this.showSpinner = false
this.showTable = true
console.log(res)
console.log(res.reportList)
console.log('filters: ' +res.reportFilter.countrySet)
this.dataSourceMyRequests = new MatTableDataSource(res.reportList) ;
this.countryList = res.reportFilter.countrySet
this.gcnList = res.reportFilter.countrySet
this.sectorList = res.reportFilter.countrySet
//this.dataSourceMyRequests.sort = this.sortRequest
//this.dataSourceMyRequests.paginator = this.paginatorRequest
this.showData = true;
},
(error) => {
this.showSpinner = false
this._snackbar.open('No data found!', 'OK');
console.log(error)
}
)
}
I want to write a filter method like below:
filter(searchField, searchValue){
}
For filtering build a frontend filter that gets applied to incoming array of items. After filtering show only filtered items in table.
If filter returns true, then filteredArray will include it.
filteredArray: any[];
observable.subscribe((allData) => {
this.filteredArrray = allData.filter((item) => {
if (item?.projectId === '123') {
return true;
} else if (item?.projectName === 'name') {
return true;
} else {
return false;
}
});
})
Then export filteredArray[] to xlsx: https://stackblitz.com/edit/angular-material-table-export-excel-xlsx?file=app%2FtableUtil.ts

How do I use rxjs map to mutate data in object before casting a new type from an object gotten from a HttpClient request?

Hope the title isn't too specific.
The back-end I am working with returns Dates as a string. I have a function to convert that string to a javascript Date object. I use a Rxjs map to convert the json response to my Typescript objects like so.
getAllRecordsByEmployeeId(employeeId: number): Observable<Record[]> {
return this.http.get<Record[]>(
this.basePath
+ this.recordPath
+ this.recordEmployeeIdParam
+ employeeId,
this.httpOptions)
.pipe(
map((res: any) => res.records as Record[]),
);
}
I want to mutate res.records.startDate with a function before it gets turned into a Record object. How can I accomplish this?
getAllRecordsByEmployeeId(employeeId: number): Observable<Record[]> {
return this.http.get<Record[]>(
I understand, that your http request does not actually return a Record array. It returns an object with a Record Array field, which is basically another Record model. It is very similar, but it's a different model.
Please consider changing it to:
interface RecordFromApi extends Record {
startDate: string; // overwrite attribute
}
interface RecordResponse {
records: RecordFromApi[];
}
getAllRecordsByEmployeeId(employeeId: number): Observable<Record[]> {
return this.http.get<RecordResponse>(
this.basePath
+ this.recordPath
+ this.recordEmployeeIdParam
+ employeeId,
this.httpOptions)
.pipe(
map((res: RecordResponse) => res.records.map(record => mapRecord(record))), // mapRecord is a custom function which maps RecordFromApi model to Record model
);
}
We do something similar in my application. But instead of returning
res.records as Record[]
we do something like this:
.pipe(
map((records: Record[]) => records.map(records => new Record(record)))
);
and then on the record.ts
export class Record {
/*
properties
*/
date: Date;
constructor(params: Partial<Record> = {}) {
this.date = new Date(params.date);
}
}
This way you actually get instances of your class and you can use any functions you may have in your class (that's the issue we had when we came up with this solution).

Promises and garbage collection

I have setup a project with react-redux and I use redux-thunk in my action-creators to do fetching. Here is an example of my thunk:
export const doPostRequest = id => {
return (dispatch, getState) => {
const { id : initiailId } = getState().currentSelection
return api.post(id).then(response => {
if (!isEqual(initialId, getState().currentSelection.id)){
return;
}
dispatch(someOtherAction(id))
return Promise.resolve(true)
})
.catch(err => {})
}
}
As you can see i want to escape the doPostRequest if the currentSelection of my state is changed by the time the response is receieved. Otherwise I return Promise.resolve(true) so the onSubmit in MyComponent can reset the form:
Inside a component (which is a form) i have the following for onSubmit:
class MyComponent extends React.PureComponent{
onSubmit = id => {
this.props.dispatch(doPostRequest(id))
.then(shouldReset => shouldReset && resetForm())
}
render(){
return <form onSubmit={this.onSubmit}>.....</form>
}
}
In most cases, when I really dont have to do anything else except fetching values, I dont do a Promise-chain on the thunk, even though it returns a promise, but here I need to do a resetForm once the postrequest is a success.
Is this implementation good enough, also when it comes to GC ? How are Promises garbage collected ? Is there a problem if I return a fetch().then() without chaining it further?

NgRX Effect for downloading media file and dispatching progress; how to deal with the stream and throttling

I am struggling a bit with understanding and applying #Effects for my episode Download option. I got some help in another question and this is my latest concoction.
Quick overview: User clicks download which dispatches a DOWNLOAD_EPISODE action which is captured in the first Effect. The download call returns a stream of HttpEvents and a final HttpResponse.
During the event.type === 3 I want to report download progress. When event.type === 4 the body has arrived and I can call the success which for example can create a Blob.
Service episodesService:
download( episode: Episode ): Observable<HttpEvent<any>> | Observable<HttpResponse<any>> {
// const url = encodeURIComponent( episode.url.url );
const url = 'https%3A%2F%2Fwww.sample-videos.com%2Faudio%2Fmp3%2Fcrowd-cheering.mp3';
const req = new HttpRequest( 'GET', 'http://localhost:3000/episodes/' + url, {
reportProgress: true,
responseType: 'blob'
} );
return this.http.request( req );
}
downloadSuccess( response: any ): Observable<any> {
console.log( 'calling download success', response );
if ( response.body ) {
var blob = new Blob( [ response.body ], { type: response.body.type } );
console.log( 'blob', blob );
}
return of( { status: 'done' } );
}
getHttpProgress( event: HttpEvent<any> | HttpResponse<Blob> ): Observable<DownloadProgress> {
switch ( event.type ) {
case HttpEventType.DownloadProgress:
const progress = Math.round( 100 * event.loaded / event.total );
return of( { ...event, progress } );
case HttpEventType.Response:
const { body, type } = event;
return of( { body, type, progress: 100 } );
default:
return of( { ...event, progress: 0 } );
}
}
The effects:
#Effect()
downloadEpisode$ = this.actions$.pipe(
ofType<episodeActions.DownloadEpisodes>( episodeActions.DOWNLOAD_EPISODE ),
switchMap( ( { payload } ) => this.episodesService.download( payload )
.pipe(
switchMap( (response: HttpEvent<any> | HttpResponse<any>) => this.episodesService.getHttpProgress( response ) ), //merge in the progress
map( ( response: fromServices.DownloadProgress ) => {
// update the progress in the episode
//
if ( response.type <= 3 ) {
return new episodeActions.DownloadProgressEpisodes( { ...payload, download: {
progress: response.progress
} } );
// pass the Blob on the download response
//
} else if ( response.type === 4 ){
return new episodeActions.DownloadEpisodesSuccess( response );
}
} ),
catchError( error => of( new episodeActions.DownloadEpisodesFail( error ) ) ),
)
)
)
#Effect( { dispatch: false } )
processDownloadEpisodeSuccess$ = this.actions$.pipe(
ofType<any>( episodeActions.DOWNLOAD_EPISODE_SUCCESS ),
switchMap( ( { payload } ) => this.episodesService
.downloadSuccess( payload ).pipe(
tap( response => console.log( 'response', payload,response ) ),
// catchError(err => of(new episodeActions.ProcessEpisodesFail(error))),
)
)
)
I am pretty happy with this solution, however i do not like the If ELSE clause in the MAP as part of the DOWNLOAD_EPISODE Effect.
Ideally I want to split the stream there, if type is 3 I want to go route A which always dispatches episodeActions.DownloadProgressEpisodes with an Episode payload.
Whenever it is type 4, the last emitted event, I want to got the B route in the stream and call episodeActions.DownloadEpisodesSuccess with the response body.
I tried to add more effects but I always end up in a situation the the result stream of the this.episodesService.download will either be a progress update OR a response body. For the progress update, I require the Episode record to be able to call the reducer and update the Episode's progress.
I tried to use things as setting filter( response => response.type === 4 ) after the DownloadProgressEpisodes but before DownloadEpisodesSuccess hoping it would allow for an iteration before the filter to deal with the progress and then filter the rest through to the Success Action.
I then learned that is not how streams work.
I also tried last() which did work but it didn't let me dispatch the Response Actions (the console log did work), but only the DownloadEpisodesSuccess action after the last() would be dispatched.
So, if it is possible to split the stream and deal with event.type 3 differently then event.type 4 I would be interested in that.
*****UPDATE*******
I want to keep the original question, however I did run into a throttling requirement because I was dispatching a lot of actions for large files.
I tried the throttle operator but that would suppress emits but could also suppress the last emit with the response body. I tried splitting it with partition but I don't think that is possible. After a light bulb moment I decided to create 2 effects for DOWNLOAD_EPISODE, one which only captures all event.type === 3 and throttles it and another effect that uses the last operator and only deals with event.type === 4.
See code:
#Effect( )
downloadEpisode$ = this.actions$.pipe(
ofType<episodeActions.DownloadEpisodes>( episodeActions.DOWNLOAD_EPISODE ),
switchMap( ( { payload } ) => this.episodesService.download( payload )
.pipe(
switchMap( (response: HttpEvent<any> | HttpResponse<any>) => this.episodesService.getHttpProgress( response ) ),
throttleTime( 500 ),
map( ( response: fromServices.DownloadProgress ) => {
console.log('Type 3', response);
// update the progress in the episode
if ( response.type <= 3) {
return new episodeActions.DownloadProgressEpisodes( { ...payload, download: {
progress: response.progress
} } );
}
} ),
catchError( error => of( new episodeActions.DownloadEpisodesFail( error ) ) ),
)
)
)
#Effect( )
downloadEpisodeLast$ = this.actions$.pipe(
ofType<episodeActions.DownloadEpisodes>( episodeActions.DOWNLOAD_EPISODE ),
switchMap( ( { payload } ) => this.episodesService.download( payload )
.pipe(
switchMap( (response: HttpEvent<any> | HttpResponse<any>) => this.episodesService.getHttpProgress( response ) ),
last(),
map( ( response: fromServices.DownloadProgress ) => {
console.log('Type 4', response);
if ( response.type === 4 ){
return new episodeActions.DownloadEpisodesSuccess( response, { ...payload, download: {
progress: response.progress
} } );
}
} ),
catchError( error => of( new episodeActions.DownloadEpisodesFail( error ) ) ),
)
)
)
What do you think, is this the best solution? Type 3 and 4 are split and I can throttle it.
*update2:
It does create an issue where the Progress Action with progress 97% can be triggered after the Download Success action with progress 100%.
Every time I run into a new challenge...
SampleTime(500) seems to work, since it doesnt seem to throw a Type 3 event after the Type 4 event came in.
*update3: I just found out that I am calling Download twice now in the effect, sigh. I am back at square one trying to throttle the HttpEvent stream coming from episodeService.download.
I think if you don't want to have if else statement, you'll have to create different effects.
In the current one you'll dispatch a new action, e.g. DownloadEpisodeProgess, with the type in the payload.
You will then create two effects that listens to this action and filter them accordingly via the type, one effect will be used to dispatch DownloadProgressEpisodes, the other one for DownloadEpisodesSuccess.
Another possible solution would be the partition operator, but I haven't used it in combination with NgRx Effects.
It is also good to keep in mind that for each subscription you make, there will be a performance cost, personally I don't really mind the if else statement.

Angular 2+ http get() JSON-data. Cannot .filter data

I have a service.ts, where I use the get() method (http) to get data from a local JSON-file. It Works fine.
getMusic() {
return this.http.get('assets/music.json')
.map((res:Response) => res.json())
In my class I have my 'suscribe':
ngOnInit() {this.musicService.getMusic().subscribe(
data => { this.music = data.music}
);
}
My JSON-file looks like this:
{
"music" : [
{
"no": 11,
"name": "Music 1",
}, ...... aso....
Everything is perfect. Well now I'm trying to filter my data, so it only shows music with no=11.
I have this Imports:
//import 'rxjs/add/operator/map';
//import 'rxjs/add/operator/filter';
import 'rxjs/Rx';
I have tried several things. Including filtering for a number instead of a string, using == instead of ===, putting .filter inside or outsite .map aso. When I try this I receive no result at all. I get no errors, just no result:
getMusic() {
return this.http.get('assets/music.json')
.map((res:Response) => res.json())
.map(x => x.filter(y => y.name === 'Music 1'));
}
You have wrong filter operator function. It should be filtering on music.
getMusic() {
return this.http.get('assets/music.json')
.map((res:Response) => res.json())
//filter on `music` object instead of `x` directly
.map(x => x.music.filter(y => y.name === 'Music 1'));
}
the filter function is applied to an array not to an instance of it
try using
this.filteredArr = this.music.filter(x=>x.name == 'music 1'); //some condition