Need to show newly added records/rows in Angular11 using web api service without page refresh - angular11

Component.ts
ngOnInit() {
this.employeeservice.getEmp_ServiceFun().subscribe(
(data: Employee[]) => this.employees = data,
error => this.error = error
);
}
service.ts
constructor(private http: HttpClient) { }
public getEmp_ServiceFun(): Observable<Employee[]> {
return this.http.get<Employee[]>(this.serverUrl + 'employees')
.pipe(
catchError(this.handleError)
);
}

it seems a bit strange to me to run the ngoninit method again since it is meant to run only once. I would wrap the employeeservice method in an observable interval. dont forget to unsubscribe though. otherwise it would keep calling the getEmp_ServiceFun until the whole app closes
ngOnInit() {
interval(1000).pipe(
map(() => {this.employeeservice.getEmp_ServiceFun().subscribe(
(data: Employee[]) => this.employees = data,
error => this.error = error
);})}
this would replace the whole array instead of just adding to it though. I would take a second look at the getEmp_ServiceFun so that you can ask only for new data and not all data and then push it to the array.
edit: even better would be to not subscribe in the the map but move it to after the pipe. you might need to use a switchMap

I used setTimeout() to refresh a component, it is working fine, but now I just need to check, is it good practice or not?
ngOnInit() {
this.employeeservice.getEmp_ServiceFun().subscribe(
(data: Employee[]) => this.employees = data,
error => this.error = error
);
//refresh this component
this.timeout = setTimeout(() => { this.ngOnInit() }, 1000 * 1)
}

Related

Async side effect depends on another effect

I have two side effects. One of them presents a global loading spinner and the other one dismisses the spinner again. For presenting and dismissing the loader I have created a LoadingService. Note that the presentLoader() and dismissLoader() methods are async. Reason is that the service encapsulates the LoadingController of the Ionic framework.
import { Injectable } from '#angular/core';
import { LoadingController } from '#ionic/angular';
#Injectable({
providedIn: 'root'
})
export class LoadingService {
private loader?: HTMLIonLoadingElement;
constructor(private loadingController: LoadingController) {
}
public async presentLoader(translationKey: string): Promise<void> {
if (this.loader) {
this.loader.message = translationKey;
return;
}
this.loader = await this.loadingController.create({
message: translationKey
});
await this.loader.present();
}
public async dismissLoader(): Promise<void> {
await this.loader?.dismiss();
this.loader = undefined;
}
}
In my Application i work with the RequestStarted, RequestSucceeded and RequestFailed Action naming pattern.
public updateMyThingsItem$ = createEffect(() => {
return this.actions$.pipe(
ofType(
MyThingsItemActions.updateMyThingsItemNameRequested,
MyThingsItemActions.updateMyThingsItemPurchasePriceRequested,
MyThingsItemActions.updateMyThingsItemPurchaseDateRequested
),
exhaustMap((action) =>
this.myThingsIntegrationService.patchMyThingsItem(action.myThingsItem.id, action.myThingsItem.changes).pipe(
map((myThingsItem) => MyThingsItemActions.updateMyThingsItemSucceeded({ myThingsItem })),
catchError((error: string) => of(MyThingsItemActions.updateMyThingsItemFailed({ error })))
)
)
);
});
Now I created a side effect to display a loader for a couple of RequestStarted actions and a side effect that hides the loader again for the related Succeeded and Failed actions. This looks something like this:
public presentLoadingSpinner$ = createEffect(() => {
return this.actions$.pipe(
ofType(
MyThingsItemActions.updateMyThingsItemNameRequested,
MyThingsItemActions.updateMyThingsItemPurchasePriceRequested,
MyThingsItemActions.updateMyThingsItemPurchaseDateRequested
),
exhaustMap(() => this.loadingService.presentLoader('loading...'))
);
}, {
dispatch: false
});
public hideLoadingSpinner$ = createEffect(() => {
return this.actions$.pipe(
ofType(
MyThingsItemActions.updateMyThingsItemSucceeded,
MyThingsItemActions.updateMyThingsItemFailed
),
exhaustMap(() => this.loadingService.dismissLoader())
);
}, {
dispatch: false
});
Now for my problem: Because the methods in my loadingService are async there is the possibility that the side effect to dismiss the loader is triggered before the presentLoader() method is completed and the loader does not yet exist in the DOM. This happens when the API is really quick or a client error (no network) happens and the Failure Action is dispatched before the loader is created.
I therefore realise that I now have depending effects. But how else could I solve the problem with a side effect that triggers async code?
I know that I could create a loading state with a isLoading flag. But I would have the same problem when creating the effect from a selector.
An other possibility would be to create an overlaying Loader Component in the AppComponent that is rendered on the isLoading flag. But then I would have to give up the LoadingController of the Ionic framework which I do not really like.
I would like to know how to solve these problems in general when calling a side effect method that is async.

