Angular HTTP request delay interceptor not working as expected - javascript

I wanted to use an HTTP interceptor so that every HTTP request has a delay of 500ms between the next one. I'm currently making these requests from an injectable service that is registered on the app.module and injected in my component. In the that same module I have registered my interceptor.
// delay-interceptor.ts
#Injectable()
export class DelayInterceptor implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return timer(500).pipe(
delay(500),
switchMap(() => next.handle(request))
)
}
}
// app.module.ts
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: DelayInterceptor,
multi: true
},
ManageHousesService
]
// manage-houses.component.ts
createHouses() {
this.houses.foreach((house: House) => {
this.createHousesService.createHouse(house.name).subscribe(createdHouse => {
house.rooms.foreach((room: Room) => {
this.createHousesService.createRoom(house.id, room.name).subscribe();
});
});
});
}
// manage-houses.service.ts
createHouse(houseName: string): Observable<House> {
return this.httpClient.post(`${this.apiUrl}/houses`, { houseName: houseName });
}
createRoom(houseId: string, roomName: string): Observable<Room> {
return this.httpClient.post(`${this.apiUrl}/houses/${houseId}/rooms`, { roomName: roomName });
}
In my component I have to make requests in a nested way. I have a list of houses and for each house I want to create a list of rooms. So for each house I make a POST request and on the subscription I use the ID of the newly created house to create the rooms. For each room I make a POST request with the room information and the house ID. Now this is where the issue appears. Between each house request the delay is working, but between all the rooms of a house it is not, and I can't figure out why that's happening.
I suppose it might have something to do with calling the same method inside each foreach which will probably reuse the same observable or something similar, and thus not trigger the HTTP interceptor, but I'm not sure. On the interceptor I tried to use both the timer and the delay approach, but I got the same result with both approaches.

how do you suppose that each request will take maximum 500ms?
it may take longer than that
did you try to use async/await?
you can use await to handle this asynchronous code, also it's better to avoid using forEach in asynchronous code, as forEach is not a promise-aware, that's how it has been designed
so it's better to the use the normal for loop or the ES6 for of loop
also we need to git rid of the subscribe and unsubscribe as now by using the async/await, we need to deal with promises instead of observables
for that, RxJS provides the toPromise() operator which converts an Observable to a promise so you can work with the HttpClient methods using promises instead of Observables
toPromise() returns a Promise that is resolved with the first value emitted by that Observable (it internally calls subscribe for you and wraps it with a Promise object).
you can then update the createHouses function to be something like that
async createHouses() {
for (const house of this.houses) {
// wait for the house to be added to db
await this.createHousesService.createHouse(house.name).toPromise();
// then loop over the rooms
for (const room of house.rooms) {
// wait for the room to be added to the db
await this.createHousesService.createRoom(house.id, room.name).toPromise()
}
}
}
hope it works as you needed

It is not correctly to change the behavior of the interceptor, because it will affect all requests. You can do this directly from the component, or create a service for this prototype.
concat(
...this.houses.map((house: House) =>
this.createHousesService.createHouse(house.name).pipe(
delay(500),
concatMap((createdHouse) =>
concat(
...house.rooms.map((room: Room) =>
this.createHousesService
.createRoom(house.id, room.name)
.pipe(delay(500))
)
)
)
)
)
).subscribe();

Related

Best practice of Angular

