I am trying to fetch a user on the angular app initialization from my api which needs a Token provided by Keycloak.
First thing first: Why I am trying to do such things ?
I am new to Angular and web development in general. I'm developing a portal for users which are authenticated via Keycloak. A user can access to only his own information and, in order to avoid fetching user on each component, I wanted to fetch it on app initialization in a UserService to be able to share it to my all app via my service.
I'm using Angular 15.
So I tried the following: first fetch Keycloak token, then fetch the user from my api. As I understand it, I have to nest my second Observable, which is the one in charge of the fetched user in the first one which is the Keycloak one. And if I successfully retrieve my token and my user with the token, the app still launch before the return of my second Observable and I can't display user information at the first render, which is what I want.
I don't understand how to say to Angular to wait for the second Observable.
My app module
providers: [
{
provide: APP_INITIALIZER,
useFactory: initializeUser,
multi: true,
deps: [UserService, KeycloakService]
}
]
function initializeUser(userService: UserService, keycloak: KeycloakService) {
return () => {
return keycloak.init({
config: {
url: 'https://mykeycloakurl.com',
realm: environment.authenticationRealm,
clientId: 'dashboard-users'
},
initOptions: {
onLoad: 'check-sso',
silentCheckSsoRedirectUri:
window.location.origin + '/assets/silent-check-sso.html'
}
}).then(_ => userService.init())
}
}
My User service
@Injectable({
providedIn: 'root',
})
export class UserService {
public user!: User
constructor(public userRepository: UserRepository){}
init(){
return this.userRepository.getById((myUserId).subscribe(user=>{
this.user= user
})
}
}
My repository
getById(userId: typeof uuid): Observable<User> {
return this.http.get<User>(UserPaths.computeUserPath(userId)).pipe(
map(user=> {
return plainToInstance(User, user);
})
);
}
Already seen those topic:
But didn't found my answer in them as they are a bit too old for my angular version.
If someone can tell me what I'm doing wrong or if I simply don't use the correct pattern/angular-tool to achieve such goal (have a user available in the whole app with only one fetch), I would strongly appreciate.
EDIT: I've also tried to wrap the return of the initializeUser function in a Promise<Boolean>. Here is the code :
function initializeUser(userService: UserService, keycloak: KeycloakService) {
return new Promise<Boolean>(resolve =>
keycloak.init({
config: {
url: 'https://mykeycloakurl.com',
realm: environment.authenticationRealm,
clientId: 'dashboard-user'
},
initOptions: {
onLoad: 'check-sso',
silentCheckSsoRedirectUri: window.location.origin + '/assets/silent-check-sso.html'
}
}).then(_ => userService.init().subscribe(user=> {
userService.user= user;
resolve(true);
}));
)
}
And so the update of the service init():
init(): Observable<User>{
return this.userRepository.getById(myUserid)
}
If it waits correctly for the user to be fetched (I thought I resolved this), it no longer redirect on Keycloak login if the authentication is expired or invalid.