I was required to update a project's UI without breaking any logic(I broke). In the old UI authentication(login page) and the rest(other resources that you get access to after authentication) were in the same layout(header + content + footer), but in the new one authentication page has a different layout than the applications layout(after logging in). So I separated layouts in such way:
RouterModule.forRoot([
{ path: 'auth', component: AuthLayoutComponent,
children: [
{ path: '', component: AuthHomeComponent },
{ path: 'login', component: LoginComponent},
{ path: 'terms-of-use', component: TermsOfUseComponent},
{ path: 'contact', component: AuthContactFormComponent}
]
},
{ path: '', component: HomeComponent,
children: [
{ path: 'contact', component: ContactFormComponent },...]
bootstrap: [AppComponent]
HomeComponent
is a layout for application while AuthLayoutComponent
is layout for authentication part.
Old configuration:
RouterModule.forRoot([
{ path: '', component: HomeComponent, pathMatch: 'full' },
{ path: 'contact', component: ContactFormComponent },
...]
bootstrap: [AppComponent]
New AppComponent
:
<ng-container>
<router-outlet></router-outlet>
<sac-alert></sac-alert>
<sac-loader></sac-loader>
</ng-container>
Old AppComponent
:
<app-header></app-header>
<div class="body-content">
<router-outlet></router-outlet>
</div>
<app-footer></app-footer>
New HomeComponent
:
<div class="admin-container">
<app-navigation></app-navigation>
<div class="admin-section-container">
<app-header></app-header>
<main>
<ng-container>
<router-outlet></router-outlet>
</ng-container>
</main>
</div>
</div>
Everything worked as expected, until I noticed that HeaderComponent
doesn't show logged user's name and surname after logging in, while in the old version it would immediately appear on the right top corner.
HeaderComponent
:
<div ngbDropdown class="d-inline-block dropdown" *ngIf="isLoggedIn && user">
<button type="button" class="btn" ngbDropdownToggle id="userDropdown">
<img src="assets/icons/combo shape.svg" alt=""> <span *ngIf="user.surname">{{user.surname}}</span>
<span *ngIf="user.name">{{user.name}}</span>
</button>
HeaderComponent.ts
:
constructor(
private apiService: ApiService,
public entityMapService: EntityMapService,
public changeNotificationsService: ChangeNotificationsService,
public router: Router,
public cdr: ChangeDetectorRef
) {
this.isProduction = environment.production === true;
this.apiService.currentUserChanged.subscribe(user => {
this.user = user;
this.isLoggedIn = this.user != null;
});
}
currentUserChanged
in ApiService
:
public currentUser: UserInfo;
private currentUserChangeObservers: Observer<UserInfo>[] = [];
public currentUserChanged = new Observable<UserInfo>(observer => {
this.currentUserChangeObservers.push(observer);
});
UserInfo
is just a interface.
Now as I said in old version as soon as you logged in your name would appear in the right top corner of HeaderComponent
, but after moving HeaderComponent
to different layout without changing any of its typescript code, it doesn't show username and surname without a page reload(f5). So after logging in I need to refresh the page for changes to take place.
Using console.log I saw that subscription logic in HeaderComponent is working well(user is not null), but when console.log(this.user)
within ngOnInit I noticed that it gives null
.
But in HeaderComponent
's constructor:
this.apiService.currentUserChanged.subscribe(user => {
this.user = user;
console.log(this.user)//gives actual object
this.isLoggedIn = this.user != null;
});
}
After refresh it works fine, but it is not supposed to work that way, is it ?
I tried:
After trying and testing the code I saw that when I moved the HeaderComponent
into AuthLayoutComponent
's children section(or just pasted it in AuthLayoutComponent
) it worked without any problem with the same typescript. From here I decide that the issue is due to using different Layouts and children properties on Route configuration. I know the problem but I don't know how to resolve it while keeping the new UI.
Anyone who knows why ChangeDetection doesn't work without refreshing page when different Layouts are used ?(services are shared so they should detect changes).
I tried to change RouteConfiguration to this, but ChangeDetection still requires page refresh:
const routes: Routes = [
{
path: '',
children: [
{
path: 'auth',
component: AuthLayoutComponent,
children: [
{ path: '', redirectTo: 'login', pathMatch: 'full' },
{ path: 'login', component: LoginComponent },
{ path: 'terms-of-use', component: TermsOfUseComponent },
{ path: 'contact', component: AuthContactFormComponent }
]
},
{
path: 'home',
component: HomeComponent,
children: [
]
},
{ path: '', redirectTo: 'home', pathMatch: 'full' },
]
},
Angular 17.3.0, Zone.js 0.14.4
I would set this up more like - using Subjects to create a global state management.
// ApiService file
@Injectable({providedIn: root})
export class ApiService {
// properties for the current user
private _currentUser$ = new ReplaySubject<User>(1);
public readonly currentUser$ = this._currentUser$.asObservable();
// a method that updates the currentUser, wherever this gets called ...
updateUserMethod(user: User) {
this._currentUsers$.next(user);
}
}
The above, is set as providedIn: root
should be available everywhere. Then you can in your HeaderComponent
constructor you can have:
this.apiService.currentUser$.subscribe(user => {
// do with the user what you need
this.user = user;
})