I've just finished my Angular lessons and I already find out some differences between what I learn and the Angular official documentation.
Let's imagine I want to recover an user with ID of an API.
Here is how I would do it according to my lessons :
export class UserService {
constructor(
private httpClient: HttpClient
) {
}
public user: User; // local variable using User model
public userSubject: BehaviorSubject<User> = new BehaviorSubject<User>(null);
async getSingleUserFromServer() {
await this.httpClient.get<any>('https://xvalor.repliqa.fr/api/v1/user/' + this.userId).subscribe(
(response) => {
this.user = response;
this.userPortfolios = this.user.portfolioAssoc;
this.emitSubjects();
});
}
emitSubjects() {
this.userSubject.next(this.user);
}
}
and here is how angular doc procceed
getHeroes (): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl)
.pipe(
tap(_ => this.log('fetched heroes')),
catchError(this.handleError<Hero[]>('getHeroes', []))
);
}
I understand than both methods are quiet doing the same thing, I just want to be sure which one I should use, especially in big project developpement.
I would stick to the second approach as it is more generic and it uses Observable. Observale allow to emit any count of events and callback will be called for each event. Promise generates a single event after completion.
In addition, service class should not have async and await parts. The goal of service is to return data and UI component can consume data using async and await parts. async and await are syntactic sugar to avoid writing .subscribe part as it is really verbose. So write async and await in your UI components.
If you want to use Promise, then your service should not have subscribe part:
getSingleUserFromServer() {
return this.httpClient.get<any>('https://xvalor.repliqa.fr/api/v1/user/' + this.userId);
}
However, it is better to return Observables from your service.
Your first approach is flawed in that the consumer must perform two separate operations: call getSingleUserFromServer() to make the call, and subscribe to UserService.user to consume the results. And in case of errors, he won't receive any feedback.
Stick to the official guidelines for now. BTW, if your goal was to additionally store the user as a variable available to everyone, then with the Observable pattern it's as simple as adding another tap to the pipe:
httpClient.get(url)
.pipe(
someOperator(),
tap(user => this.user = user),
anotherOperator(...someArgs),
)
Observables and Subjects are two diffrent objects from rxjs and bring diffrent properties with them. The answers to this question show some of the key differences: What is the difference between a Observable and a Subject in rxjs?

How to return a mapped string from a Service that fetches values from an API with rjxs?

