Resolving data in angular 2 - javascript

Introduction:
I have a search-box that let's you select a tv show, which then logs the title of the show you clicked, as well as the id for that show. I define this in my:
landingpage.component
<li (click)="selectShow(list.title)" [routerLink]="['/details', list.id]"
*ngFor="let list of shows"> {{list.show}} </li>
When a li is clicked, it sends the list.id as a parameter to my /details component. selectShow just logs the name (list.title) of the show that was clicked.
My problem:
I cannot seem to figure out how to resolve my list.title value, so that it appears in my /details route in this.route.snapshot.data['title']
My code:
app.routing.module.ts
import { NgModule } from '#angular/core';
import { RouterModule, Routes } from '#angular/router';
import { TitleResolver } from './services/title.service';
const routes: Routes = [
{ path: '', component: LandingPage },
{
path: 'details/:id', component: ResultPage,
resolve: {
title: TitleResolver //hopefully contains 'title' data here?
}
}
}
];
resultpage.component.ts
title; //variable we will assign to title
constructor(private route: ActivatedRoute) {}
this.route.data
.subscribe((data: { title: Title}) => {
console.log(title);
});
title.service.ts
// this gives us the name of the clicked show
class ShowService {
fetchTitle(title) {
return title;
}
}
#Injectable()
export class TitleResolver {
constructor(private showservice: ShowService) { }
resolve(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<any> | Promise<any> | any {
return this.showservice.fetchTitle(route.params.title);
}
}
My question
What are the intermediate step(s) that I need to do in order to send the selected list.title value from my landingpage.component data to my app.routing.module.ts, so that I can receive it in my resultpage.component ?

Related

Javascript/Jest - Unable to verify location.path() in testing... whenever routerLink functionality clearly works

I have a component called LocationDropdown, which is a clickable h1 title which then drops down into a list of locations (based on a mode input (derived from a parent class) it will either show historic or live locations).
So, if the user clicks the title, they'll be presented with options like:
QueensRoad, KingsRoad, JacksRoad and finally Browse All Locations...
The former 3, when clicked, will trigger an event which navigates (via router) the user to another page.
The latter option navigates the user to another page via a routerLink {{ galleryPath }} option.
I've been able to correctly write the test for the former's behaviour, but I'm really struggling with testing that routerLink changes the Location.path().
On jest it is failing because it says:
expect(received).toEqual(expected) // deep equality
Expected: "/gallery/live"
Received: ""
I have written similar tests which used this same behaviour, and there was never an issue with the location actually being ""... so I'm a bit lost. I have spent days trying to figure this small issue out.
I really hope someone can help me. Here's the code:
location-dropdown.component.ts
import { Component, Input, OnInit } from '#angular/core';
import { configurations } from '#bfsdf/digital-fabric-data-objects';
import { ActivatedRoute, Router } from '#angular/router';
import { ConfigService } from 'src/app/services/config-service';
export enum LocationMode {
LOCATION = 'location',
HISTORY = 'history'
}
#Component({
selector: 'app-location-dropdown',
templateUrl: './location-dropdown.component.html',
styleUrls: ['./location-dropdown.component.css']
})
export class LocationDropdownComponent implements OnInit {
public location: configurations.LocationConfig;
public locations: configurations.LocationConfig[];
#Input()
public mode = LocationMode.LOCATION;
public galleryPath: string;
showLocationDropdown = false;
constructor(public configService: ConfigService, public router: Router, private route: ActivatedRoute) {
console.log('LocationDropdownComponent Constructor');
}
ngOnInit(): void {
console.log('LocationDropdownComponent ngOnInIt');
console.log('LocationDropdownComponent mode is :', this.mode);
const locationId = this.route.snapshot.paramMap.get('locationId');
console.log('LocationDropDownComponent current locationId is', locationId);
if (this.mode == LocationMode.HISTORY) {
this.galleryPath = 'gallery/history';
this.locations = this.configService.getConfig().locations.filter((loc) => loc.hasTrafficHistory);
} else {
this.galleryPath = 'gallery/live';
this.locations = this.configService.getConfig().locations;
}
this.location = this.configService.getLocationById(locationId);
}
// as a result of clicking a dropdown entry:
changeLocationRoute(selectedLocation: configurations.LocationConfig): void {
this.location = selectedLocation;
console.log('LocationDropDownComponent changeLocationRoute locationId: ', this.location.id);
this.router
.navigate([`${this.mode}/`, selectedLocation.id])
.then((flag) => {
console.log('LocationDropDownComponent Navigate completed flag:%o', flag);
})
.catch((err) => {
console.error('LocationDropDownComponent Navigate failed with error', err);
});
this.showLocationDropdown = false;
}
toggleTitleDropdown(): void {
this.showLocationDropdown = !this.showLocationDropdown;
}
public trackByLocationId(index: number, item: configurations.LocationConfig): string {
return item.id;
}
}
location-dropdown.component.html
The code within the very last < li > section is the code in question.
All of this is completely functionable as expected on browser.
It's worth noting here, I think, that during testing I had previously added a (click) method to that anchor tag. The method simply logged HELLO. In the browser, when clicked, it would log "TEST" successfully. But in the Jest run logs, there is no TEST .. am I somehow selecting it incorrectly in Jest..?
<!-- clickable title -->
<div class="title-container" *ngIf="mode === 'history'">
<h1
i18n-aria-label="Clickable page title revealing camera selection menu##pageTitleDashboard"
i18n-role="Clickable page title role as button revealing camera selection menu##pageTitleDashboardBtnRole"
class="title clickable"
role="button"
tabindex="0"
aria-label="Select a different camera location"
[class.opened]="showLocationDropdown"
(click)="toggleTitleDropdown()"
(keyup.enter)="toggleTitleDropdown()"
>
<p i18n="TrafficHistory page title##trafficHistoryPageTitle">Historical Traffic Analysis | {{ location.displayName }}</p>
</h1>
</div>
<div class="title-container" *ngIf="mode !== 'history'">
<h1
i18n-aria-label="Clickable page title revealing camera selection menu##pageTitleDashboard"
i18n-role="Clickable page title role as button revealing camera selection menu##pageTitleDashboardBtnRole"
class="title clickable"
role="button"
tabindex="0"
aria-label="Select a different camera location"
[class.opened]="showLocationDropdown"
(click)="toggleTitleDropdown()"
(keyup.enter)="toggleTitleDropdown()"
>
{{ location.displayName }}
</h1>
</div>
<div class="title-dropdown-container fade-in" [class.hidden]="!showLocationDropdown" (keyup.esc)="toggleTitleDropdown()">
<button
i18n-aria-label="Close button label for camera selection menu##cameraSelectMenuCloseBtnLabel"
i18n-title="Close button for camera selection menu##cameraSelectMenuCloseBtn"
class="icon-btn title-dropdown-close"
title="Close location selection"
aria-label="Close location selection"
(click)="toggleTitleDropdown()"
(keyup.enter)="toggleTitleDropdown()"
>
<i class="fa fa-times" aria-hidden="true"></i>
</button>
<h2 i18n="Subheading for camera location selection menu##cameraSelectMenu">Select a location to view</h2>
<ul class="title-dropdown">
<li
*ngFor="let loc of locations ? locations.slice(0, 10) : []; trackBy: trackByLocationId"
[id]="loc.id"
[class.selected]="loc.id === location.id"
>
<a (click)="changeLocationRoute(loc)" (keyup.enter)="changeLocationRoute(loc)" tabindex="0">
{{ loc.displayName }}
</a>
</li>
<li id="browseAllLocations">
<a
i18n="Page navigation to camera gallery screen##browseAllCameras"
i18n-routerLink="Page link to camera gallery screen##browseAllCamerasLink"
routerLink="/{{ galleryPath }}"
tabindex="0"
>Browse all locations...</a
>
</li>
</ul>
</div>
location-dropdown.component.spec.ts
The test.only section is the part which is failing.
import { Component, DebugElement } from '#angular/core';
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '#angular/core/testing';
import { FormsModule } from '#angular/forms';
import { By } from '#angular/platform-browser';
import { ActivatedRoute, Router } from '#angular/router';
import { RouterTestingModule } from '#angular/router/testing';
import { ConfigService } from '../../services/config-service';
import { LocationDropdownComponent, LocationMode } from './location-dropdown.component';
import { Location } from '#angular/common';
let component: LocationDropdownComponent;
let fixture: ComponentFixture<LocationDropdownComponent>;
let nativeElement: Element;
let location: Location;
let router: Router;
#Component({ template: '' })
class DummyComponent {}
describe('LocationDropdownComponent - location mode', () => {
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule.withRoutes([
{
path: 'location/:locationId',
component: DummyComponent
},
{
path: 'gallery',
component: DummyComponent,
children: [
{ path: 'live', component: DummyComponent },
{ path: 'history', component: DummyComponent }
]
}
]),
FormsModule
],
declarations: [LocationDropdownComponent, DummyComponent],
providers: [
ConfigService,
{
provide: ActivatedRoute,
// without this, location.displayName in LocationDropdown is null because it cannot be derived from snapshot
useValue: {
snapshot: { paramMap: { get: jest.fn().mockReturnValue('QueensRoad') } }
}
}
]
})
.compileComponents()
.then((res) => {
console.log('Compile components result', res);
})
.catch((err: Error) => {
console.error('Compile components error', err);
return err;
});
})
);
beforeEach(() => {
fixture = TestBed.createComponent(LocationDropdownComponent);
component = fixture.componentInstance;
component.mode = LocationMode.LOCATION;
nativeElement = fixture.nativeElement as Element;
router = TestBed.inject(Router);
location = TestBed.inject(Location);
fixture.detectChanges();
});
test('should create the app', () => {
expect(component).toBeTruthy();
});
test('should render title in a h1 tag', () => {
expect((fixture.debugElement.query(By.css('h1')).nativeElement as HTMLElement).textContent).toContain(
"Queen's Road - Sydenham Road"
);
});
test('should render location list - non-historic', () => {
const locationDropDownTitle: DebugElement = fixture.debugElement.query(By.css('h1.title.clickable'));
expect(locationDropDownTitle).toBeDefined();
expect((locationDropDownTitle.nativeElement as HTMLElement).textContent).toContain("Queen's Road - Sydenham Road");
expect(nativeElement.querySelector('.title-dropdown-container').classList).toContain('hidden');
locationDropDownTitle.triggerEventHandler('click', null);
fixture.detectChanges();
expect(nativeElement.querySelector('.title-dropdown-container').classList).not.toContain('hidden');
const locationDropDownList = fixture.debugElement.queryAll(By.css('.title-dropdown li'));
expect(locationDropDownList).toBeDefined();
expect(locationDropDownList.length).toBe(9);
expect((locationDropDownList[0].nativeElement as HTMLElement).textContent).toContain("Queen's Road - Sydenham Road");
expect((locationDropDownList[8].nativeElement as HTMLElement).textContent).toContain('Browse all locations...');
});
test('should change location list', () => {
const mockListener = jest.spyOn(router, 'navigate').mockImplementation(
() =>
new Promise<boolean>((resolve) => {
resolve(true);
})
);
const locationDropdownEntry: DebugElement = fixture.debugElement.query(By.css('#DuncrueStreet a'));
expect(locationDropdownEntry).toBeDefined();
locationDropdownEntry.triggerEventHandler('click', null);
fixture.detectChanges();
expect(mockListener).toHaveBeenCalledWith(['location/', 'DuncrueStreet']);
const locationDropDownTitle: DebugElement = fixture.debugElement.query(By.css('h1.title.clickable'));
expect(locationDropDownTitle).toBeDefined();
expect((locationDropDownTitle.nativeElement as HTMLElement).textContent).toContain('Duncrue Street');
// dropdown hidden post-navigation
expect(nativeElement.querySelector('.title-dropdown-container').classList).toContain('hidden');
});
test.only('should change with browse all locations... selected', fakeAsync(() => {
expect(location.path()).toEqual('');
expect(component.galleryPath).toBe('gallery/live');
const locationDropdownEntryAllLocations = fixture.debugElement.query(By.css('#browseAllLocations a'));
console.log('locationdropdownentry is ', locationDropdownEntryAllLocations);
fixture.detectChanges();
expect((locationDropdownEntryAllLocations.nativeElement as HTMLElement).textContent).toContain('Browse all locations...');
locationDropdownEntryAllLocations.triggerEventHandler('click', null);
tick();
fixture.detectChanges();
expect(location.path()).toEqual('/gallery/live'); // ("")
}));
});
I really would appreciate any help I can get. I'm rather new to this, but this seems very strange, I assumed I'd filled in all the blanks really.

