1

I'm writing a nav component for an Angular app. I've got the following code. I want to avoid the multiple subscription anti-pattern. I am struggling with the RxJs syntax, and which way to go (forkJoin, mergeMap, etc).

How can I refactor these, to remove the subscribe within the subscribe.

Here's what I have, which currently works, but has a subscribe within a subscribe:

@Component({
  selector: 'ehrcc-nav',
  templateUrl: './nav.component.html',
  styleUrls: ['./nav.component.css']
})
export class NavComponent implements OnInit {

  applicationName: string = 'AppName';
  userDisplayName: string = '';
  isAuthorizedUser: boolean = false;
  isAdminUser: boolean = false;

  groupsList: MemberGroup[] = [];

  constructor(private userService:UserService,
    private auditService: UserAuditService,
    private router: Router) { }

  ngOnInit() {

    this.getDisplayName();

    this.userService.getGroupMembershipsForUser().subscribe(members =>{
      this.groupsList = members;
      for (let g of this.groupsList){
        if (g.id === this.userService.usersGroupId){
          this.isAuthorizedUser = true;
          this.router.navigate(['/workItem']);
        }
        if (g.id === this.userService.adminGroupId){
          this.isAdminUser = true;
        }
      }
      this.logUserInfo();   <---- ANTI-PATTERN
     });

  }

  getDisplayName(){
    this.userService.getSignedInAzureADUser().subscribe(
      (user) => this.userDisplayName = user.displayName,
      (error: any) => {
        return console.log(' Error: ' + JSON.stringify(<any>error));
    });
  }

  logUserInfo(){
    var audit = new UserAudit();
    audit.Application = this.applicationName;
    audit.Environment = "UI";
    audit.EventType= "Authorization";
    audit.UserId = this.userDisplayName;
    audit.Details = ` User Is Authorized: ${this.isAuthorizedUser}, User Is Admin: ${this.isAdminUser}`;

    this.auditService.logUserInfo(audit)
    .subscribe({ 
      next: (id)=> console.log('Id created: '+ id),
      error: (error: any) => console.log(' Error: ' + JSON.stringify(<any>error) )
    });
  }
}

8
  • Why dont you use the map operator in your Observable and emit your data modified? can you post your source Observable¿? Commented Jul 19, 2019 at 10:46
  • Personally I recommend to use ngrx Statemanagement for such tasks. Chaining actions can be much easier then joining responses. And of course it is easiert to test them. Commented Jul 19, 2019 at 10:46
  • @Robertgarcia I have posted the source observable, or what I think you mean when you are asking for the source observable. It shows what I had to do to get the original data out of another API. Commented Jul 19, 2019 at 10:55
  • @MoxxiManagarm Thanks for the comment. I have not used the technology you are speaking of. Commented Jul 19, 2019 at 10:56
  • 1
    Possible duplicate of Angular - two subscriptions in ngOnInit result in object 'undefined' Commented Jul 20, 2019 at 16:04

2 Answers 2

2

I found this question because I was searching why some subscribe code blocks were being executed multiple times with no apparent reason.

I found out that this was due to services that returned some Observables which were not yet loaded with the proper data. Thus I solved this issue with auditTime which:

Ignores source values for duration milliseconds, then emits the most recent value from the source Observable.

Therefore, using the question's code, a pipe can be added with that operator:

import { auditTime, ... } from 'rxjs/operators';

this.userService.getGroupMembershipsForUser().
 .pipe(auditTime(1E3)) // delays for 1 second and gets the most recent value
 .subscribe(members => { ... });
Sign up to request clarification or add additional context in comments.

Comments

1

You can use forkJoin https://www.learnrxjs.io/operators/combination/forkjoin.html

forkJoin({
   displayName: this.userService.getSignedInAzureADUser() // This will give you the 
   observable subscription value,
   groupMemberShip:this.userService.getGroupMembershipsForUser() 
})

Once you subscribe to that forkJoin you will get an object with all the values and you can call the logUserInfo from it, all the observables needs to complete() in order to emit the forkJoin

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.