0

Setup:

I have a function, isAuthorized(), in a singleton, AuthSessionSingleton which relies on the result of an async function.

The async function is an api call, called in the constructor, which expects the return of an object. isAuthorized() relies on that object.


Issue:

Some component calls the function isAuthorized() immediately, before the backend call resolves. I want to be able to wait inside the isAuthorized() function until the backend call resolves and the object is no longer null.


Simple Code:

//auth.singleton.ts
@Injectable({providedIn: 'root'})
export class AuthSessionSingleton {
  userInfo: UserInfo = null;
  constructor(private service: MyService) {
    this.loadUserInfo();
  }
  
  loadUserInfo() {
    this.service.makeBackendCall().subscribe({
      next: (obj: UserInfo) => {
        this.userInfo = obj;
      }
    });
  }
  
  isAuthorized() {
    if(this.userInfo == null) {
      // do something to wait asynchronously
    }

    // Very simple example
    if(this.userInfo.Role == 'Admin') {
      return true;
    }
    return false;
  }
}
//some.component.ts
export class SomeComponent {
  authorized: boolean = false;
  
  constructor(private auth: AuthSessionSingleton) {
    this.checkAuthorization();
  }
  
  async checkAuthorization() {
    this.authorized = await this.auth.isAuthorized();
  }
}

In the above simplified program, SomeComponent calling isAuthorized() will likely check userInfo while it is still null. I need to wait for it to not be null.


Possible Solutions:

There are many solutions I've determined so far. I'm looking for the most correct solution and hopefully one that I haven't even thought of.

1. Listen to Subscription

In isAuthorized(), if(this.userInfo==null) subscribe to the same subscription made in loadUserInfo() and wait on it.

2. Wait Until Signaled

I could create a Subject in AuthSessionSingleton and Subscribe from SomeComponent, wait until I get a signal, then call IsAuthorized().

This seems especially roundabout.

3. Just Wait

There are many ways I could just spin my gears until the object I need is no longer null.

while(this.userInfo == null) {
  // do something
}

Request:

I want a way to wait asynchronously in isAuthorized() until this.userInfo != null. Any number of calls can be made to isAuthorized() (from different components) and all of them should wait until that value is resolved.

Any ideas?


Edit

A more accurate use-case which I now realize changes the possible solutions follows. It is made more complex by accepting an array of values to loop through.

isAuthorized(roles: string[]) {
  let(i = 0; i < roles.length; i++) {
    let role = roles[i];
    if(this.userInfo.Role == role) {
      return true;
    }
    return false;
  }
}
2
  • I'd go with solution 2, but instead you could make the isAuthorized() function asynchronous and return the status after the call is complete. In solution 1, if you subscribe to the same end-point, you would be triggering an additional unwanted HTTP request. Solution 3 could potentially lead to an infinite loop if the back-end doesn't reply. Commented May 28, 2020 at 13:07
  • will it be fine if your app doesn't initilialize until your api call is complete? Commented May 28, 2020 at 13:33

2 Answers 2

1

When there is one async operation involved, you could make all it's dependents asynchronous. Try the following

auth.singleton.ts

@Injectable({providedIn: 'root'})
export class AuthSessionSingleton {
  userInfo = new BehaviorSubject<UserInfo>(null);

  constructor(private service: MyService) {
    this.loadUserInfo();
  }

  loadUserInfo() {
    this.service.makeBackendCall().subscribe({
      next: (obj: UserInfo) => {
        this.userInfo.next(obj);
      }
    });
  }

  isAuthorized() {
    const result = new Subject<boolean>();

    this.userInfo.pipe(take(1)).subscribe(
      userInfo => {
        if (userInfo == 'Admin') {
          result.next(true);
        } else {
          result.next(false)
        }
      }
    );

    return result.asObservable();
}

Now you could subscribe to the isAuthorized() function in the components. You could also expose userInfo to check authorization in the component as well.

Component

ngOnInit() {
  this.authService.isAuthorized().subscribe(
    status => {
      if (status) {
        // authorized
      } else {
        // unauthorized
      }
    }
  );
}

As mentioned in the comments, I wouldn't prefer solutions 1 and 2 for the following reasons

  1. Solution 1: The subscription could trigger an additional unwanted HTTP request.
  2. Solution 3: Could potentially lead to an infinite loop if the back-end doesn't reply.
Sign up to request clarification or add additional context in comments.

1 Comment

I appreciate the thorough solution. I've added an edit to show a more complex situation which I now realize would make your solution more difficult to implement.
1

The below code will make sure that your app initialized only after your loadUserInfo is complete. So other components will only call isAuthenticated when app will initialize and your userinfo is already there.

In your AppModule have this :-

{
      provide: APP_INITIALIZER,
      useFactory: init_app,
      deps: [AuthSessionSingleton],
      multi: true
 }

 export function init_app(authService: SessionService) {
  return () => authService.loadUserInfo();
 }

Change your AuthSessionSingleton :-

@Injectable({providedIn: 'root'})
export class AuthSessionSingleton {

  constructor(private service: MyService) {
  }

  loadUserInfo() {
    return this.service.makeBackendCall().pipe(map((obj: UserInfo) => {
        this.userInfo.next(obj);
    })).toPromise();
  }

   isAuthorized() {
      if(this.userInfo == null) {
          // do something to wait asynchronously
      }

      // Very simple example
      if(this.userInfo.Role == 'Admin') {
        return true;
      }
      return false;
   }
}

2 Comments

While this would work, it seems to defeat the purpose of using async calls and allowing everything else to continue working in the mean time.
but it will make your app stable everywhere. because user info is main core requirement right?

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.