Home > Software engineering >  Using RxJs/Reactive programming - how can I get a related entity?
Using RxJs/Reactive programming - how can I get a related entity?

Time:01-07

I have two entities: Organization and User. In my component, I have access to the currently authenticated user like this:

// component.ts

public authenticatedUser$ = this._authService.authenticatedUser$.pipe(
        tap((user) => {
            console.log('authenticatedUser$: ', user); // {id: '123', organizationId: '456',...}
        }),
        catchError((err) => {
            return EMPTY;
        })
    );

What I am trying to do is get the organization that this user belongs to. I've been trying different variations of this without much success:

// component.ts

// private _organizationSubject:Subject<IOrganization>();
// public organization$ = this._organizationiSubject.asObservable();

public organization$ = this.authenticatedUser$.pipe(
        map(
            (user) =>
                tap((user) => {
                    console.log('user', user);
                })
            // this._organizationService.getOrganization(user.organizationId)
            // this._organizationSubject.next([--response from service--]);
        ),
        catchError((err) => {
            return EMPTY;
        })
    );
    
...


ngOnInit(): void {
  // actual call(s) need to go here...

    this.authenticatedUser$
            .pipe(
                tap((user) => console.log('user: ', user)),
                tap((user) => {
                    this._organizationService.getOrganization(
                        user.organizationId
                    );
                }),
                catchError((err) => {
                    return EMPTY;
                })
            )
            .subscribe();
}

I realize my ???map is likely incorrect as well. I've been watching a lot of Deborah Kurata's videos & trying to make my data stream mimic her action streams. I am working with two data streams as I am always working with an authenticated user. There is no "interaction" to get these values.

In following her advice:

// what do we want (organization)
// what do we have (org id)
// when do we want it (when page loads e.g. OnInit)

Doing this a procedural way would be quite simple, however I'm really struggling with making it work in a declarative way.

EDIT

I have something working, but still struggling outside of just guessing what is happening.

// component.ts

public authenticatedUser$ = this._authService.authenticatedUser$.pipe(
        catchError((err) => {
            return EMPTY;
        })
    );
    
...


ngOnInit(): void {
        this.authenticatedUser$
            .pipe(
                switchMap((user) =>
                    this._organizationService
                        .getOrganization(user.organizationId)
                        .pipe(tap((data) => console.log('data: ', data))) // {id: '123', name: 'My Org', ...}
                ),
                catchError((err) => {
                    return EMPTY;
                })
            )
            .subscribe();
    }

Since I am using switchMap will I also need to unsubscribe? Perhaps in the ngOnDestory method or something?

CodePudding user response:

The last code posted is fine, it should work. You could use mergeMap or switchMap, it depends on your needs, switchMap discards the current value if another one is emitted, and mergeMap works with every new value emitted.

Giving you an analogy for this, you are eating a meal while using switchMap, suddenly, the waitress comes with a new meal, then, you proceed to discard your current meal, and start eating the new one, it doesn't matter if you finished your first meal, you just discard it. If you were using mergeMap, you would eat both meals...

If you were using exhaustMap while eating the first meal, and the second meal comes, you would ignore this second one and incoming ones until you finish the current meal, and those ignored meals are lost... and lastly, if you were using concatMap while eating your fist meal, and the second one comes at you, you would wait until you finish your first meal, and only then, you start eating the second one...

If you don't want to handle the subscription, then just store it in an Observable the result of authenticatedUser$ and the service call getOrganization for then subscribe using the async pipe, which in this case, is the reactive approach:

userOrganization$!: Observable<SomeType>;

ngOnInit(): void {
  this.userOrganization$ = this.authenticatedUser$.pipe(
    switchMap((user) =>  
       this._organizationService.getOrganization(user.organizationId))
  );
}

HTML

<ng-container *ngIf="(userOrganization$ | async) as userOrganization">
 <div>
   <p>{{ userOrganization.id }}</p>
   <p>{{ userOrganization.name }}</p>
   ...
 </div>
</ng-container>

Also, if you want to do it in a procedural way, you should store the subscription, and then destroy it in ngOnDestroy lifecycle hook:

private sub = new Subscription();


ngOnInit(): void {
  this.sub.add(
    this.authenticatedUser$.pipe(...).subscribe();
  );
}

ngOnDestroy(): void {
  this.sub?.unsubscribe();
}

The tap operator is used for handling side effects, so in this case, it is correct the way you are using it, you are console.logging in this case.

If you have an observable and you want to use tap, you can do it directly without using map

// there is no need for using map here
myObservable$.pipe(tap((data) => console.log('The data: ', data))).subscribe(...);
  • Related