I have a service called MappingService in Angular 7. This service fetches a list of names from an API:
{
'guid1': 'Item 1',
'guid2': 'Item 2',
'guide': 'Item 3',
}
These values should later be mapped by their ids. Of course I could subscribe to items$ in a componentn and then do something like:
{{ mapping[id] }}
But I thought it would be nice, If I don't have to subscribe to that service or its list so I don't have to pass it around all the time, because the mapping is fixed once loaded and is needed by multiple components.
So I thought I add a method map to the service that does this:
gets an id
returns the corresponding string
Like:
{{ mappingSrervice.map(id) }}
But this is where I'm stuck:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { mergeMap, tap, catchError } from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class MappingService {
items$ = new BehaviorSubject<any>(null);
constructor(private http: HttpClient) { }
getAll(): Observable<any> {
return this.items$.pipe(mergeMap(items => {
if (items) {
return of(items);
} else {
return this.http.get<any>(
'http://api'
).pipe(
tap((response: any) => response),
catchError(error => {
return of([]);
})
);
}
}));
}
map(id: number): string {
return this.items$[id] || null;
}
}
How do I correctly implement map()? Should I run getAll() in the constructor? The problem is still of course that items$ will not be filled before getAll() is finished.
Can I get it to work like I thought? Is string the correct return type?
I wonder whether this approach is possible? Or do I need to return promises instead? But this would generate a new level of abstraction, making it not that much simpler instead of the naive approach (just subscribing to items$.
Let's first talk about caching. You already tried using a BehaviorSubject which is a very good idea. As always with async operations, there is a certain timespan when the data is not available because it is still loading.
This is not a problem and we can deal with this in our components.
There is no really cool way to do the request before the application loads (there are ways but I don't want to recommend any of them).
Talking about this, a BehaviorSubject is not the best choice, because we always need to initialize it with a value.
What is more suitable is the ReplaySubject: It keeps the last elements in its buffer and replays them to each new subscriber.
While all this is cool the way you did it, we can do easier: with the shareReplay() operator.
This makes a cold observable hot, which means: It converts the normal HTTP Observable, which does an HTTP request for each subscriber, to a shared Observable which does its task once and shares its values with all subscribers.
The code could look something like this:
export class MappingService {
items$ = this.getAll().pipe(shareReplay(1));
getAll(): Observable<any>() {
return this.http.get('http://api'); // also add catchError here as you already did
}
}
For the very first subscriber, getAll() will be called and the HTTP request will be performed. For all subsequent subscribers, the cached value will be used.
Note that you still need to subscribe to the items$ Observable, but it is not a problem or challenge to subscribe to Observables in components.
For the mapping I recommend using the RxJS map() operator.
You should provide a service method that takes an ID and returns an Observable with the result. It could look like this:
getItem(id: number): Observable<string> {
return this.items$.pipe(
map(items => items[id])
);
}
This way – using Observables everywhere – you also avoid race conditions through async operations. Each of your Observables will return data as soon as they have arrived.
Resolver may help you.
In summary, you want to delay rendering the routed component until all necessary data have been fetched.
If you return Observable or Promise in the resolve() method of your resolver, your component is not rendered until the Observable completes or the Promise is fulfilled.
In your component you can access the data provided by the resolver with route.snapshot.data.xxx.
The drawback of this approach is that it delays the rendering of your component.
Actually, I believe that it would be better to simply subscribe to items$ in this case.

Angular and Observale: how to avoid multiple requests to API within a given time

I have something like this in an Angular 4 app (for the sake of the example, I have removed code)
#Injectable()
export class SomeService {
constructor(
private http: Http
) {
}
get(id: number) {
return this.http.get('http://somedomain/somemodel/${id}.json');
}
}
this is used by some components to make API calls.
constructor(private someService: SomeService) {}
...
someMethod() {
// code here...
this.someService.get(2).subscribe( someHandlerFunction );
}
someOtherMethod() {
// more code here...
this.someService.get(2).subscribe( someHandlerFunction );
}
the problem is that I don't know when someMethod() and someOtherMethod() will be called. Sometimes both of them may be called and then, my API will be called twice. What I am trying to find is if there any way to change my Service to do this request only after an X amount of time. I tried to use debounce:
get(id: number) {
return this.http.get(`http://somedomain/somemodel/${id}.json`).debounceTime(10000);
}
expecting that (in this case) this HTTP get request will only be repeated after 10 seconds, if the request is made within this 10 seconds, the request won't repeat but the observable emits the last value. But this didn't work.
Any tips?
PS: I know I could control this using some kind of flag, but I can't do this as long I this does not scale very good. I have multiple services with a lot of HTTP requests.
Any thoughts about this?
Debounce should be used on the observable that calls the http service rather than on the http request itself. So for instance :
someObservable
.valueChanges
.debounceTime(1000)
.subscribe(// Call the http service here)
A much better explanation was provided on a previously asked question here.
You can cache your results with RxJS and then set an interval that will clear the cache every few seconds, allowing a new request to be made. This is typically a good approach when you don't want users to re-query your database every time they load a page, especially is nothing new has been added.
This is a slightly more verbose approach than the other answer, but it provides a couple of benefits:
You are cacheing results, limiting load on your server and database
Unlike the other answer using debounceTime only, you are not just blocking the requests. You are blocking the requests AND giving the cached results. If you only block the requests, you will get no results back. With my approach, you still get results if you need them, using the last results that were fetched -- it just won't make a new request until you allow it to.
Cacheing results:
Class ProjectService {
private cachedProjects: Observable<Project[]>;
all(): Observable<Project[]> {
if (!this.cachedProjects) {
this.cachedProjects = this.httpService.get(`${this.url}/project`)
.map(res => res.data.map(project => new Project(project)))
.publishReplay()
.refCount();
}
return this.cachedProjects;
}
}
When you call the service:
this.projectService.all().subscribe((projects: Project[]) => {
// 'projects' will be fetched the first time this is called,
// and any subsequent requests will use the 'cachedProjects'
// stored in the service, until the interval sets it to null.
});
Now to make sure it only blocks the HTTP calls every so often, you can set an interval on your service. Modify your service as follows:
Class ProjectService {
private cachedProjects: Observable<Project[]>;
constructor() {
// Put this in a function or something.
setInterval(() => {
this.cachedProjects = null;
}, 5000);
}
all(): Observable<Project[]> {
if (!this.cachedProjects) {
this.cachedProjects = this.httpService.get(`${this.url}/project`)
.map(res => res.data.map(project => new Project(project)))
.publishReplay()
.refCount();
}
return this.cachedProjects;
}
}
So now you are allowed to make a new request every 5 seconds, otherwise cached projects will be used.
I have similar issue, my solution is create a rxjs subject, and emit id.
#Injectable()
export class SomeService {
private getSubject = new Subject<string>();
constructor(private http: Http) {
this.getSubject.asObservable().distinctUntilChange()
.subscribe((id) => {
this.http.get('http://somedomain/somemodel/${id}.json')
});
}
get(id: number) {
this.getSubject.next(id);
}
}
My solution is not exactly what you want, but you can tweak it a little bit to match your need.

Better way to use fork join subsequent to another observable

I have a login process that has fairly complicated login variations and has to be scalable to easily add more in the future. So initially the user is authenticated in the typical manner and a user object is returned. Then I must make additional http calls to get information that will determine the various requirements before the user is granted access to the app. This is done using some of the values returned in the user object. I want to write the code in a way that I can easily add http calls without changing current code so I thought using fork join for the subsequent calls would be good since they can be done in parallel. Below is my working code.
I can easily add new requests to the fork join call and while it doesn't look too bad to me I have been told nested subscriptions is a code smell and typically bad practice. Any ideas on how to do this better would be great.
Thanks.
this.authenticate.login(this.model)
.subscribe(
_data => {
this.subscription = Observable.forkJoin(
this.devicesHttp.getDevicesByMacAddress(this.macAddress),
this.teamsService.getTeamsByUserId(_data['userId'])
);
this.subscription.subscribe(
_data => {
// Check login type and other stuff...
}
);
}
);
For example like this using the concatMap() operator:
this.authenticate.login(this.model)
.concatMap(_data => Observable.forkJoin(
this.devicesHttp.getDevicesByMacAddress(this.macAddress),
this.teamsService.getTeamsByUserId(_data['userId'])
))
.subscribe(_data => {
// Check login type and other stuff...
});
The Observables in forkJoin will run in parallel and forkJoin will wait until they both finish.
Also concatMap() waits until the inner Observable completes and then pushes the result further.
In 2021 this must be written with pipe, stand-alone operators, array in forkJoin and Observer argument in subscribe:
import { concatMap, forkJoin } from 'rxjs';
this.getFirst().pipe(
concatMap(data =>
forkJoin([
this.getSecond(data),
this.getThird(data)
])
)
).subscribe({
next: result => ...,
error: e => ...
});
How about this:
this.authenticate.login(this.model)
.switchMap(data => Observable.forkJoin(
this.devicesHttp.getDevicesByMacAddress(this.macAddress),
this.teamsService.getTeamsByUserId(data['userId'])
))
.subscribe(...,...)

.asObservable dont want to work with Observable.forkJoin

I have service:
export class ConfigService {
private _config: BehaviorSubject<object> = new BehaviorSubject(null);
public config: Observable<object> = this._config.asObservable();
constructor(private api: APIService) {
this.loadConfigs();
}
loadConfigs() {
this.api.get('/configs').subscribe( res => this._config.next(res) );
}
}
Trying to call this from component:
...
Observable.forkJoin([someService.config])
.subscribe( res => console.log(res) ) //not working
someService.config.subscribe( res => console.log(res) ) // working
...
How can i use Observable.forkJoin with Observable variable config?
I need to store configs in service and wait unlit them and others request not finished to stop loader.
Since you're using BehaviorSubject you should know that you can call next() and complete() manually.
The forkJoin() operator emits only when all of its source Observables have emitted at least one values and they all completed. Since you're using a Subject and the asObservable method the source Observable never completes and thus the forkJoin operator never emits anything.
Btw, it doesn't make much sense to use forkJoin with just one source Observable. Also maybe have a look at zip() or combineLatest() operators that are similar and maybe it's what you need.
Two very similar question:
Observable forkJoin not firing
ForkJoin 2 BehaviorSubjects

Categories