How can I subscribe and unsubscribe from individual firestore queries?

I am trying to modify an effect I have made into letting me start and stop multiple firestore queries by using two actions. Currently the effect allows me to start and stop a single firestore query by listening for two separate actions in the effect. I simply use a switchMap to switch into an empty observable when there is a stop action. This works just fine.
#Effect()
startStopQuery$ = this.actions$.pipe(
ofType(
ActionTypes.START,
ActionTypes.STOP
),
switchMap(action => {
if (action.type === ActionTypes.STOP) {
return of([]);
} else {
return this.afs.collection('collection', ref => {
return ref.where('field', '==', 'x');
}).stateChanges();
}
}),
mergeMap(actions => actions),
map(action => {
return {
type: `[Collection] ${action.type}`,
payload: { id: action.payload.doc.id, ...action.payload.doc.data() }
};
})
);
What I actually want to do is to have multiple queries ongoing that I can start and stop with those same two actions, but where it depends on the action payload. When I modified it everytime I performed a new query the last one stops working. I think it is because the switchMap operator switches away from my last query observable. This is the best I have come up with:
#Effect()
startStopQueryById$ = this.actions$.pipe(
ofType(
ActionTypes.START_BY_ID,
ActionTypes.STOP_BY_ID
),
switchMap(action => {
if (action.type === ActionTypes.STOP_BY_ID) {
return of([]);
} else {
return this.afs.collection('collection', ref => {
return ref.where('field', '==', action.id);
}).stateChanges();
}
}),
mergeMap(actions => actions),
map(action => {
return {
type: `[Collection] ${action.type}`,
payload: { id: action.payload.doc.id, ...action.payload.doc.data() }
};
})
);
As I said, I think the issue is the switchMap operator. But that is also what I depended on to make the "stop" work in the first place. I cant seem to come up with another solution as I am not very well versed in the style yet.
Any help would be greatly appreciated!
I came up with a solution. I make an object that maps ID's to the firestore statechanges observables. On the start action I make the listener and adds it to the object. I make sure that it automatically unsubscribe by piping takeUntil with the corresponding stop action. It returns a merge of all the observables in the object and I silply do as before. I also have a seperate effect triggered by the stop action to remove the observable from the object. It looks like so:
queriesById: {[id: string]: Observable<DocumentChangeAction<Element>[]>} = {};
#Effect()
startQuery$ = this.actions$.pipe(
ofType(ActionTypes.START_BY_ID),
switchMap(action => {
this.queriesByPlay[action.pid] = this.afs.collection<Element>('requests', ref => {
return ref.where('field', '==', action.id);
}).stateChanges().pipe(
takeUntil(
this.actions$.pipe(
ofType(ActionTypes.STOP_BY_ID),
filter(cancelAction => action.id === cancelAction.id),
)
)
);
return merge(
Object.values(this.queriesByPlay)
);
}),
mergeMap(actions => actions),
mergeMap(actions => actions),
map(action => {
return {
type: `[Collection] ${action.type}`,
payload: { id: action.payload.doc.id, ...action.payload.doc.data() }
};
})
);
Effect({dispatch: false})
stopQuery$ = this.actions$.pipe(
ofType(ActionTypes.STOP_BY_ID),
map(action => delete this.queriesByPlay[action.id]),
);
This seems to work and have no problems except for being convoluted hard to understand.

How to catch error with ngrx