Get the Query parameter from the URL in to Component

I am trying to understand how I can get the Query parameter from the URl in to my Component. Below is what I tried, I set the route in the app-routing.module.ts like
{
path: 'ProjectShipment/userId/231',
component: ProjectShipmentComponent,
data: { title: 'Project Shipment' },
}
And with in the project-shipment.component.ts I tried like
import {Router, ActivatedRoute, Params} from '#angular/router';
export class ProjectShipmentComponent implements OnInit {
constructor( private activatedRoute: ActivatedRoute) { }
ngOnInit() {
debugger;
this.activatedRoute.queryParams.subscribe(params => {
const userId = params['userId'];
console.log(userId);
});}}
When I debug it I get undefined in the logs
What am I missing here
You need to change your route to
{
path: 'ProjectShipment/:userId',
component: ProjectShipmentComponent,
data: { title: 'Project Shipment' },
}
Then when you call it like yourhost/projectshipment/231 in your component
this.activatedRoute.params.subscribe(params => {
const userId = params['userId'];
console.log(userId);
})
to get queryparams you code is right but your route should
{
path: 'ProjectShipment',
component: ProjectShipmentComponent,
data: { title: 'Project Shipment' },
}
and url should be yourhost/projectshipment?userid=231

Binding header to text based on router url (Angular4)

