I'm building an authentication app using the PEAN stack (i.e., PostgreSQL - ExpressJS - Angular - NodeJS).
I have two components: dashboard.component.ts
and edit-profile.component.ts
. They both subscribe to getUser
in ngOnInit()
, as follows:
ngOnInit(): void {
console.log('ngOnInit triggered');
this.authService.getUser().subscribe(this.getUserObserver);
}
Problem
The problem is that if I refresh the page (e.g. press the F5 key):
dashboard.component.ts
does triggerngOnInit()
; whileedit-profile.component.ts
does not triggerngOnInit()
.
I've been able to confirm this in two ways:
- using
console.log('ngOnInit triggered');
and - using the Network tab in Developer Tools.
After the dashboard.component.ts
page refresh:
After the edit-profile.component.ts
page refresh:
Question
Why is ngOnInit()
triggered if I refresh dashboard.component.ts
but not triggered if I refresh edit-profile.component.ts
?
Note: Those two components have different code (form groups, error messages, observers, etc.), but I don't think the code in the components is causing the problem. The biggest difference is the routing. See the relevant part of the app-routing.module.ts
below. Also, auth guards should work as expected as long as a component makes an API call to the get-user
API endpoint because both auth guards rely on the same information whether the get-user
returns 200
or 400
.
app-routing.module.ts
const routes: Routes = [
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
{ path: 'dashboard', component: DashboardComponent },
{ path: 'dashboard/:token', component: DashboardComponent, canActivate: [IfSignedIn] },
{
path: 'profile',
component: MainProfileComponent,
canActivate: [IfSignedIn],
children: [
{ path: 'edit-profile', component: EditProfileComponent, canActivate: [IfSignedIn] },
],
},
];
EDIT
See the full code for both components below.
dashboard.component.ts
import { Component, OnInit } from '@angular/core';
import { AuthService } from 'src/app/services/auth/auth.service';
import { Router } from '@angular/router';
import { MatSnackBar } from '@angular/material/snack-bar';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss'],
})
export class DashboardComponent implements OnInit {
showUserData: any = '';
readonly getUserObserver = {
next: (x: any) => {
this.showUserData = x;
},
error: (err: any) => {
if (err.status == 401) {
this.snackBar.open('Token invalid', 'Close', {
duration: 5000,
panelClass: ['red-snackbar'],
});
}
if (err.status == 500) {
this.snackBar.open('Internal server error', 'Close', {
duration: 5000,
panelClass: ['red-snackbar'],
});
}
},
};
readonly editProfileConfirmationObserver = {
next: (x: any) => {
this.dashboardRouter.navigateByUrl('/refresh', { skipLocationChange: true }).then(() => {
this.snackBar.open('Profile changes saved successfully', 'Close', {
duration: 5000,
panelClass: ['green-snackbar'],
});
});
},
error: (err: any) => {
if (err.status == 401) {
this.snackBar.open('Link invalid or expired, please edit your profile again', 'Close', {
duration: 5000,
panelClass: ['red-snackbar'],
});
}
},
};
constructor(private authService: AuthService, private dashboardRouter: Router, private snackBar: MatSnackBar) {}
ngOnInit(): void {
console.log('ngOnInit triggered');
this.authService.getUser().subscribe(this.getUserObserver);
this.authService.editProfileConfirmationLink().subscribe(this.editProfileConfirmationObserver);
}
}
edit-profile.component.ts
import { Component, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { FormControl } from '@angular/forms';
import { Validators } from '@angular/forms';
import { AuthService } from 'src/app/services/auth/auth.service';
import { Router } from '@angular/router';
import { MatSnackBar } from '@angular/material/snack-bar';
import { MatDialog } from '@angular/material/dialog';
import { EditProfileDialogComponent } from './dialogs/edit-profile-dialog/edit-profile-dialog.component';
@Component({
selector: 'app-edit-profile',
templateUrl: './edit-profile.component.html',
styleUrls: ['./edit-profile.component.scss'],
})
export class EditProfileComponent implements OnInit {
oldEmail: string = 'Initial value';
showUserData: any = '';
formEditProfile = new FormGroup({
editProfileEmail: new FormControl('', [
Validators.required,
Validators.pattern('^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$'),
]),
});
getErrorMessageEmail() {
if (this.formEditProfile.controls['editProfileEmail'].hasError('required')) {
return 'Email is required';
}
return this.formEditProfile.controls['editProfileEmail'].hasError('pattern') ? 'Not valid email' : '';
}
readonly getUserObserver = {
next: (x: any) => {
this.showUserData = x;
this.oldEmail = x.email;
},
error: (err: any) => {
this.snackBar.open('User data fetched unsuccessfully', 'Close', {
duration: 5000,
panelClass: ['red-snackbar'],
});
},
};
onSubmitEditProfile() {
const editProfileObserver = {
next: (x: any) => {
this.editProfileDialog.open(EditProfileDialogComponent, {
panelClass: 'custom-dialog-container',
width: '550px',
});
},
error: (err: any) => {
if (err.status == 409) {
this.snackBar.open(err.error.message, 'Close', {
duration: 5000,
panelClass: ['red-snackbar'],
});
}
if (err.status == 401) {
this.snackBar.open('Token invalid or expired, please try again', 'Close', {
duration: 5000,
panelClass: ['red-snackbar'],
});
}
if (err.status == 400) {
this.snackBar.open('Something went wrong', 'Close', {
duration: 5000,
panelClass: ['red-snackbar'],
});
}
if (err.status == 500) {
this.snackBar.open('Internal server error', 'Close', {
duration: 5000,
panelClass: ['red-snackbar'],
});
}
},
};
this.authService.editProfileNewEmail(this.formEditProfile.value.editProfileEmail!).subscribe(editProfileObserver);
}
constructor(private authService: AuthService, private editProfileRouter: Router, private snackBar: MatSnackBar, private editProfileDialog: MatDialog) {}
ngOnInit(): void {
console.log('ngOnInit triggered');
this.authService.getUser().subscribe(this.getUserObserver);
}
}