I'm trying to persist component data when user close/refresh tab or (click back/forth) using Router Reuse Strategy, but after adding CustomReuseStrategy provider routerLink stopped working properly.
After login we are redirected to '/dashboard/begin', then if i try to go '/dashboard/search' or '/dashboard/123' via routerLink the component doesn't change
However, a direct access url works well
Has anyone ever experienced this?
app.module.ts
#NgModule({
providers: [
AuthGuard,
{
provide: RouteReuseStrategy,
useClass: CustomReuseStrategy
}]
})
app.routing.ts
const ROUTES: Routes = [
{ path: 'dashboard', loadChildren: './dashboard/dashboard.module#DashboardModule', canActivateChild: [AuthGuard]},
{ path: 'login', component: LoginComponent },
{ path: '', redirectTo: '/login', pathMatch: 'full' }
]
dashboard.routing.ts
const DASHBOARD_ROUTES: Routes = [
{
path: '', component: DashboardComponent,
children: [
{
path: 'begin',
component: BeginListComponent,
},
{
path: 'search',
children: [
{path: '', component: SearchListComponent },
{path: ':id', component: SearchIdComponent}
]
}
}
]
custom.reuse.strategy.ts
import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '#angular/router';
export class CustomReuseStrategy implements RouteReuseStrategy {
public static handlers: { [key: string]: DetachedRouteHandle } = {};
private static waitDelete: string;
public static deleteRouteSnapshots(): void {
CustomReuseStrategy.handlers = {};
}
public static deleteRouteSnapshot(name: string): void {
if (CustomReuseStrategy.handlers[name]) {
delete CustomReuseStrategy.handlers[name];
} else {
CustomReuseStrategy.waitDelete = name;
}
}
public shouldDetach(route: ActivatedRouteSnapshot): boolean {
console.log(route);
if (!route) {
CustomReuseStrategy.deleteRouteSnapshots();
return false;
}
if (route.params && Object.keys(route.params).length > 0) {
return false;
}
return true;
}
public store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
if (
CustomReuseStrategy.waitDelete &&
CustomReuseStrategy.waitDelete === this.getRouteUrl(route)
) {
CustomReuseStrategy.waitDelete = null;
return;
}
CustomReuseStrategy.handlers[this.getRouteUrl(route)] = handle;
}
public shouldAttach(route: ActivatedRouteSnapshot): boolean {
return !!CustomReuseStrategy.handlers[this.getRouteUrl(route)];
}
public retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
if (!route.routeConfig) {
return null;
}
return CustomReuseStrategy.handlers[this.getRouteUrl(route)];
}
public shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
return (
future.routeConfig === curr.routeConfig &&
JSON.stringify(future.params) === JSON.stringify(curr.params)
);
}
private getRouteUrl(route: ActivatedRouteSnapshot) {
return route['_routerState'].url.replace(/\//g, '_');
}
}
Related
im starting to work with Angular, and im trying to create a simple route guard, to redirect user to login page, if my service return unauthorized.
To do that i created this route schema ->
const routes: Routes = [
{
path: '',
component: LoggedComponent,
children: [
{path: '', component: HomeComponent}
],
canActivate: [RouteGuard]
},
{
path: '',
component: AuthComponent,
children: [
{path: '', redirectTo: '/login', pathMatch: 'full'},
{path: 'login', component: LoginComponent},
{path: 'signin', component: SigninComponent}
]
},
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
And this is my guard service ->
PS: Im setting a default value false.
import {Subject, Observable} from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class RouteGuard implements CanActivate {
authorized: Subject<boolean> = new Subject();
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean {
this.setObservable(false)
return false;
}
getObservable(): Observable<boolean> {
console.log(`starts to observe`)
return this.authorized.asObservable();
}
setObservable(newState: boolean) {
console.log(`new observable state: ${newState}`)
this.authorized.next(newState)
}
}
Ok, since the value is returning false as default, I expect the route to be automatically redirected to the AuthComponent, because Auth is the second option at my routes[]. Right?
So...
At the AuthComponent i stated to observe the authorized status:
import {RouteGuard} from '#acn-collections-ws/shared';
#Component({
selector: 'acn-collections-ws-auth',
templateUrl: './auth.component.html',
styleUrls: ['./auth.component.scss']
})
export class AuthComponent implements OnInit {
constructor(private guard: RouteGuard, router: Router) {
console.log('im here');
this.guard.getObservable().subscribe(authorized => {
})
}
ngOnInit(): void {
}
}
But AuthComponent dosent load. it seems that when the canActivate parameter returns false, it does not go to the AuthComponent, it does not load anything. When the authorized (canActivate) returns true, it runs normally. Has anyone had a similar problem and can help me?
This is how I do it when authenticating using Firebase:
export class GuardGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router){}
async canActivate() {
const user = await this.authService.isLogged();
if(user){
return true;
}
else {
this.router.navigate(['login']);
return false;
}
}
}
If the user's logged return true so it loads the requested route if not redirects to the login route and return false.
And this is the routing:
import { NgModule } from '#angular/core';
import { Routes, RouterModule } from '#angular/router';
import { LoginComponent } from './modules/login/pages/login/login.component';
import { SignupComponent } from './modules/login/pages/signup/signup.component';
import { HeroComponent } from './shared/components/hero/hero.component';
import { NotFoundComponent } from './shared/components/not-found/not-found.component';
import { GuardGuard } from './shared/guards/guard.guard';
const routes: Routes = [
{ path: 'home', component: HeroComponent },
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'signup', component: SignupComponent },
{ path: 'login', component: LoginComponent },
{ path: 'tasks', loadChildren: ()=> import('./modules/task/task.module').then(m => m.TaskModule), canActivate: [GuardGuard] },
{ path: 'profile', loadChildren: ()=> import('./modules/user/user.module').then(m => m.UserModule), canActivate: [GuardGuard] },
{ path: '**', component: NotFoundComponent }
];
I am trying to use my service for getting values from database server to display onscreen. I do so with a resolver for the service as the database is a little slow sometimes.
But the data this.route.data.subscribe gives me is always undefined no matte what I tried. I checked if the service is getting a response from the server, and it does. Weird thing is that if I use the service directly everything works fine.
Component where the data is processed:
import { Component, OnInit, Input } from '#angular/core';
import { TempsService, Temps } from '../../temps.service';
import { ActivatedRoute } from '#angular/router';
#Component({
selector: 'app-temps',
templateUrl: './temps.component.html',
styleUrls: ['./temps.component.scss']
})
export class TempsComponent implements OnInit {
#Input() solar: boolean;
solarURL: string = 'tempSolar';
waterURL: string = 'tempWater';
tempSolar: number;
tempWater: number;
timestamp: string;
temps: Temps;
constructor(private route: ActivatedRoute,
private tempService: TempsService) { }
showWaterTemp() {
this.tempService.getTemp(this.waterURL)
.subscribe(data => {
this.tempWater = data.rawValue;
this.timestamp = data.time;
});
}
showSolarTemp() {
this.route.data
.subscribe(data => {
this.tempSolar = data.rawValue;
});
}
ngOnInit() {
if (this.solar) {
this.showSolarTemp();
this.showWaterTemp();
}
}
}
This is he routing module (I am using the NowUI Angular theme by CreativeTim, so most things were done by them):
import { Routes } from '#angular/router';
import { DashboardComponent } from '../../dashboard/dashboard.component';
import { UserProfileComponent } from '../../user-profile/user-profile.component';
import { TableListComponent } from '../../table-list/table-list.component';
import { TypographyComponent } from '../../typography/typography.component';
import { IconsComponent } from '../../icons/icons.component';
import { MapsComponent } from '../../maps/maps.component';
import { NotificationsComponent } from '../../notifications/notifications.component';
import { TempsComponent } from '../../dashboard/temps/temps.component';
import { TempResolver } from '../../temp-resolver/temp-resolver.resolver';
export const AdminLayoutRoutes: Routes = [
{ path: 'dashboard', component: DashboardComponent, children: [
{ path: '', component: TempsComponent, resolve: { temps: TempResolver } }
] },
{ path: 'user-profile', component: UserProfileComponent },
{ path: 'table-list', component: TableListComponent },
{ path: 'typography', component: TypographyComponent },
{ path: 'icons', component: IconsComponent },
{ path: 'maps', component: MapsComponent },
{ path: 'notifications', component: NotificationsComponent }
];
And this is how the resolver looks like:
import { Injectable } from '#angular/core';
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '#angular/router';
import { Temps, TempsService } from '../temps.service';
import { Observable } from 'rxjs/internal/Observable';
#Injectable()
export class TempResolver implements Resolve<Temps> {
test: number;
constructor(private tempService: TempsService) { }
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Temps> {
this.tempService.getTemp('tempSolar').subscribe(data => {this.test = data.rawValue})
alert(this.test)
return this.tempService.getTemp('tempSolar');
}
}
In my opinion this is a really strange problem.
UPDATE:
This is the service for getting the data:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { TempsComponent } from './dashboard/temps/temps.component'
export interface Temps {
id: string;
time: string;
date: string;
name: string;
rawValue: number;
}
#Injectable()
export class TempsService {
constructor(private http: HttpClient) { }
url: string = window.location.hostname;
tempUrl = 'http://' + this.url + ':3000/latestTime/';
getTemp(temp: String) {
return this.http.get<Temps>(this.tempUrl + temp);
}
}
I just tried adding the resolve to the dashboard component in which the Temp component is used. And now it works like a charm.
It looks now like this:
{ path: 'dashboard', component: DashboardComponent, resolve: {temps: TempResolver} }
instead of this:
{ path: 'dashboard', component: DashboardComponent,
children: [{ path: '', component: TempsComponent, resolve: { temps: TempResolver } }]
},
Can you try this
this.route.data
.subscribe(({temps}) => {
this.tempSolar = temps;
});
No matter what, avoid subscribing to getTemps() in resolve(), simply return an Observable<whatever>. Keep in mind the asynchronous nature of getTemps(). alert(this.test) will almost always execute before getTemps() has been completed, basically guaranteeing it will be undefined at the time of alert.
Simply return getTemp() so that it returns an Observable<Temps>:
import { Injectable } from '#angular/core';
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '#angular/router';
import { Temps, TempsService } from '../temps.service';
import { Observable } from 'rxjs';
#Injectable()
export class TempResolver implements Resolve<Temps> {
constructor(private tempService: TempsService) { }
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Temps> {
return this.tempService.getTemp('tempSolar');
}
}
Then in the component extracting the rawValue property as needed:
showSolarTemp() {
this.route.data.subscribe((data: { temps: Temps }) => {
this.tempSolar = data.temps.rawValue;
});
}
Here is a StackBlitz showing the functionality in action.
Hopefully that helps!
I have the following routes in the application. the problem here is if I navigate to say getEmp-by-id or page-not-found and hit refresh, then application is landing on app-home,. But I want it to stay on the same page where refresh is hit.I am not implementing any RouteGuards, simple navigations. Is there a way I can acheive this.
const appRoutes: Routes = [
{path: '', component: HomeComponent, children: [
{path: 'app-home', component: AppHomeComponent, resolve: {ApphomeResolver : AppHomeResolver}},
{path: 'getEmp-by-id', component: EmpComponent},
{path: 'page-not-found', component: pageNotFoundComponent},]
},
{path: '**', redirectTo: 'page-not-found', pathMatch: 'full'}
];
export class EmpComponent implements OnInit {
constructor(private router: Router, private route: ActivatedRoute, private alertService: AlertService, private employeeService: EmployeeService) { }
ngOnInit() {}
onSubmit() {
this.employeeService.getEmployee(empId).subscribe(
(data) => {
var responseCode = JSON.parse(data).responseCode;
var responseMessage = JSON.parse(data).responseMessage
if (responseCode === 200) {
this.router.navigate(['../emp-details'], { relativeTo: this.route });
} else {
this.router.navigate(['../page-not-found'], { relativeTo: this.route });
}
}, error => {
this.router.navigate(['../page-not-found'], { relativeTo: this.route });
});
} else {
this.alertService.error("Error");
}
}
}
One way of handling page refreshes is to using hash routing. To implement this, write the following code in app.module.ts:
import { APP_BASE_HREF, LocationStrategy, HashLocationStrategy } from '#angular/common';
#NgModule({
......
providers: [
{ provide: APP_BASE_HREF, useValue: '', }
, { provide: LocationStrategy, useClass: HashLocationStrategy }
.....
]})
export class AppModule {
}
Please note that this will add # to your route.
I am using Angular2 and Auth0 for authentication. I can see the token is being stored into local storage. I am writing a function in my auth.service.ts file to check for a valid token then calling that function on init in the component. I have tried many different variations of this function but cannot get the app to validate correctly.
After I login it forwards me back to the home page even when I do login and retrieve a valid token.
My goal is to not allow access to this page without a valid token. But when there is a valid token allow access to this page.
This is what I have tried currently,
auth.service.ts
import { Injectable } from '#angular/core';
import { Router, CanActivate } from '#angular/router';
import { tokenNotExpired, JwtHelper } from 'angular2-jwt';
import { myConfig } from './auth.config';
// Avoid name not found warnings
declare var Auth0Lock: any;
var options = {
theme: {
logo: '../assets/img/logo.png',
primaryColor: '#779476'
},
auth: {
responseType: 'token',
redirect: true,
redirectUrl: "http://localhost:3000/dashboard",
},
languageDictionary: {
emailInputPlaceholder: "email#example.com",
title: "Login or SignUp"
},
};
#Injectable()
export class Auth {
// Configure Auth0
lock = new Auth0Lock(myConfig.clientID, myConfig.domain, options,{});
constructor(private router: Router ) {
// Add callback for lock `authenticated` event
this.lock.on('authenticated', (authResult) => {
localStorage.setItem('jwtToken', authResult.idToken);
});
}
public login() {
this.lock.show();
};
public authenticated() {
return tokenNotExpired();
};
public isAuthenticated(): boolean {
try {
var jwtHelper: JwtHelper = new JwtHelper();
var token = this.accessToken;
if (jwtHelper.isTokenExpired(token))
return false;
return true;
}
catch (err) {
return false;
}
}
private get accessToken(): string {
return localStorage.getItem('jwtToken');
}
public logout() {
localStorage.removeItem('jwtToken');
};
}
guard.service.ts
import { Injectable } from '#angular/core';
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '#angular/router';
import { Auth } from './auth.service';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class Guard implements CanActivate {
constructor(protected router: Router, protected auth: Auth ) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
if (state.url !== '/pages/home' && !this.auth.isAuthenticated()) {
this.auth.logout();
this.router.navigate(['/pages/home']);
return false;
}
return true;
}
}
app.routing.ts
import {Guard} from "./services/guard.service";
const appRoutes: Routes = [
{
path: '',
redirectTo: 'pages/home',
pathMatch: 'full',
},
{
path: '',
component: FullLayoutComponent,
canActivate: [Guard],
data: {
title: 'Home'
},
children: [
{
path: 'dashboard',
component: DashboardComponent,
data: {
title: 'Dashboard'
}
},
app.module.ts
import { Guard } from "./services/guard.service";
import { Auth } from "./services/auth.service";
providers: [
Guard,
Auth
],
right way to achieve this to use Guards
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '#angular/router';
import { AuthService } from './authService';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class AuthGuard implements CanActivate {
constructor(protected router: Router, protected authService: AuthService) {
}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
if (state.url !== '/login' && !this.authService.isAuthenticated()) {
this.authService.logOut();
this.router.navigate(['/login']);
return false;
}
return true;
}
}
And in you rotes set
path: 'admin',
component: AdminPages.AdminPagesComponent,
canActivate: [AuthGuard],
children: [
{
path: 'dashboard',
component: Dashboard,
data: {
menu: {
title: 'Dashboard',
icon: 'ion-android-home',
selected: false,
expanded: false,
order: 0
}
}
},
authservice
public isAuthenticated(): boolean {
try {
var jwtHelper: JwtHelper = new JwtHelper();
var token = this.accessToken;
if (jwtHelper.isTokenExpired(token))
return false;
return true;
}
catch (err) {
return false;
}
}
public logOut(): void {
localStorage.removeItem("access_token");
}
private get accessToken(): string {
return localStorage.getItem('access_token');
}
private saveJwt(jwt): void {
if (jwt) {
localStorage.setItem('access_token', jwt)
}
}
public login(loginModel: LoginModel): Promise<void> {
return new Promise<void>((resolve, reject) => {
var username = loginModel.email;
var password = loginModel.password;
var creds =
"username=" + username + "&password=" + password + "&client_id=" + this.clientId + "&grant_type=" + this.grant_type;
var headers = new Headers();
headers.append('Content-Type', 'application/x-www-form-urlencoded');
this.httpClient.post(this.identityServerUrl + "/connect/token", creds, { headers })
.toPromise()
.then(response => {
this.saveJwt(response.json().access_token);
resolve();
})
.catch(err => {
reject(err.json().error_description)
});
});
}
I have this code.
Route 'new' this child route for 'users'.
Route 'users' has resolver.
This work fine.
But after success create user.
I redirect to 'users', but new user not display in list, because
DataResolver not work after redirect from child route.
How Can i fix it?
//Roiting
export const ROUTES: Routes = [
{ path: 'dashboard',
component: Dashboard,
children: [
{ path: 'users',
component: Users,
resolve: {
users: DataResolver
},
children: [
{ path: 'new', component: NewUser }
]
},
]
}
];
//Resolver
#Injectable()
export class DataResolver implements Resolve<any> {
constructor(private userService: UserService) {}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> {
return this.userService.all(); // return users
}
}
//Component
export class NewUser {
errorMessage: string;
user: User;
constructor(public route: ActivatedRoute,
private userService: UserService,
private router: Router) {
this.user = new User();
}
onSubmit(): void {
this.userService
.createUser(this.user)
.subscribe(
user => {
this.router.navigate(['/dashboard', 'users']);
},
error => this.errorMessage = <any>error);
}
}
// USers component
export class Users implements OnInit {
users: User[];
constructor(public route: ActivatedRoute,
private router: Router) {
this.route.data.subscribe((data: any) => {
this.users = data.users;
});
}
}
Your 'Users' component should be like this, I assume you return User[] type from your web service;
export class Users {
users;
constructor(private _route: ActivatedRoute, ...){
_route.data.subscribe((wrapper: {res: any[] }) => {
this.users = <User[]>wrapper.res;
});
}
}
When your link is .../dashboard/user, it gets users via resolver and wrap it. I just call it as wrapper, you can give another name. Just know that after resolving, it wraps the response from your service.
Please let me know if you need more information.
Edit 1:
Here is how I use resolver, I hope it helps
//app.route
const appRoutes: Routes = [
{
path: '',
redirectTo: 'user',
pathMatch: 'full'
},
{
path: 'user',
loadChildren: '.../user.module#UserModule'
}
];
const rootRoutes: Routes = [
...appRoutes
];
export const appRouting = RouterModule.forRoot(rootRoutes, { useHash: true });
//user.route
const userRoutes: Routes = [
{
path: '',
component: UserComponent,
children: [
{
path: 'create',
component: UserCreateComponent
},
{
path: '',
component: UserListComponent,
resolve: {
res: UserResolver
}
}
]
}
];
export const userRouting = RouterModule.forChild(userRoutes);
//user.module
#NgModule({
imports: [
CommonModule,
ReactiveFormsModule,
userRouting,
FormsModule
],
declarations: [
UserComponent,
UserCreateComponent,
UserListComponent
],
providers: [
UserService,
UserResolver
]
})
export class UserModule { }
//user.resolver
export class UserResolver implements Resolve<any>{
constructor(private service: UserService) { }
public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> {
return this.service.get();//return all users
}
}