Issue
When I started using Angular, someone in my work place told me to use take(1)
when performing API calls with observables in RxJs, claiming that it automatically unsubscribes after one API call.
So I trusted this claim, and kept using it until I figured out I have massive rendering delays on components load especially when navigating between pages.
Let's take a look at my example:
Here's a UserRepository
that speaks with the API to grant permissions to the application:
@Injectable({
providedIn: 'root'
})
export class UserRepository {
private apiUrl: string = environment.apiUrl;
constructor(private readonly httpRequestService: HttpRequestService) {
super();
}
@UnsubscribeOnDestroy()
getPermissions() {
return this.httpRequestService.get(`${this.apiUrl}/user/permissions`).pipe(map((res: any) => res.user));
}
}
And the usage:
userRepository.getPermissions().pipe(take(1)).subscribe(data => ...);
I googled for a bit and figured out that take(1)
does not automatically unsubscribes and it's not the purpose at all, all it does just takes one observable from the pipe and ignore rest.
To fix this I had the following:
- Option #1: Use
takeUntil
with aunsubscriptionSubject
, callunsubscriptionSubject.next
to trigger unsubscribe onngOnDestroy
- Option #2: Create an
BaseRepository
with a dictionary ofmethodName => Subject
, so for each API call I can save it's own subject, and create a Decorator that automatically unsubscribes from previous API Call, and make sure that there will be never more than one subscription for a type of an API call.
Example of the decorator and usages mentioned for Option #2:
BaseRepository
export abstract class BaseRepository {
protected unsubscriptionSubjects: {[key: string]: Subject<void>} = {};
}
Implementation:
@Injectable({
providedIn: 'root'
})
export class UserRepository extends BaseRepository {
private apiUrl: string = environment.apiUrl;
constructor(private readonly httpRequestService: HttpRequestService) {
super();
}
@UnsubscribeOnDestroy()
getPermissions() {
return this.httpRequestService.get(`${this.apiUrl}/user/permissions`).pipe(map((res: any) => res.user));
}
}
The decorator:
export function UnsubscribeOnDestroy() {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
// Access the instance of the class
const instance = this as any; // Cast to any to access custom properties
// Create a new Subject for the method
const methodName = propertyKey;
instance.unsubscriptionSubjects = instance.unsubscriptionSubjects || {};
if (instance.unsubscriptionSubjects[methodName]) {
instance.unsubscriptionSubjects[methodName].next();
} else {
instance.unsubscriptionSubjects[methodName] = new Subject();
}
// Invoke the original method
const result = originalMethod.apply(this, args);
// Return the original result
return result.pipe(takeUntil(instance.unsubscriptionSubjects[methodName]));
};
return descriptor;
};
}
I have went with option #2, and since then everything got fixed, smooth rendering, no delays.
I went with this option because I load a lot of data in the same page without refreshing, so when using it for a few minutes performing a lot of searches, you can see that slowly the loading states become laggy, and everything takes more time to load. So from what I understand the ideal is to always clear the subscription when using it again.
I would like to ask:
- Why doesn't rxjs have built in take only one request and unsubscribe automatically like using
then
andcatch
in axios? - Does my approach really fix it or can it cause any other issues that I am not seeing?
- Why do most frontend developers that use Angular use observables instead of then/catch with axios?
Solution
RXJS is an implementation(helper) of reactive programming in javascript. In reactive programming you are usually working with an stream of data or events, so the issue emerges when an angular developer uses httpClient or any other modules from angular library which have all accepted the reactive programming paradigm in there code but the rest of the code is not following the same paradigm. so your first question answer would be if RXJS worked like that what would be the purpose of RXJS or the angular httpClient module just use fetch directly.
I think your approach is not a good one because it hasn't been designed in the mindset of reactive programming. A hint to what you are doing wrong is that you think the consumer of the getPermissions() function or any other function which you want to use the decorator with will always subscribe once and doesn't want the observable anymore. you can have multiple subscriptions to the same observable.
The answer to your final question can be concluded from the above. as an angular developer you have accepted to implement your application according to reactive programming paradigm angular works best when you use it like that so httpClient module of angular is best suited for the job.
Answered By - Az264
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.