I have a bootstrap navbar, on the right side of navigation bar, i have some links like login,logout, register
I put it on my app.component.html.ts
<div class="navbar-collapse collapse">
// Here i check if user is authenticated, display : Hello abc#gmail.com
<ul *ngIf="user" class="nav navbar-nav navbar-right">
//code in here
</ul>
// If user is not authenticated, display Login - Register
<ul *ngIf="!user" class="nav navbar-nav navbar-right">
<li><a routerLink="/register" id="registerLink">Register</a></li>
<li><a routerLink="/login" id="loginLink">Log in</a></li>
</ul>
In login.component.ts i call my Authen.Service.ts to get token that is store on localStorage
import { UrlConstants } from './core/common/url.constants';
import { LoggedInUser } from './core/domain/loggedin.user';
import { SystemConstants } from './core/common/system.constants';
#Component({
selector: 'app-login',
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
public user: any;
private isLoggedIn = false;
loginUser(valid: boolean) {
this.loading = true;
if (valid) {
const userData = {
username: this.form.controls.username.value,
password: this.form.controls.password.value
}
this._authenService.login(userData.username, userData.password).subscribe(data => {
this.user = JSON.parse(localStorage.getItem(SystemConstants.CURRENT_USER));
// If success redirect to Home view
this._router.navigate([UrlConstants.HOME]);
}, error => {
this.loading = false;
});
}
}
ngOnInit() {
}
}
Here is my Authen.Service.ts
import { Injectable } from '#angular/core';
import { Http, Headers, RequestOptions, Response } from '#angular/http';
import 'rxjs/add/operator/map';
import { SystemConstants } from '../common/system.constants';
import { LoggedInUser } from '../domain/loggedin.user';
#Injectable()
export class AuthenService {
constructor(private _http: Http) {
}
login(username: string, password: string) {
let body = "userName=" + encodeURIComponent(username) +
"&password=" + encodeURIComponent(password) +
"&grant_type=password";
let headers = new Headers();
headers.append("Content-Type", "application/x-www-form-urlencoded");
let options = new RequestOptions({ headers: headers });
return this._http.post(SystemConstants.BASE_API + '/api/oauth/token', body, options).map((response: Response) => {
let user: LoggedInUser = response.json();
if (user && user.access_token) {
localStorage.removeItem(SystemConstants.CURRENT_USER);
localStorage.setItem(SystemConstants.CURRENT_USER, JSON.stringify(user));
}
});
}
logout() {
localStorage.removeItem(SystemConstants.CURRENT_USER);
}
isUserAuthenticated(): boolean {
let user = localStorage.getItem(SystemConstants.CURRENT_USER);
if (user != null) {
return true;
}
else
return false;
}
Here is my app.component.ts
export class AppComponent implements OnInit {
// the user object got from localStore
ngOnInit() {
this.user = JSON.parse(localStorage.getItem(SystemConstants.CURRENT_USER));
console.log(this.user);
}
}
The problem i got is i cant update the navbar to change in right state (It still work, i have the token but i have to refresh the whole page to update the nav bar)
How can i update the navigation bar in angular way? Thanks
As i understood your problem it is: How to hide "login" link located on main component after user signed himself in
I can think about solution like following:
Inside your AuthService you can add public boolean member "isLoggedIn":
#Injectable()
export class AuthService {
isLoggedIn = false;
}
You can share this service between components
Inside login component you can set isLoggedIn to true after successful login
login(){
this.auth.isLoggedIn = true
}
In your app.component you can subscribe to NavigationEnd event of the router :
export class AppComponent {
constructor(
private router: Router, private auth:AuthService){}
ngOnInit() {
this.router.events.subscribe(event => {
if (event.constructor.name === "NavigationEnd") {
this.isLoggedin = this.auth.isLoggedIn;
}
})
}
}
And then, in app component template you can show "login" menu with *ngIf="!isLoggedin"
here is plunker
hope it helps...
Related
I am building a site that allows one to search for a beer and it returns data about that beer. The user clicks the search button, and it runs the http request I have setup on a service. All displays fine. But what I am trying to do is move my search form from the displaying component, to be inside the navbar. How do I link the search form on the navbar to the viewing component?
here is the home.component where the search form currently sits(clicking search runs the "searchBeer" function below passing the beer name being searched for:
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
constructor(private beerSearchService:BeerSearchService) { }
beerName:string;
beers:{};
selectedBeer: {};
searchBeer(beerName){
this.beers=null;
this.beerSearchService.searchBeer(beerName)
.subscribe(data => console.log(this.beers=data));
this.selectedBeer=null;
}
onSelect(beer): void {
this.selectedBeer = beer;
console.log(beer);
}
ngOnInit() {
}
}
EDIT... Had wrong service before.....
beer-search.service:
import { Injectable } from '#angular/core';
import {HttpClient} from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class BeerSearchService{
constructor(private http: HttpClient) { }
searchBeer(name){
let data= {
query: `{beerSearch(query:"`+name+`"){items{id, name, style {description}, description, overallScore,
imageUrl, abv, brewer {name, facebook, web}}}}`,
variables:"{}",
operationName:null
}
return this.http.post('https://api.r8.beer/v1/api/graphql/', data, {
headers:{
'x-api-key': '<API-KEY>'}
});
}
}
If I move the search bar to the navbar component, how do I call this searchBeer function?
You store the results of API call to BehaviorSubject in the service, from navbar call the method to get beers from API and in component instead of subscribing to API result, subscribe to Observable (from BehaviorSubject of BeerS - your data):
BeerSearchService
export class BeerSearchService {
private _beers = new BehaviorSubject<Beer[]>(null);
constructor(private http: HttpClient) { }
searchBeer(beerSearch?: string) {
// do something with beerSearch parameter
let data = {
query: ` {topBeers{items{id, name, style {description}, description,
overallScore, imageUrl, abv, brewer {name, facebook, web}}}}`,
variables:"{}",
operationName:null
};
this.http.post('https://api.r8.beer/v1/api/graphql/', data, {
headers: {'x-api-key': '<api-key>'}
}).subscribe(data => {
this._beers.next(data);
});
}
get beers$() {
return this._beers.asObservable();
}
}
navbar.ts
export class NavbarComponent implements OnInit {
constructor(private beerSearchService: BeerSearchService) {}
searchBeer(beerName) {
this.beerSearchService.searchBeer(beerName);
}
}
Component.ts
export class HomeComponent implements OnDestroy {
beers:{};
sub: Subscription;
constructor(private beerSearchService: BeerSearchService) {
this.sub = this.beerSearchService.beers$.subscribe(beers => {
this.beers = beers;
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
}
There are many examples around the web on this subject but none of them helped me. This is the scenario: I've got 2 components and a service. The two components aren't parent/children but are 2 independent components. One of them has a list of names, the other should load a table when one of the names is clicked. This is my home.html with both components
<div class="material-docs-app">
<div class="docs-primary-header">
<h1>Yep!</h1>
</div>
<div fxLayout="row" fxLayout.xs="column" class="component-layout-body">
<app-heroes-sidenav></app-heroes-sidenav>
<app-heroes-table #heroesTable fxFlex="1 2 calc(15em + 20px)" style="width: 100%"></app-heroes-table>
</div>
</div>
Heroes sidenav component:
<div *ngIf="loadingData == true">
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</div>
<nav *ngIf="loadingData == false">
<p *ngFor="let item of heroesNames.results let i = index" [attr.data-index]="i">
<button mat-button (click)="getHero(i)">
{{item.name}}
</button>
</p>
</nav>
On click getHero() is called correctly. This is the sidenav component ts:
import { Component, OnInit, Input } from '#angular/core';
import {SwCharactersServiceService} from '../sw-characters-service.service';
import {HeroesTableComponent} from '../heroes-table/heroes-table.component';
#Component({
selector: 'app-heroes-sidenav',
templateUrl: './heroes-sidenav.component.html',
styleUrls: ['./heroes-sidenav.component.css']
})
export class HeroesSidenavComponent implements OnInit {
heroesNames: any;
heroData:any;
loadingData = true;
#Input() heroesTable: HeroesTableComponent;
constructor(private _swService: SwCharactersServiceService) { }
ngOnInit() {
this.getHeroes();
}
getHeroes() {
this._swService.getCharacters().then(result => {
this.loadingData = false;
this.heroesNames = result;
});
}
getHero(index) {
this._swService.getHero(index);
}
}
and this is the service:
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import 'rxjs/add/operator/map'
import {Observable} from 'rxjs/Observable';
#Injectable({
providedIn: 'root'
})
export class SwCharactersServiceService {
param:any;
constructor(private http: HttpClient) { }
getCharacters(): Promise<any[]> {
return this.http.get<any[]>("https://swapi.co/api/people/")
.toPromise()
.then(result => result)
.catch(this.handleError);
}
getHero(index): Observable<any>{
console.log(index);
let headers = new HttpHeaders();
headers.append('Content-Type', 'application/json');
return this.http.get("https://swapi.co/api/people/" + index, {
headers: headers
}).map(res => res );
}
private handleError(error: any): Promise<any> {
console.error('An error occurred', error); // for demo purposes only
return Promise.reject(error.message || error);
}
}
I can correctly see the console.log(index) but the request doesn't work. There is no request initiated in chrome console network tab.
This is the component with the table:
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
import { Subscription } from 'rxjs/Subscription';
import {SwCharactersServiceService} from '../sw-characters-service.service';
#Component({
selector: 'app-heroes-table',
templateUrl: './heroes-table.component.html',
styleUrls: ['./heroes-table.component.css']
})
export class HeroesTableComponent implements OnInit {
loadingData = true;
heroData :any;
subscription: Subscription;
constructor(private _swService: SwCharactersServiceService) {
this.subscription = this._swService.getHero(1).subscribe(result => { this.heroData = result; });
console.log(this.heroData);
}
ngOnInit() {
}
ngOnDestroy() {
// unsubscribe to ensure no memory leaks
this.subscription.unsubscribe();
}
}
There are 2 problems now:
1) As you can see I wrote this._swService.getHero(1) without passing a dynamic param. How does it work? How can I pass the correct index?
2) The service doesn't fire and I haven't got any result.
Is there any other way to do that?
Thanks.
you can use BehaviourSubject to pass the index value and send the query request as the list is cliked
in the service
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
public index: BehaviorSubject<number> = new BehaviorSubject<number>(null);
in the sidenav component
getHero(index) {
this._swService.index.next(index);
}
in the hero table component
ngAfterViewInit(){
this._swService.index.subscribe(index=>{
if(index){
this._swService.getHero(index).subscribe(result => { this.heroData = result; });
}
})
}
You missed to subscribe to _swService.getHero(). If not subscribed to a method which returns an Observable, then it wont be invoked.
getHero(index) {
this._swService.getHero(index).subscribe(
(resp) => {
// manipulate your response here
console.log(resp);
},
(err) => {}
);
}
In the template component AppComponent, depending on the value, the variable this.loggedInService.isLoggedIn switches between the logIn() and logout() methods, which in the application component AppComponent are subscribed to these methods in the service LoggedinService and depending on the method, change the value of the variable to true or false.
Also in the Guard's method checkLogin (url: string) I return true or false depending on the value of the variable this.loggedInService.isLoggedIn
Everything works, but when I reset the page, I need to keep the value of the input or output button. I try to do this in the login() and logout() methods in the service, but after reloading the page, the changes are still not saved. Help solve this problem so that the changes remain after the page reboot.
template of AppComponent:
<li class="nav-item">
<a class="btn btn-outline-success"
[class.btn-outline-success]="!this.loggedInService.isLoggedIn"
[class.btn-outline-danger]="this.loggedInService.isLoggedIn"
(click)="this.loggedInService.isLoggedIn ? logout() : logIn()">
{{this.loggedInService.isLoggedIn ? 'Exit' : 'Enter'}}
</a>
</li>
code of AppComponent:
export class AppComponent implements OnInit {
constructor(private loggedInService: LoggedinService,
private router: Router) {
}
ngOnInit() {}
logIn(): void {
this.loggedInService.login();
if (this.loggedInService.isLoggedIn) {
let redirect = this.loggedInService.redirectUrl ? this.loggedInService.redirectUrl :
'/gallery';
this.router.navigate([redirect]);
}
}
logout(): void {
this.loggedInService.logout();
this.router.navigate(['/']);
}
}
LoggedinService:
export class LoggedinService implements OnInit {
isLoggedIn: boolean = false;
redirectUrl: string;
constructor() {
}
ngOnInit() {
this.CheckAuthentication();
}
enter code here
CheckAuthentication(): boolean {
if (localStorage.getItem('login') === 'true') {
return this.isLoggedIn = true;
} else if (localStorage.getItem('login') === 'false') {
return this.isLoggedIn = false;
}
}
login() {
localStorage.setItem('login', 'true')
}
logout() {
localStorage.removeItem('login');
localStorage.setItem('login', 'false')
}
AuthGuard:
export class AuthGuard implements CanActivate {
constructor(private loggedInService: LoggedinService) {
}
canActivate(next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean{
let url: string = state.url;
return this.checkLogin(url);
}
checkLogin(url: string): boolean {
if (this.loggedInService.isLoggedIn) {
return true;
} else {
this.loggedInService.redirectUrl = url;
return false;
}
}
}
Change is isLoggedIn to be get method base on localStorage item
export class LoggedinService implements OnInit {
redirectUrl: string;
constructor() {}
get isLoggedIn(): boolean {
return localStorage.getItem('login') ? true : false;
}
login(){
localStorage.setItem('login','true')
}
logout(){
localStorage.removeItem('login')
}
}
app.component
export class AppComponent {
constructor(private loggedInService: LoggedinService,
private router: Router) {
}
logIn(): void {
this.loggedInService.login(); // set the state as login
let redirect = this.loggedInService.redirectUrl ? this.loggedInService.redirectUrl :
'/gallery';
this.router.navigate([redirect]);
}
logout(): void {
this.loggedInService.logout(); //// set the state as logout
this.router.navigate(['/']);
}
}
stackblitz demo
I have a doubt with your code.
In LoggedInService onInit why are you calling login() and logout() directly?
this.CheckAuthentication();
this.login();
this.logout();
Doing that is adding and deleting from your localStorage. Also, you can check data in your local storage by typing localStorage in browser console.I think you should comment or remove onInit method
I have a issue when I try to do the login. Step by step:
- The user clicks the login button;
- Auth0 appears to do the login;
- The user profile is saved in localStorage;
- When login is successful the internal page is loaded and the user can use the system. Every page need the profile data (in localStorage).
The problem
It's impossible to enter in the system in the first login. IT's EMPTY!!! even if the login was successful! I set a flow to logout the system when the localStorage is empty, so it's redirect to the login page.
BUT when you try again, everything goes fine! I have no idea why.
Follows the code.
AuthService
import { Injectable } from '#angular/core';
import { Router } from '#angular/router';
import { ApiService } from './api.service';
import { Profile } from '../models/Profile';
import auth0 from 'auth0-js';
import 'rxjs/add/operator/filter';
#Injectable()
export class AuthService {
auth0 = new auth0.WebAuth({
// Credentials
});
constructor(protected router: Router, protected api: ApiService) {}
public login(): void {
this.auth0.authorize();
}
public logout(): void {
localStorage.removeItem('profile');
localStorage.removeItem('id_token');
localStorage.removeItem('expires_at');
localStorage.removeItem('access_token');
this.router.navigate(['/']);
}
private setSession(authResult): void {
const profile = authResult.idTokenPayload;
const expiresAt = JSON.stringify((authResult.expiresIn * 1000) + new Date().getTime());
localStorage.setItem('expires_at', expiresAt);
localStorage.setItem('id_token', authResult.idToken);
localStorage.setItem('access_token', authResult.accessToken);
this.api.getUsuario(profile.name)
.subscribe(res => {
profile.nivel = res.nivel;
profile.idClube = res.idClube;
localStorage.setItem('profile', JSON.stringify(profile));
});
}
public handleAuthentication(): void {
this.auth0.parseHash((err, authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
window.location.hash = '';
this.setSession(authResult);
this.router.navigate(['/calendario_']);
} else if (err) {
this.router.navigate(['/login']);
console.error(err);
}
});
}
public isAuthenticated(): boolean {
const expiresAt = JSON.parse(localStorage.getItem('expires_at'));
return new Date().getTime() < expiresAt;
}
}
LoginComponent
import { Component } from '#angular/core';
import { AuthService } from '../../services/auth.service';
#Component({
moduleId: module.id,
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent {
constructor(protected auth: AuthService) { }
}
Component (After successful login)
import { Component, OnInit } from '#angular/core';
import { Profile } from '../../models/Profile';
import { Calendario } from '../../models/Calendario';
import { ApiService } from '../../services/api.service';
import { AuthService } from '../../services/auth.service';
#Component({
moduleId: module.id,
selector: 'app-calendarioproximo',
templateUrl: './calendarioProximo.component.html'
})
export class CalendarioProximoComponent implements OnInit {
protected title: string;
protected dataAtual: any;
protected loading = true;
protected profile: Profile;
protected model: Calendario[] = [];
protected calendario: Calendario[] = [];
constructor(protected api: ApiService, protected auth: AuthService) { }
getCalendario() {
this.api.getCalendario(this.profile.idClube)
.subscribe(res => {
this.loading = true;
this.model = res;
this.api.getData()
.subscribe(data => {
this.dataAtual = data.dataCompleta;
for (let cont = 0; cont < this.model.length && this.calendario.length < 5; cont++) {
if (this.model[cont].data >= this.dataAtual) {
this.calendario[this.calendario.length] = this.model[cont];
}
}
this.loading = false;
}, err => console.error(err));
});
}
ngOnInit() {
this.title = 'Calendário Próximo';
this.profile = new Profile();
// HERE!
JSON.parse(localStorage['profile']) ? this.profile = JSON.parse(localStorage['profile']) : this.auth.logout();
this.getCalendario();
window.scrollTo(0, 0);
}
}
Your setSession(authResult) function does not save the profile at the same time when it saves expires_at, id_token, and access_token.
The assignment will happen eventually, as a part of the handler in
getUsuario(profile.name).subscribe(() => {...})`.
Moving this.router.navigate(['/calendario_']); from handleAuthentication into setSession may resolve your issue:
private setSession(authResult): void {
const profile = authResult.idTokenPayload;
const expiresAt = JSON.stringify((authResult.expiresIn * 1000) + new Date().getTime());
localStorage.setItem('expires_at', expiresAt);
localStorage.setItem('id_token', authResult.idToken);
localStorage.setItem('access_token', authResult.accessToken);
this.api.getUsuario(profile.name)
.subscribe(res => {
profile.nivel = res.nivel;
profile.idClube = res.idClube;
localStorage.setItem('profile', JSON.stringify(profile));
this.router.navigate(['/calendario_']); // Navigate after you `profile` has been set for sure
});
}
public handleAuthentication(): void {
this.auth0.parseHash((err, authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
window.location.hash = '';
this.setSession(authResult);
// this.router.navigate(['/calendario_']); // This is too early...
} else if (err) {
this.router.navigate(['/login']);
console.error(err);
}
});
}
I am building a profile page and trying to get the authenticated user data to display there. My API call works with their id, and it works on the front end if I manually enter the id into the url.
But when I try to navigate to the profile from the navbar, I receive a
400 Bad Request for URL: http://localhost:3000/users/undefined
What I can assume right now is that it's an asynchrony issue. My profile page calls the user data, but that user data isn't available in my nav component. And it seems as though I need to pass in my id param into my profile [routerLink] if I want to navigate correctly. Since my user data isn't available in my nav component, it has nothing to pass.
Is there a better approach to this? Should I be using an event emitter?
Fairly new to Angular - help much appreciated!
Profile Component
import { Component, OnInit, Input } from '#angular/core';
import { AuthService } from '.././services/auth.service';
import { UserService } from '.././services/user.service'
import { Router, ActivatedRoute } from '#angular/router';
#Component({
selector: 'app-profile',
templateUrl: './profile.component.html',
styleUrls: ['./profile.component.css'],
providers: [UserService]
})
export class ProfileComponent implements OnInit {
currentUser;
isAuth: boolean;
constructor(
private session: AuthService,
private router: Router,
private userService: UserService,
private route: ActivatedRoute
) {
this.session.isAuth
.subscribe((isAuth: boolean) => {
// user will be false if logged out
// or user object if logged in.
this.isAuth = isAuth;
});
if (this.session.token) {
this.isAuth = true;
console.log(this.session);
} else {
this.isAuth = false;
}
}
ngOnInit() {
this.route.params.subscribe(params => {
this.getUserDetails(params['id']);
});
}
getUserDetails(id) {
this.userService.get(id)
.subscribe((user) => {
this.currentUser = user;
console.log(this.currentUser);
});
}
}
Nav Template
Where I'm navigating to my profile page.
<nav class="navbar navbar-default">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">bnb</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right">
<li *ngIf="!isAuth"><a [routerLink]="['login']">Login</a></li>
<li *ngIf="isAuth"><a [routerLink]="['profile']"><span class="glyphicon glyphicon-user" aria-hidden="true"></span> Profile</a></li>
<li *ngIf="isAuth"><a (click)="logout()">Logout</a></li>
<li *ngIf="!isAuth"><a [routerLink]="['signup']">Signup</a></li>
</ul>
</div>
</div>
</nav>
Nav Component
import { Component, OnInit, Input } from '#angular/core';
import { AuthService } from '.././services/auth.service';
import { UserService } from '.././services/user.service';
import { Router, ActivatedRoute } from '#angular/router';
#Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.css']
})
export class NavbarComponent implements OnInit {
isAuth: boolean;
currentUser: any;
constructor(
private session: AuthService,
private userService: UserService,
private router: Router,
private route: ActivatedRoute
) {
this.currentUser = JSON.parse(localStorage.getItem("User"))
console.log("USER",this.currentUser) //Currently returns Null
console.log(this.session)
this.session.isAuth
.subscribe((isAuth: boolean) => {
// user will be false if logged out
// or user object if logged in.
this.isAuth = isAuth;
});
if (this.session.token) {
this.isAuth = true;
} else {
this.isAuth = false;
}
}
ngOnInit() {
}
logout() {
this.session.logout();
}
}
Router
import { Routes } from '#angular/router';
import { LoginComponent } from '../login/login.component';
import { SignupComponent } from '../signup/signup.component';
import { HomeComponent } from '../home/home.component';
import { RentalListingsComponent } from '../rental-listings/rental-listings.component';
import { SingleRentalComponent } from '../rental-listings/single-rental/single-rental.component';
import { ProfileComponent } from '../profile/profile.component'
import { AuthService } from '../services/auth.service';
export const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'login', component: LoginComponent },
{ path: 'signup', component: SignupComponent },
{ path: 'rentals', component: RentalListingsComponent },
{ path: 'listing', component: SingleRentalComponent },
{ path: 'profile/:id', component: ProfileComponent, canActivate: [AuthService] } <--profile path. I know I have to match my url paths, but don't know how to do this from the navbar.
// { path: 'home', component: HomeComponent, canActivate: [AuthService] },
{ path: '**', redirectTo: '' }
];
Thanks for providing the detail. Somewhere you need to subscribe to 'after login' or 'authentication' event, grab the user profile JSON, and save it to localstorage so you can use it anywhere you want. If you can't hook in or subscribe to one of these, then do it imperatively somewhere convenient in your code. Find out what call you can make to fetch the entire user JSON and save it as follows...
Check out my AuthService init() below. First line is this.authProvider.on('authenticated', this.onAuth);. Whatever authentication service API you are using should provide a way for you to specify a callback (providing the login token) whenever someone logs in. The onAuth callback function saves the token in localstorage and then fetchProfile(...){...} makes another call to the authentication service API to get the whole JSON user profile using the token just received this.user.getProfile(idToken, this.onProfile);. For example, I use Auth0 in projects, and my call to Auth0 API looks like this.lock.getProfile(idToken, this.onProfile); but I replaced that with an example of what your call might look like this.user.getProfile(idToken, this.onProfile); So use whatever your API uses replace in fetchProfile. Then the onProfile callback saves the entire JSON profile in a single key in local storage using this.localStorage.set('profile', profile); Then you can get it any time by calling this.localStorage.get('profile').
Do not provide UserService through the lazy-loaded ProfileComponent. That creates a separate branch on the dependency injection tree you might not want. See https://angular-2-training-book.rangle.io/handout/modules/shared-modules-di.html Import the UserService in a top-level module like AppModule or SharedModule and provide it there. No need to export it if it's in AppModule.
app.module.ts
...
#NgModule({
imports: [
...
UserService,
...
]
providers: [
...
UserService,
...
]
Handle Auth related stuff in Auth, not Profile. Profile seems visual/implementation-specific (e.g. it has a template). Here is a code snippet example.
auth.service.ts
#Injectable()
export class Auth {
userProfile: UserProfile;
constructor(
...
private localStorage: LocalStorageService,
private router: Router,
private user: UserService,
private authProvider: ...
...
) {
}
init() {
this.authProvider.on('authenticated', this.onAuth);
// Set userProfile attribute if already saved profile
this.userProfile = this.localStorage.get('profile');
setTimeout(() => { // let AppComponent listener initialize
this.localStorage.set('profile', this.userProfile);
}, 0);
}
}
onAuth = (authResult: AuthResult) => {
this.localStorage.set('id_token', authResult.idToken);
this.fetchProfile(authResult.idToken);
}
// Save current route for redirect url
login() {
this.localStorage.set('redirect_url', this.router.url);
this.authProvider.show({initialScreen: 'login'});
};
// Check if user is logged in.
authenticated() {
// Check if unexpired token.
// Searches for item in localStorage with key == 'id_token'
return this.authProvider.tokenNotExpired();
};
logout() {
this.router.navigateByUrl('');
this.userProfile = undefined; // do before localstorage
this.localStorage.remove('id_token');
this.localStorage.remove('profile');
};
fetchProfile(idToken: string) {
this.user.getProfile(idToken, this.onProfile);
}
/**
* On profile event callback.
* Save profile to LocalStorage.
* Redirect to url if present in LocalStorage.
*/
onProfile = (error: any, profile: UserProfile) => {
if (error) {
console.log(error);
return;
}
this.userProfile = profile;
this.localStorage.set('profile', profile);
// Redirect if there is a saved url to do so.
const redirectUrl: string = this.localStorage.get('redirect_url');
if (redirectUrl !== undefined) {
this.router.navigateByUrl(redirectUrl);
this.localStorage.remove('redirect_url');
}
}
Interact with localStorage through a LocalStorageService and subscribe to changes as follows.
localstorage.service.ts
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs/Subject';
#Injectable()
export class LocalStorageService {
[key:string]: any;
/**
* define your localstorage variables here as observables
*/
id_token$ = new Subject();
redirect_url$ = new Subject();
profile$ = new Subject();
customer$ = new Subject();
set(key: string, value: any) {
this[key + '$'].next(value); // this will make sure to tell every subscriber about the change.
localStorage.setItem(key, JSON.stringify(value));
}
get(key: string) {
const value = localStorage.getItem(key);
return value && JSON.parse(value);
}
remove(key: string) {
this[key + '$'].next(undefined);
localStorage.removeItem(key);
}
}
Don't do so much in constructor. Example:
app.component.ts
export class AppComponent implements OnDestroy, OnInit {
webRobot: boolean = false;
private profileSub: any;
private customerSub: any;
private subscriptionSub: any;
constructor(
private analyticsService: AnalyticsService,
private auth: Auth,
private localStorage: LocalStorageService,
) {
}
ngOnInit(): void {
this.init();
}
init() {
this.auth.init(this.webRobot);
this.analytics.init(this.webRobot);
if (!this.webRobot) {
// subscribe to authed profile changes
this.profileSub =
this.localStorage.profile$.subscribe(this.onProfile);
// Subscribe to changes to Stripe customer
this.customerSub =
this.localStorage.customer$.subscribe(this.onCustomer);
}
// always delete active subscribes on destroy
ngOnDestroy() {
this.profileSub.unsubscribe();
this.customerSub.unsubscribe();
}
onProfile = (profile: UserProfile) => {
// ...do stuff
}
onCustomer= (profile: Customer) => {
// ...do stuff
}
In your profile route configuration, it is expecting the id query param
{ path: 'profile/:id', component: ProfileComponent, canActivate: [AuthService] }
<--profile path.
I know I have to match my url paths,
but don't know how to do this from the navbar.
but your navbar link is not passing the id value
<li *ngIf="isAuth"><a [routerLink]="['profile']"><span class="glyphic
you need to do something like this in your navbar
<li *ngIf="isAuth"><a [routerLink]="['profile/user.id']">