TL;DR - Solution:
I was using subscribe rather than pipe the value to the APP_INITIALIZER. Also, I was registering twice UserService, once as a singleton (which it should be) and second time within the Header component it self, which basically was a new instance of UserService.
=======================
I'm calling my UserService from app.config.ts:
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideHttpClient(),
provideAnimationsAsync(),
{
provide: APP_INITIALIZER,
useFactory: (userService: UserService) => {
return () => userService.getSessionMock();
},
deps: [UserService],
multi: true,
},
],
};
UserService:
public user$ = new BehaviorSubject<AuthUser | null>(null);
constructor(private readonly http: HttpClient) {}
public getSession() {
return this.http
.get<SessionUser>('https://localhost:7298/api/auth/session', {
withCredentials: true,
})
.subscribe((user) => {
(user as AuthUser).isAuthenticated = true;
this.user$.next(user as AuthUser);
});
}
This doesn't work even when I'm using a mock of a user. It works when I run the code from ngOnInit on the component itself.
HeaderComponent which uses the data:
export class HeaderComponent {
userService: UserService = inject(UserService);
currentUser$ = this.userService.user$;
ngOnInit() {
this.currentUser$.subscribe((user) => console.log(user));
}
}
header.component.html
<div *ngIf="currentUser$ | async as currentUser">
<a href="https://localhost:7298/api/auth" *ngIf="!currentUser.isAuthenticated">
<button class="bg-white text-cyan-500 px-4 py-2 rounded-full">Login</button>
</a>
@if(currentUser.isAuthenticated) {
<span>{{currentUser.name}}</span>
<a href="https://localhost:7298/api/auth/logout">
<button class="bg-white text-cyan-500 px-4 py-2 rounded-full">Logout</button>
</a>
}
</div>
getSessionMock
public getSessionMock() {
this.user$.next(mockUser);
}
You have to return the observable and not subscribe to it, also you can use tap
to perform side effects inside the stream.
Only then the user$
will be initialized before the application loads.
public user$ = new BehaviorSubject<AuthUser | null>(null);
constructor(private readonly http: HttpClient) {}
public getSession() {
return this.http
.get<SessionUser>('https://localhost:7298/api/auth/session', {
withCredentials: true,
})
.pipe(
tap((user) => {
(user as AuthUser).isAuthenticated = true;
this.user$.next(user as AuthUser);
})
);
}
Also why are you calling getSessionMock
shouldn't it be getSession
.
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideHttpClient(),
provideAnimationsAsync(),
{
provide: APP_INITIALIZER,
useFactory: (userService: UserService) => {
return () => userService.getSession();
},
deps: [UserService],
multi: true,
},
],
};