I am trying to catch an error with ngrx and angularfire2 firetore.
Here is the effect
#Effect()
delete$: Observable<Action> = this.actions$
.ofType(actions.DELETE)
.map((action: actions.Delete) => action.id)
.mergeMap(id =>
of(this.afs.doc<Collection(`collections/${id}`).delete()))
.map(() => new actions.Success())
.catch(err => of (new actions.Fail(err.message )));
and actions:
export class Success implements Action {
readonly type = SUCCESS;
constructor() {
console.log('success')
}
}
export class Fail implements Action {
readonly type = ERROR;
constructor(public payload: any) {
console.log('failed');
}
}
I keep getting success even if the action is not completed. What is the good way to do this?
Currently, this code is trying to catch errors on the entire stream, rather than the inner observable that matters.
Try this:
#Effect delete$: Observable<Action> = this.action$
.ofType(actions.DELETE)
.map((action: actions.Delete) => action.id)
.mergeMap(id => {
return of(this.afs.doc<Collection(`collections/${id}`).delete())
.map(() => new actions.Success())
.catch(err => of(new actions.Fail(err.message))
});
See the docs on mergeMap and this helpful answer on SO about it. The stream that may throw an error is the internal of(this.afs.doc...) observable, so the .catch should be on that observable. In the current implementation (from your example above), it will always map the result of of(this.afs.doc...) to a new actions.Success(), whether it fails or not.

Angular2 Observable HTTP Data not filled

I´m fairly new to Angular2 and want to load data from a JSON-file.
I read the AngularIO Article and did the tutorial, but i still need some help.
I splitted the "getting Data" into an apiService which loads data and a component, whicht gets the data from the apiService.
Because it´s simpler, for this question I want to load the data directly to the component without the apiService.
This is the component:
export class StatusComponent implements OnInit {
private status = {};
private error: string;
constructor(private router: Router, private variables: Variables, private apiService: ApiService, private http: Http) {
if (!this.variables.getIsLoggedIn()) {
this.router.navigate(['login']);
}
}
ngOnInit() {
this.getStatus();
}
getStatus() {
this.http.get('../app/apiFiles/status.json')
.map((res:Response) => res.json())
.subscribe(
data => { this.status = data},
err => console.error(err),
() => console.log('done')
);
console.log(JSON.stringify(this.status));
}
}
This is JSON:
{
"status": {
"timercount": "0",
"reccount": "0"
}
}
In the component, the getStatus() is correctly load, and goes through without an error, but the variable i fill status isn´t filled.
After the getStatus() the variable is still {}.
Moreover, the output of console.log(JSON.stringify(this.status)); is {}.
I hope someone can help me and solve it !
Thank so much!
That's because console.log(JSON.stringify(this.status)); is executed after http call is done. Have in mind that all http calls are asynchronous methods. Try this:
getStatus() {
this.http.get('../app/apiFiles/status.json')
.map((res:Response) => res.json())
.subscribe(
data => { this.status = data},
err => console.error(err),
() => console.log(this.status);
);
}
You'll see this will print your result because () => console.log(this.status); is executed after http call is successfuly completed.
The correct solution is this:
getStatus() {
this.http.get('../app/apiFiles/status.json')
.map((res:Response) => res.json())
.subscribe(
data => { this.status = data.status},
err => console.error(err),
() => console.log('done')
);
console.log(JSON.stringify(this.status));
}
So, you have to use data.status, because the JSON file begins with "status" ;)

Assign AJAX data to class variable in AngularJS2

I am doing the following code and unable to figure out that why the data I am obtaining through AJAX is not being assigned to the class variable which is this.users
Code Snippet
getUsers() {
this.http.get('/app/actions.php?method=users')
.map((res:Response) => res.json())
.subscribe(
res => { this.users = res}, // If I console 'res' here it prints as expected
err => console.error(err),
() => console.log('done')
);
console.log(this.users) // Printing 'undefined'
return this.users;
}
Any help will be much appreciated. This (http://prntscr.com/cal2l1) is link to my console output.
It is an asynchronous call, so you don't fetch data right away. However, if you setTimeout() on console.log(), it will be printed correctly because printing will occur after the data is fetched:
getUsers() {
this.http.get('/app/actions.php?method=users')
.map((res:Response) => res.json())
.subscribe(
res => { this.users = res}, // If I console 'res' here it prints as expected
err => console.error(err),
() => console.log('done')
);
setTimeout(() => {
console.log(this.users) // Printing 'undefined'
}, 1000);
return this.users;
}
Reason for Problem
Well, it was really a silly mistake which I was making here. Since, getUsers() was being called after the DOM was loaded so it was assigning the value to class variable which is this.users after loading of DOM which restricted my page to load the required values at page loading stage (not after page loading).
Solution
Angular2 comes with a hook called OnInit or ngOnInit(). I was supposed to call the function in this event as follows.
getUsers() {
this.http.get('/app/actions.php?method=users')
.map((res:Response) => res.json())
.subscribe(
res => { this.users = res},
err => console.error(err),
() => console.log('done')
);
console.log(this.users)
return this.users;
}
ngOnInit() {
getUsers();
}
Documentaion of OnInit: https://angular.io/docs/ts/latest/api/core/index/OnInit-class.html
Also the following documentation came up as a helping tool:
https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html

Resources