What's the best way to get header text to change based on the page? I'm currently working from a single header that changes color based on router.url (see previous question here) and I want to change the page header text dynamically.
I feel like the logic should also be similar should I also want to add in images based on the pages as well.
At first I thought to iterate through an array of titles, but that ended up just printing all the titles on all the pages. Then I thought to create a function that check would check the link and return the appropriate title, but I'm not sure how to insert it...
app.component.html
<!-- <div class="set-margin page-title"><h1 *ngFor="let pageTitle of pageTitles">{{ pageTitle.title }}</h1></div> -->
<div class="set-margin page-title">
<!-- <h1 *ngFor="let pageTitle of pageTitles">{{ pageTitle.title }}</h1> -->
<h1>{{ getHeaderText() }}</h1>
</div>
app.component.ts
export class AppComponent {
activatedRoute = '';
title: string;
pageTitles = [
{ 'title': 'About'},
{ 'title': 'Resources'},
{ 'title': 'News' },
{ 'title': 'Contact' }
];
activateRoute(activatedRoute: string) {
this.activatedRoute = activatedRoute;
}
getHeaderText() {
if (this.activatedRoute.includes('about')) {
this.title = 'About';
return this.title;
} else if (this.activatedRoute.includes('resources')) {
this.title = 'Resources';
return this.title;
} else if (this.activatedRoute.includes('newsroom')) {
this.title = 'News';
return this.title;
} else {
this.title = 'Contact';
return this.title;
}
}
constructor(private router: Router) {}
}
You dont need the title or pageTitles. Create a getter property for getting the active page title.
Change your component html to:
<div class="set-margin page-title">
<h1>{{ headerTitle }}</h1>
</div>
... and in your component class:
export class AppComponent {
activatedRoute = '';
activateRoute(activatedRoute: string) {
this.activatedRoute = activatedRoute;
}
get headerTitle {
if (this.activatedRoute.includes('about')) {
return 'About';
} else if (this.activatedRoute.includes('resources')) {
return 'Resources';
} else if (this.activatedRoute.includes('newsroom')) {
return 'News';
} else {
return 'Contact';
}
}
constructor(private router: Router) {}
}
You could pass these titles as data via the router, which would look something like this
{ path: 'about', component: AboutComponent, data: {title: 'About Us'}},
{ path: 'resources', component: AboutComponent, data: {title: 'Amazing Resources'}},
{ path: 'newsroom', component: AboutComponent, data: {title: 'News'}},
app.component.ts
first, be sure to add these imports at the top
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute, Data } from '#angular/router';
then you need to fetch this data from the router. Your AppComponent might look like this
export class AppComponent implements OnInit {
pageTitle = 'default page title';
constructor(private route: ActivatedRoute) {}
ngOnInit() {
this.route.data.subscribe(
(data: Data) => {
this.pageTitle = data['title'];
}
);
}
}
then display the data on app.component.html
app.component.html
{{ pageTitle }}

