Stay at the same page after reloading Angular 4 - javascript

My Anuglar 4 app has multiple routes, in below example there are only two of them - Logging and List of items. So basically there are two routes: http://localhost:4200/#/ and http://localhost:4200/#/items. When I am at the http://localhost:4200/#/items and reload the page it automatically navigates me to http://localhost:4200/#/ which from my point of view is wrong behaviour. Is there a nice way how to prevent it? And when currently I am at the http://localhost:4200/#/items and reload the page to stay at the same page?
Below I post kind of configuration which might help you:
<base href="/">
imports: [
RouterModule.forRoot([
{path: '', component: LoginComponent},
{path: 'items', component: ItemListComponent, canActivate: [AuthGuard]},
])
],
providers: [
{provide: HTTP_INTERCEPTORS, useClass: CustomHttpInterceptor, multi: true},
{provide: LocationStrategy, useClass: HashLocationStrategy},
AuthenticationService,
AuthGuard,
],
AuthGuard:
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthenticationService) {
}
canActivate(next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
return this.authService.isLoggedIn;
}
}
#Injectable()
export class AuthenticationService extends DataService{
private _isLoggedIn: boolean;
constructor(http: HttpClient) {
super(http);
this._isLoggedIn = false;
this.url = '/api/auth';
}
get isLoggedIn(): boolean{
return this._isLoggedIn;
}
set isLoggedIn(value: boolean){
this._isLoggedIn = value;
}
And the server returns the token. For your information after reloading there is still valid token keeping in localStorage

This has nothing to do with authtoken, but all about your canActivate. When your app is initialized, means that app is destroyed and recreated. So when on a route that requires the logged in status, you have initially declared _isloggedIn as false. So the guard is working properly and redirecting you to your LoginComponent.
You would want to use localStorage or something else to persist your logged in status in case of page refresh.

add pathMatch: 'full'
{path: '', component: LoginComponent, pathMatch: 'full'},

Angular is a Single Page Application based framework. Page refresh will be done only when the app is initialized. You can make use of two-way binding, make a call to back-end and update the list of items received in response.
You can also try router.navigate(['/items']), it will work only when you are navigating from other components.

Related

Auth0 Angular - Cannot match any route

I have an angular application in which I'm trying to integrate with Auth0. I followed these two tutorials:
https://auth0.com/docs/quickstart/spa/angular2/01-login
https://auth0.com/docs/quickstart/spa/angular2/02-calling-an-api
Here is the setup of my project:
AuthService
Copy+Pasted from the first tutorial link
LoginController
export class LoginComponent implements OnInit {
constructor(private authService: AuthService) { }
ngOnInit(): void {
this.authService.login();
}
}
App-Routing Module
const routes: Routes = [
{ path: 'login', component: LoginComponent },
{ path: 'profile', component: ProfileComponent, resolve: { queues: ProfileResolver}, canActivate: [AuthGuard]}
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: InterceptorService,
multi: true
}
]
})
export class AppRoutingModule { }
I'm getting logged in successfully, however when auth0 does its callback, it navigates to http://localhost:4200/profile?code=[code]&state=[state], which then Angular throws "Cannot match any routes".
My questions are:
What are the code and state parameters used for?
What should I do to properly handle these so my site can route to /profile properly?
Thanks a bunch!
This is a nasty gotcha with auth0-spa-js that caused a big headache for me in production. The issue is describe in this thread. How I solved it was changing the last line of the handleAuthCallback function in the auth service from:
this.router.navigate([targetRoute])
to:
this.router.navigate([name-of-path-you're-redirecting-to]).

Route to different components when angular app starts

I have an angular app.
When a user logs in, I want to display different UI based on user's role.
A user can be just a customer or an admin
In my routing module I have the following
{path: '', redirectTo: 'admin-portal' , pathMatch: 'full'},
{path: 'admin-portal', component: AdminPortalComponent, canActivate: [AuthGuard]},
{path: 'customer-portal', component: CustomerPortalComponent, canActivate: [AuthGuard]}
I want to be able to fetch a variable from local storage and use it to decide where I redirect the user when the app loads. I am thinking of having a condition like {path: '', redirectTo: 1===1 ? 'admin-portal' : 'customer-portal' , pathMatch: 'full'}
You create another guard and put the conditions inside
#Injectable()
export class LoadGuard implements CanActivate {
constructor(private router: Router) {
}
canActivate(next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
if(1===1){
this.router.navigate(['admin-portal']);
return false;
}else{
this.router.navigate(['customer-portal']);
return false;
}
}
}
and then in the route
{path: '', pathMatch: 'full',component: sampleComponent, canActivate: [LoadGuard]}

Angular 2+ guard has strange behavior on page refresh

Using Asp Core on Angular 4 with the default template from asp core. The guard works, but on page refresh I get unwanted behavior. When refreshing a guarded route it briefly shows my login page when canActivate is true. Image below shows this behavior. Notice on refresh the screen flashes red (my login page).
Steps to reproduce issue:
Create project with dotnet new angular
Run the dotnet restore and npm install
Add file auth.guard.ts (code below)
Add file auth.service.ts (code below)
Add login component
Add service and guard to routes in app.modal.shared.ts (code below)
Add login button on home component
Run program and click login button
Navigate to the counter route
Press F5 to refresh, the login page will appear before showing counter route (should not show login page as canActivate should be true)
Please let me know if you wish to see any additional code or if you have any questions. I have been pulling my hair out the past two days trying all kinds of things with Observables, maps and subscriptions with no results. Any help will be greatly appreciated. Thanks in advance!
auth.guard.ts
import { Injectable } from '#angular/core';
import { Router, CanActivate } from '#angular/router'
import { AuthService } from './auth.service';
#Injectable()
export class AuthGuard implements CanActivate {
constructor(
private authService: AuthService,
private router: Router) {
}
canActivate() {
if (!this.authService.isLoggedIn()) {
this.router.navigate(['/login']);
return false;
}
return true;
}
}
auth.service.ts
import { Injectable, Inject, PLATFORM_ID } from '#angular/core';
import { Router } from '#angular/router';
import { Response, Headers, RequestOptions } from '#angular/http';
import { isPlatformBrowser, isPlatformServer } from '#angular/common';
import { Observable } from 'rxjs/Rx';
import { BehaviorSubject } from 'rxjs/Rx';
#Injectable()
export class AuthService {
private baseUrl: string = '';
private loggedIn = false;
uid: string | null;
constructor(
private router: Router,
#Inject(PLATFORM_ID) private platformId: Object
) {
if (isPlatformBrowser(this.platformId)) {
this.loggedIn = !!localStorage.getItem('auth_token');
}
}
login() {
this.loggedIn = true;
localStorage.setItem('auth_token', 'test');
}
logout() {
localStorage.removeItem('auth_token');
localStorage.removeItem('uid');
window.location.replace('/home'); // redirect as we want all var and obj to reset
}
isLoggedIn() {
return this.loggedIn;
}
}
app.module.shared.ts
...
RouterModule.forRoot([
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{ path: 'login', component: LoginComponent },
{ path: 'counter', component: CounterComponent, canActivate: [AuthGuard] },
{ path: 'fetch-data', component: FetchDataComponent, canActivate: [AuthGuard] },
{ path: '**', redirectTo: 'home' }
])
...
EDIT: Added gif of issue.
EDIT: Found out this is an issue with server-side prerendering. I am currently looking into how to setup a token storage service and pass this to the server.

Angular 2 delay rendering of router-outlet

I'm developing an Angular 2 app with multiple components that rely on some data that is loaded from the server via the http-service (it's data about the user and his roles).
Most of my route components throw errors within their ngOnInit() methods if this data is not already loaded. The data is loaded and stored within a service that is injected in all components.
Is there a way to delay the rendering of the current route within my root-component until the http call is finished?
Otherwise I would have to implement some kind of check and retry mechanism within the ngOnInit of all the route components, which would be very awkward.
I already tried to hide the router-outlet element until the call finished but this leads to an error saying "Cannot find primary outlet to load xxx"
There is an option to delay of router initial navigation when creating Router module:
RouterModule.forRoot([
// routes here
], {
initialNavigation: false // the propery to delay navigation
}),
Later the initial navigation can be triggered via Angular Router, like this:
this.router.initialNavigation();
I accomplished this with a CanActivate guard. The key to making it work is returning an Observable from the canActivate method. That way, you can delay as long as you need to.
import { CanActivate } from '#angular/router';
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Rx';
import { StateService } from '../../_services/state.service';
import { Subject } from 'rxjs/Rx';
import { Subscription } from 'rxjs/Subscription';
#Injectable()
export class LoadingGuard implements CanActivate {
constructor(private state: StateService) {}
public canActivate(): Observable<boolean> {
if (!this.state.loading$.getValue()) {
return Observable.of(true);
}
let subject = new Subject<boolean>();
let subscription = this.state.loading$.subscribe(value => {
if (!value) {
subject.next(true);
subject.complete();
subscription.unsubscribe();
}
});
return subject;
}
}
Above, StateService is a service that evaluates the current user and pre-caches some data for the rest of the app. It has a subject named loading$ that returns false when loading has completed.
All that's left is to declare the guard in the app module.
import { LoadingGuard } from './app/loading-guard/loading-guard';
// other imports omitted
#NgModule({
// other module properties omitted
providers: [LoadingGuard]
})
export class AppModule {}
Then declare the guard in your routing.
import { LoadingGuard } from './app/loading-guard/loading-guard';
// other imports omitted
export const rootRouterConfig: Routes = [
{ path: 'app', component: AppComponent,
canActivate: [LoadingGuard],
children: [
{ path: 'index', component: IndexComponent },
// child routes omitted
] },
{ path: 'sign-in', component: SignInComponent },
{ path: '**', redirectTo: 'sign-in' }
];
For reference, here's the documentation on CanActivate:
https://angular.io/docs/ts/latest/api/router/index/CanActivate-interface.html

