Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you prevent a BehaviourSubject from being consumed before it's defined?

Tags:

angular

rxjs

I'm working on a user service that keeps track of users by their user_id. It first checks if user_id is present in their cookies, if it isn't it makes a get request to the api to create a new one and returns the id in the response. My problem is that the user_id is being consumed before the response is completed. I have two questions:

  1. The BehaviourSubject is being consumed before it's even defined as a new BehaviourService, components that consume it are calling .subscribe() on undefined as a result and the app is crashing.
  2. Is the really necessary for me to subscribe to the user_id every single time I want to retrieve it? The code is turning into hell to work with since i'm subscribing to the behavioursubject first and then inside the subscribe method I'm writing my code. I can't figure out a better way to go about it, and from the texts I've read they mention that linking streams is the right approach, but this just feels very wrong.

Here's a simplified example of what I'm doing

get ruid or create one - user.service.ts

constructor(...) {
    public ruid: BehaviorSubject<any>;
    if(!Cookies.get('ruid')) {
        this.http.get(url).subscribe(
            (value) => {
                this.ruid = new BehaviorSubject(value)
                Cookies.set('ruid', value)
            }
        )
    } else {
        this.ruid = new BehaviorSubject(Cookie.get('ruid'));
    }
}

use ruid in component

constructor(private userService: UserService) {
    data;
    this.userService.ruid.subscribe(
        (value) => {
            this.data = this.http.get(url + value).map(res => res.json())
        }
    );
}
like image 964
Nikola Jankovic Avatar asked Nov 17 '25 22:11

Nikola Jankovic


1 Answers

I believe what you need here is to setup resolver on your route. See my example from real project below:

resolver.ts

@Injectable()
export class LocationResolver implements Resolve<any> {
    constructor(private  locationsApiService: LocationsApiService, private appStorage: AppStorage, private authService: AuthService) {

    }

    resolve(route: ActivatedRouteSnapshot): Observable<any> {
        return new Observable((observer) => {
            const sessionData = this.authService.getSessionData();

            if (!sessionData.Location) {
                this.locationsApiService.getUserLocations().subscribe(
                    locations => {
                        sessionData.Location = locations[0].Name;
                        sessionData.LocationId = locations[0].Id;

                        this.authService.updateSessionData(sessionData);
                        this.appStorage.set(AppConstants.storage.userLocations, locations);

                        observer.next(sessionData.LocationId);
                        observer.complete();
                    }
                );
            } else {
                observer.next(sessionData.LocationId);
                observer.complete();
            }
        });
    }
}

Routes

{
        path: 'app',
        component: MainComponent,
        canActivate: [AuthGuard],
        resolve: {
            locationId: LocationResolver
        },
        children: [...]
}

Basically what it does is not resolving route up until it gets the necessary data. In my case I need to query locationId and provide it to every call after login. So resolver either looks for it in LocalStorage or doing a call to the API and then sets the location.

UPD: In your case you can put such resolver on the route that subscribes to BehaviorSubject and it will not get instantiated and subscribed until you finish all the API calls.

like image 186
Vitalii Chmovzh Avatar answered Nov 19 '25 11:11

Vitalii Chmovzh