Access activated route data from some other component

We have component ( ka-cockpit-panel) which is not mapped to any route and inserted manually in the some other component as shown below :
..
...
<section class="ka-cockpit-panel cockpit-1 pull-left">
<ka-cockpit-panel></ka-cockpit-panel>
</section>
...
..
In this component i want to access the current active route data.
For eg : if we have some other component ( say ka-integration-component ) and it has some route data associated with it ( as shown below ), whenever we navigate to this component ( via url or by clicking some routerlink ) , we want to access integration component route data in our ka-cockpit-component.
..
...
{
path: "",
component: IntegrationComponent,
data : {
cockpit1 : false,
cockpit2 : true,
kpi : true
},
}
..
...
Basically, we want to configure our ka-cockpit-component for certain components in our app which is mapped to some route so that we can hide / show or change its appearance.
Cockpit component code :
import { Component, OnInit } from '#angular/core';
import { Router,Event,NavigationEnd,ActivatedRoute } from '#angular/router';
#Component({
selector: 'ka-cockpit-panel',
templateUrl: './cockpit-panel.component.html',
styleUrls : ['./cockpit-panel.component.scss']
})
export class CockpitPanelComponent implements OnInit {
constructor(private router:Router,private activatedRoute : ActivatedRoute) {
this.router.events.subscribe( (event:Event) => {
if(event instanceof NavigationEnd) {
console.log("Cockpit Panel Component : Route successfully changed - ",event,this.router,this.activatedRoute);
// THIS IS WHAT WE WANT - get Integration component route data here whenever i navigate to integration component!!!
}
});
}
ngOnInit() { }
}
You have to use Resolve Guard for the thing you want to implement.
// MyDataResolver Service
import { Injectable } from '#angular/core';
import { Router, Resolve, RouterStateSnapshot,
ActivatedRouteSnapshot } from '#angular/router';
#Injectable()
export class MyDataResolver implements Resolve<any> {
constructor(private cs: CrisisService, private router: Router) {}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<any> {
let pathFromRoot = route.pathFromRoot;
// you can compare pathFromRoot with your route to return different data
return Promise.resolve({
cockpit1 : false,
cockpit2 : true,
kpi : true
});
}
}
// Routing configuration
.
.
{
path: "",
component: IntegrationComponent,
resolve : {
data: MyDataResolver
},
}
.
.
// Component
export class CockpitPanelComponent implements OnInit {
someBinding : string = "testing Value";
constructor(private router:Router,private activatedRoute : ActivatedRoute) {
this.activatedRoute.data.subscribe( (res) => {
// here you will get your data from resolve guard.
console.log(res);
});
}
ngOnInit() { }
}