redirectTo not working when using canActivate guard

The redirectTo property isn't working in my Angular 2 app. I have the following routes in my app.routing.ts:
const routes: Routes = [
{ path: '', redirectTo: '/page/1', pathMatch: 'full' },
{ path: 'page', loadChildren: 'app/modules/page/page.module#PageModule' }
]
export const routing = RouterModule.forRoot(routes);
Then, in my page.routing.ts, I have the following:
const pageRoutes: Routes = [
{ path: ':id', component: PageComponent, canActivate: [LoginGuard] }
];
export const pageRouting = RouterModule.forChild(pageRoutes);
Every time I access the home page it displays the LoginComponent for a second, then it disappears. However, it should redirect to the PageComponent.
Why isn't that happening? Why the LoginComponent is being loaded (even if it's only for a brief second) if the user is already logged in?
Here's my LoginGuard:
#Injectable()
export class LoginGuard implements CanActivate {
constructor(private af: AngularFire, private router: Router) {}
canActivate(): Observable<boolean> {
return this.af.auth.map(auth => {
if (auth === null) {
this.router.navigate(['/login']);
return false;
} else {
return true;
}
}).first();
}
}
EDIT: Temporarily, I changed the LoginComponent to redirect to the PageComponent if a user is logged in. I still wonder, though, why redirectTo isn't working.
I don't know exactly why this is happening, but I believe if you check the LoginGuard before the PageModule loads, it will work.
app.routing.ts
const routes: Routes = [
{ path: '', redirectTo: '/page/1', pathMatch: 'full' },
{
path: 'page',
// Call the guard before the module is loaded
canLoad: [ LoginGuard ]
loadChildren: 'app/modules/page/page.module#PageModule'
}
]
export const routing = RouterModule.forRoot(routes);
LoginGuard
#Injectable()
export class LoginGuard implements CanActivate, CanLoad {
constructor(private af: AngularFire, private router: Router) {}
// Add this method to validade the canLoad
canLoad(route: Route): Observable<boolean> {
return this.canActivate();
}
canActivate(): Observable<boolean> {
return this.af.auth.map(auth => {
if (auth === null) {
this.router.navigate(['/login']);
return false;
} else {
return true;
}
}).first();
}
}
You should NOT navigate inside a Guard
This is happening because you call this.router.navigate(['/login']); directly from your route guard which initializes a new route navigation on top of the one currently running.
You create a "race" between two navigations; my guess is the one to /login' wins because the other one has to lazy load the module (takes some time). But after the loading is done, it changes to that route afterwards, hence the "flashing" login popup.
You should NOT navigate inside a Guard, instead you should always return either a boolean (to allow navigate true/false) or a UrlTree if you want to redirect/change the route. The Guard returns the value and the router will then change navigation to the provided UrlTree for you inside the ongoing/triggered navigation and you won't get any race.
So change your method like this, and it will work correct.
canActivate(): Observable<boolean|UrlTree> {
return this.af.auth.map(auth => {
if (auth === null) {
return this.router.parseUrl('/login');
}
return true;
}).first();
}
You should see it like this, if you would call this.router.navigate in several route guards then the router wouldn't know where to navigate to, by returnin a UrlTree this problem is resolved. See also a related question/answer here

Categories

Resources