Angular 2 AuthGuard Service with redirect?

I have an application that I am building that implements CanActivate on the dashboard route. It works fine except on page reload, I check a flag in the user service to see if a user is logged in or not. By default this flag is false which kicks the user out to login. Also on page reload I am trying to fetch user data with a token in localStorage, if fetch is successful, I want them to be able to stay on the dashboard. The problem is that I am seeing a glimpse of login and having to manually redirect them to the dashboard. Is there any way to fix this to where the authGuard doesn't do anything until after it checks the API? Code is here: https://github.com/judsonmusic/tfl
dashboard:
import { Component, ViewChild } from '#angular/core';
import { LoginComponent } from "../login.component";
import { UserService } from "../user.service";
import { SimpleChartComponent } from "../charts/simpleChart.component";
import { AppleChartComponent } from "../charts/appleChart.component";
import { BarChartComponent } from "../charts/barChart.component";
import { DonutChartComponent } from "../charts/donutChart.component";
import { AlertComponent } from 'ng2-bootstrap/ng2-bootstrap';
import { ModalDemoComponent } from "../modals/modalDemoComponent";
import { NgInitHelperComponent } from "../helpers/nginit.helper.component";
import { ModalDirective } from "ng2-bootstrap/ng2-bootstrap";
import { MODAL_DIRECTIVES, BS_VIEW_PROVIDERS } from 'ng2-bootstrap/ng2-bootstrap';
#Component({
selector: 'dashboard',
templateUrl: '/app/components/dashboard/dashboard.component.html',
providers: [UserService, BS_VIEW_PROVIDERS],
directives: [SimpleChartComponent, AppleChartComponent, BarChartComponent, DonutChartComponent, AlertComponent, ModalDemoComponent, NgInitHelperComponent, ModalDirective]
})
export class DashboardComponent {
public areas: any;
constructor() {
this.areas = [
"Spiritual",
"Habits",
"Relationships",
"Emotional",
"Eating Habits",
"Relaxation",
"Exercise",
"Medical",
"Financial",
"Play",
"Work/ Life Balance",
"Home Environment",
"Intellectual Well-being",
"Self Image",
"Work Satisfaction"
]
}
}
Routes:
import { Routes, RouterModule } from '#angular/router';
import { AboutComponent } from './components/about.component';
import { PageNotFoundComponent } from "./components/pageNotFound.component";
import { HomeComponent } from "./components/home.component";
import { DashboardComponent } from "./components/dashboard/dashboard.component";
import { SurveyComponent } from "./components/survey/survey.component";
import { ResourcesComponent } from "./components/resources.component";
import { LogoutComponent } from "./components/logout.component";
import { AuthGuard } from "./components/auth-guard.service";
import { loginRoutes, authProviders } from './login.routing';
import { LoginComponent } from "./components/login.component";
const appRoutes:Routes = [
{ path: '', component: HomeComponent },
{ path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] },
{ path: 'logout', component: LogoutComponent },
{ path: 'resources', component: ResourcesComponent },
{ path: 'survey', component: SurveyComponent },
{ path: 'about', component: AboutComponent },
{ path: 'login', component: LoginComponent },
{ path: '**', component: PageNotFoundComponent }
];
export const appRoutingProviders: any[] = [
authProviders
];
export const routing = RouterModule.forRoot(appRoutes);
login route:
import { Routes } from '#angular/router';
import { AuthGuard } from './components/auth-guard.service';
import { AuthService } from './components/auth.service';
import { LoginComponent } from './components/login.component';
export const loginRoutes: Routes = [
{ path: 'login', component: LoginComponent }
];
export const authProviders = [
AuthGuard,
AuthService
];
In AuthGuard do the following:
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate() {
if (/*user is logged in*/) {
this.router.navigate(['/dashboard']);
return true;
} else {
this.router.navigate(['/Login']);
}
return false;
}
}
Here's how to correctly handle redirects in a guard by using an UrlTree
#Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivateChild {
constructor(
private authService: AuthService,
private logger: NGXLogger,
private router: Router
) {}
canActivateChild(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> {
return this.authService.isLoggedIn().pipe(
map(isLoggedIn => {
if (!isLoggedIn) {
return this.router.parseUrl('/login');
}
return true;
})
);
}
}
Big thanks to Angular In Depth for the explanation!
You can now return a UrlTree from an AuthGuard, or a boolean true / false.
Kind of amazed nobody has mentioned this yet! Sorry no example right now, but the idea is pretty simple.
I actually changed my service to this and it works:
import { Injectable } from '#angular/core';
import { CanActivate, Router,
ActivatedRouteSnapshot,
RouterStateSnapshot } from '#angular/router';
import { AuthService } from './auth.service';
import {UserService} from "./user.service";
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router, private userService: UserService) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (this.authService.isLoggedIn){
console.log('ATUH GUARD SAYD THEY ARE ALREADY LOGGED IN!');
return true;
}else {
this.userService.getUser().subscribe((user) => {
console.log('AUTH GUARD GETTING USER', user);
if (user._id) {
this.authService.isLoggedIn = true;
// Store the attempted URL for redirecting
this.authService.redirectUrl = state.url;
this.router.navigate(['/dashboard']);
return true;
}else{
console.log('Validation Failed.');
localStorage.clear();
this.router.navigate(['/login']);
return false;
}
}, (error) => {
console.log('There was an error.');
this.router.navigate(['/login']);
return false
});
}
}
}
I solved it like this and used it in my AuthGuard
isLoggedIn(): Observable<boolean> {
return this.afAuth.authState
.pipe(
take(1),
map(user => {
return !!user;
},
() => {
return false;
}
),
tap(loggedIn => {
if (!loggedIn) {
this.router.navigate(['/']);
}
}
));
}
This is what I did for canActivate
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
// ensure the user is properly logged in and the url he's requesting is within his right
if (this.authSvc.getRole().trim().length > 0 && this.authSvc.getToken().trim().length > 0
&& state.url.includes(this.authSvc.getRole().trim())) {
let url: string;
// base on user's role send to different url to check
if (this.authSvc.getRole().trim() === 'client') {
url = ClientAccessRequestUrl;
} else {
url = AdminAccessRequestUrl;
}
return this.http.post<AccessRequestResponse>(url, {
token: this.authSvc.getToken(),
}).pipe(map(response => {
console.log('response is:', response);
// check successful then let go
if (response.reply === true) {
return true;
// unless go to visitor site
} else {
return this.router.createUrlTree(['/visitor']);
}
}));
} else {
return this.router.createUrlTree(['/visitor']);
}
}
The best way to do redirects after authentication is structuring the logic as shown below;
in the AuthGuard,
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree
{
// keep the requsted url for redirect after login
let url: string = state.url;
// call the authentication function
var authenticated = this.http.isAuthenticated();
var subject = new Subject<boolean>();
authenticated.subscribe(
(res) => {
//if not authenticated redirect to the login route with the initial route attached as an query param 'returnUrl'
if(!res.successState) {
this.router.navigate(['/login'], {queryParams: {returnUrl: url}});
}else{
// the user is authenticated just go to the requested route
subject.next(res.successState);
}
});
return subject.asObservable();
}
in the login route
loginAction(data: any){
// if the auth works fine the go the route requested before the inconviniences :)
if(data.successState){
// get the query params to see if there was a route requested earlier or they just typed in the login route directly
this.route.queryParams.subscribe(params => {
// console.log(params); //returnUrl
if(params.returnUrl){
// pearse the url to get the path and query params
let urlTree: UrlTree = this.router.parseUrl(params.returnUrl);
let thePathArray : any[] = [];
// populate it with urlTree.root.children.primary.segments[i].path;
for(let i = 0; i < urlTree.root.children.primary.segments.length; i++){
thePathArray.push(urlTree.root.children.primary.segments[i].path);
}
let the_params = urlTree.queryParams;
this.router.navigate(thePathArray, {queryParams: the_params});
}else{
this.router.navigate(['']);
}
});
}else{
// tell them about it and let them try again or reset the password
}
}
That should work perfectly. it will even preserve query params for the initial request.
THANKS

Categories

Resources