i'm struggling to find a solution to this problem. I googled for hours but couldn't solve.
I need to change the menu item in my navbar template showing login vs logout when the user logs in and out.
I have 2 services, the authService which let the user login/logout and the sessionService which provides some basic functions to check if the session is set or not.
I want that every time i logIn or logout the user (so i update the localStorage in sessionService) my components which use this get updated too.
I tried with .subscribe, .map but cannot make this work. The only thing that make this work is call this.loggedIn.next(this._sessionService.isSetUserSession()); in login and logout methods.
Please, what am i doing wrong?
navbar.component.html
<div class="dropdown-menu" aria-labelledby="dropdownAccount">
<ng-template *ngIf="(loggedIn$ | async); else elseDiv;">
<a class="nav-link" href="javascript:void(0)" (click)="logout()">LOGOUT</a>
</ng-template>
<ng-template #elseDiv>
<a class="nav-link" href="javascript:void(0)"(click)="login(...);">
LOGIN
</a>
</ng-template>
</div>
navbar.component.ts
import {Component, OnInit} from '#angular/core';
import {AuthService} from '../services/auth.service';
#Component({
moduleId: module.id,
selector: 'app-nav-bar',
templateUrl: 'navbar.component.html',
styleUrls: ['./navbar.component.css'],
})
export class NavbarComponent implements OnInit {
isNavbarCollapsed = true;
loggedIn$: any;
constructor(
private authService: AuthService) {
}
ngOnInit() {
this.loggedIn$ = this.authService.isLoggedIn;
}
}
auth.service.ts
import {Injectable} from '#angular/core';
import {Http, Response} from '#angular/http';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/do';
import {IUser} from '../entities/user';
import {SessionService} from './session.service';
import {Router} from '#angular/router';
import {Subject} from 'rxjs/Subject';
#Injectable()
export class AuthService {
private loggedIn: Subject<boolean> = new Subject<boolean>();
get isLoggedIn() {
return this.loggedIn.asObservable();
}
constructor(private _http: Http,
private _sessionService: SessionService,
private _router: Router) {
this.loggedIn.next(this._sessionService.isSetUserSession());
}
login(user: IUser) {
return this._http.get('assets/api/responseSuccess.json?email=' + user.email + '&password=' + user.password)
.map((responseLogin => {
const jsonResponse = responseLogin.json();
if (jsonResponse.response === 'success') {
const userResponse: IUser = jsonResponse.user;
this._sessionService.setUserSession(userResponse);
//this.loggedIn.next(this._sessionService.isSetUserSession()); ==> This makes this works but i don't want fo call this every time i change the session, i just want that the session syncs automatically
return true;
} else {
console.log("error loggin in");
return false;
}
}));
}
logout() {
this._sessionService.clearUserSession();
// this.loggedIn.next(this._sessionService.isSetUserSession()); =>> Same here
return this._router.navigate(['/']);
}
}
session.service.ts
import {Injectable} from '#angular/core';
import {IUser} from '../entities/user';
import {isNullOrUndefined} from 'util';
import {Subject} from "rxjs/Subject";
#Injectable()
export class SessionService {
isSetUserSession(): boolean {
return !!localStorage.getItem('user');
}
clearUserSession() {
localStorage.removeItem('user');
}
setUserSession(user: IUser) {
localStorage.setItem('user', JSON.stringify(user));
}
}
This can be achieved without using subject instead use Shared service
Have the variable in service as below,
export class DataService {
isLoggedIn: boolean = false;
}
In the component get and set it as below,
get data():string {
return this.dataService.isLoggedIn;
}
set data(value: string) {
this.dataService.isLoggedIn = value;
}
Update 1 : Alternatively it can updated via another service also as below,
#Injectable()
export class AuthService {
constructor(public dataService: DataService) { }
update(value){
console.log(value)
this.dataService.isLoggedIn = value;
}
}
LIVE DEMO
Please make following changes.
Use BehaviorSubject instead of Subject.
A BehaviorSubject holds one value. When it is subscribed it emits the value immediately. A Subject doesn't hold a value. When you set the subject variable, it doesn't reflect in the nav component.
auth.service.ts
private loggedIn = new BehaviorSubject<boolean>(false);
get isLoggedIn() {
return this.loggedIn.asObservable();
}
Set the loggedIn variable in login and logout functions.
login(user: IUser) {
return this._http.get('assets/api/responseSuccess.json?email=' + user.email + '&password=' + user.password)
.map((responseLogin => {
const jsonResponse = responseLogin.json();
if (jsonResponse.response === 'success') {
const userResponse: IUser = jsonResponse.user;
this._sessionService.setUserSession(userResponse);
this.loggedIn.next(this._sessionService.isSetUserSession());
return true;
} else {
console.log("error loggin in");
return false;
}
}));
}
logout() {
this._sessionService.clearUserSession();
this.loggedIn.next(this._sessionService.isSetUserSession());
return this._router.navigate(['/']);
}
in navbar.component.ts
replace loggedIn$: any; by
isLoggedIn$: Observable<boolean>;
An explanation as follows.
The BehaviorSubject keeps the latest value cached which is set when user log ins or logs out. So when an Observer subscribes to the isLoggedIn(), the cached value is going to be emitted right away, depending on user is signed in or not.
This code will work for sure, as I am using in my current project.
Related
I have an application using laravel as backend and Angular in frontend.
What I want to do is to get the user data and output it anywhere on my website. For example I would like to get the name for the user and output it on the homepage when the user is logged in.
I can successfully register and log in a user. I can get the user data from my login method in the authService in the console.log. But how can I use that user data and get the user data from my getUser method? Is there any way for me to send the data from login method to the getUser method?
authService
import { Injectable } from '#angular/core';
import { User } from '../shared/user';
import { Observable, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { environment } from 'src/environments/environment';
import {
HttpClient,
HttpHeaders,
HttpErrorResponse
} from '#angular/common/http';
import { Router } from '#angular/router';
#Injectable({
providedIn: 'root'
})
export class AuthService {
endpoint: string = `${environment.RECIPE_LIST_API}`;
headers = new HttpHeaders().set('Content-Type', 'application/json');
currentUser = {};
constructor(private http: HttpClient, public router: Router) {}
// Log in
login(user: User) {
return this.http
.post<any>(`${this.endpoint}/login`, user)
.subscribe((res: any) => {
console.log(res);
localStorage.setItem('access_token', res.token);
this.currentUser = res;
});
}
getUser() {
this.currentUser
// want to get my userdata here so that I can send it to any component
}
}
component that I want to send my data to
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { AuthService } from '../services/auth.service';
#Component({
selector: 'app-recipe-lists',
templateUrl: './recipe-lists.component.html',
styleUrls: ['./recipe-lists.component.css']
})
export class RecipeListsComponent implements OnInit {
currentUser: Object = {};
constructor(
public authService: AuthService,
private actRoute: ActivatedRoute
) {
this.authService.getUser();
}
ngOnInit(): void {}
}
user.ts
export class User {
email!: String;
password!: String;
}
I'm still new to Angular and learning Angular 8 currently.
I'm trying to create a simple API communication Service to load the data needed for display. I have a main Component with a sub-Component and both need to fetch data to load.
I've tried following several tutorials but my common issue is that the Component loading is happening before the API HTTP request is returned, leading to undefined data.
My current API Service uses HttpClient to communicate with the API
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Observable, throwError } from 'rxjs';
import { retry, catchError } from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class ApiService {
constructor(private http: HttpClient) {}
getUserFeed(id: number): Observable<Post[]> {
return this.http
.get<Post[]>(`${API_URL}/feed`)
.pipe(
retry(3),
catchError(this.handleError)
);
}
getProfile(id: number): Observable<Profile> {
return this.http
.get<Profile>(`${API_URL}/profile/${id}`)
.pipe(
retry(3),
catchError(this.handleError)
);
}
handleError(error: any) {
let errorMessage: string;
// Set error message
(error.error instanceof ErrorEvent) ?
errorMessage = error.error.message :
errorMessage = `Error Code: ${error.code}\nMessage: ${error.message}`;
console.log(errorMessage);
return throwError(errorMessage);
}
}
The API should be returning an array of Posts I've defined.
I call this in my component as
import { Component, OnInit } from '#angular/core';
import { UserService } from '../user/user.service';
import { ApiService } from '../api/api.service';
import { User } from '../user';
import { Post } from '../Post';
#Component({
selector: 'app-feed',
templateUrl: './feed.component.html',
styleUrls: ['./feed.component.css'],
})
export class FeedComponent implements OnInit {
posts: Post[] = [];
user: User;
post: Post;
constructor(private userService: UserService) {
this.user = this.userService.user;
}
public ngOnInit() {
this.userService.getUserFeed(this.user.id).subscribe((feed) => {
this.posts = feed;
console.log(this.posts);
});
}
}
My Component HTML should loop through these posts and pass the post to the sub-Components I have
<div *ngIf="posts.length">
<mat-list *ngFor="let post of posts">
<!-- Post Display -->
<app-post-display [post]=post></app-post-display>
<!-- Post Interaction Row -->
<app-post-interaction-bar [post]=post></app-post-interaction-bar>
<!-- Comment Preview -->
<app-comment-preview [post]=post></app-comment-preview>
<mat-divider></mat-divider>
</mat-list>
</div>
So far it seems to be picking up the posts for the main component as expected. The issue is in the sub-Component app-post-display which performs a similar action getting the post author from the post.authorId property.
I've declared an author and I've placed the code to fetch the author data in ngOnInit but I consistently get ERROR TypeError: Cannot read property 'id' of undefined in the console, no matter what I try it seems that the Component is trying to display before the author is fetched.
What do I need to adjust to have the author data fetched before the Component display is loaded
import { Component, Input, OnInit } from '#angular/core';
import { UserService } from '../user/user.service';
import { User } from '../user';
import { Post } from '../post';
import { Profile } from '../profile';
import { ApiService } from '../api/api.service';
#Component({
selector: 'app-post-display',
templateUrl: './post-display.component.html',
styleUrls: ['./post-display.component.css'],
})
export class PostDisplayComponent implements OnInit {
#Input() post: Post;
user: User;
author: Profile;
constructor(private userService: UserService, private backend: BackendService) {
this.user = this.userService.user;
}
ngOnInit() {
this.backend.getProfile(this.post.authorId).subscribe((profile) => {
this.author = profile;
console.log(this.author);
});
}
}
ngOnInit of the Child Component will run only once. Also, you can't expect it to get the post defined initially.
To fix it, you should move your call to ngOnChanges and check if post is defined first. Here, give this a try:
import { Component, Input, OnChanges } from '#angular/core';
import { UserService } from '../user/user.service';
import { User } from '../user';
import { Post } from '../post';
import { Profile } from '../profile';
import { ApiService } from '../api/api.service';
#Component({
selector: 'app-post-display',
templateUrl: './post-display.component.html',
styleUrls: ['./post-display.component.css'],
})
export class PostDisplayComponent implements OnChanges {
#Input() post: Post;
user: User;
author: Profile;
constructor(
private userService: UserService,
private backend: BackendService
) {
this.user = this.userService.user;
}
ngOnChanges() {
if (this.post) {
this.backend.getProfile(this.post.authorId).subscribe((profile) => {
this.author = profile;
console.log(this.author);
});
}
}
}
Alternatively, you can do that in your Parent Component:
<div *ngIf="posts">
<mat-list *ngFor="let post of posts">
<!-- Post Display -->
<app-post-display [post]=post></app-post-display>
<!-- Post Interaction Row -->
<app-post-interaction-bar [post]=post></app-post-interaction-bar>
<!-- Comment Preview -->
<app-comment-preview [post]=post></app-comment-preview>
<mat-divider></mat-divider>
</mat-list>
</div>
Just make sure you're not initializing the posts with an empty array initially though.
The best answer I found for my problem was actually using a resolver before the page is directed to (https://angular.io/api/router/Resolve).
This allowed the data to be loaded before the page was fully rendered, hence no errors.
I have two Component and one Service
Components:
1: LoginComponent
2: HeaderComponent (Shared)
Service:
1: authentication.service
In LoginComponent I use authentication.service to get authenticate and after successful authentication, I add User info into the Cookie and in the end, I automatically navigate to return-URL page, in the returned page I have a Component for the header that must show User info from the saved Cookie, BUT there is nothing in the cookie unless I refresh manually the page with F5 button.
My question is how can I access the cookie without refreshing the page?
Update
this is where I want to get the cookie:
import { Component, OnInit } from '#angular/core';
import { CookieHelper } from '../../_helpers/index';
#Component({
moduleId: module.id,
selector: 'app-header-ichart',
templateUrl: 'header.component.html',
styleUrls: ['header.component.css']
})
export class HeaderComponent implements OnInit {
currentUser = '';
isLogged = false;
constructor(private cookie: CookieHelper) { }
ngOnInit() {
this.isLogged = this.cookie.checkCookie('currentUser');
if (this.isLogged) {
this.currentUser = JSON.parse(this.cookie.getCookie('currentUser'));
}
}
}
Update 2
I used your suggestion technic to achieve my goal but need more help:
I Update my AuthenticationService to serve Observable Variable:
AuthenticationService:
import { Injectable } from '#angular/core';
import { Http, Headers, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/operator/map';
import { AppConfig } from '../app.config';
import { CookieHelper } from '../_helpers/index';
#Injectable()
export class AuthenticationService {
cookies: Object;
keys: Array<string>;
user$ = new Subject<any>();
constructor(private http: Http, private config: AppConfig, private cookie: CookieHelper) { }
login(username: string, password: string) {
const headers = new Headers();
headers.append('Content-Type', 'application/x-www-form-urlencoded');
let body = `grant_type=${'password'}&username=${username}&password=${password}`;
return this.http.post(
this.config.apiUrl + '/token',
body, { headers: headers })
.map((response: Response) => {
// login successful if there's a jwt token in the response
let user = response.json();
if (user && user.access_token) {
user['username'] = username;
// Observable Variable
this.user$.next(JSON.stringify(user));
// store user details and jwt token in cookie to keep user logged in between page refreshes
this.cookie.addCookie('currentUser', JSON.stringify(user));
}
});
}
getUser(): Observable<any> {
return this.user$.asObservable();
}
logout() {
// remove user from cookie to log user out
this.cookie.removeCookie('currentUser');
// Logout Subscribe
this.user$.next(null);
}
}
HeaderComponent:
export class HeaderComponent implements OnInit {
currentUser = '';
isLogged = false;
constructor(private cookie: CookieHelper, private auth: AuthenticationService) { }
ngOnInit() {
// Get the currentUser from Observable Variable
this.auth.getUser().subscribe(currentUser => { this.currentUser = currentUser; });
console.log(this.currentUser);
this.isLogged = this.cookie.checkCookie('currentUser');
if (this.isLogged) {
this.currentUser = JSON.parse(this.cookie.getCookie('currentUser'));
}
}
}
I suggest to use an Observable to achieve that. So your headerComponent will be notified after the login.
Update your authentication.service so that you have something like
import { Subject } from 'rxjs/Subject';
export class AuthenticationService {
user$: Subject<any>;
login() {
// login stuff
this.user$.next('userDetails');
}
logout() {
this.user$.next(null);
}
}
and then from whatever component you want you can check for the user$ observable.
In template with
{{ (user$ | async)?.username }}
or in code with
user$.subscribe(user => console.log(user.username))
I have a web application where there is a JWT token passed to the admin service. This JWT comes from the query URL as there is a redirect from another application. The constructor in the service checks for that URL and set the token value to it (if that params is there).
The problem I am facing is that the canActivateGuard fires too early. When this is called the observable to get the JWT in the service is not solved yet, so the JWT is always not there when the guard is being fired.
I have figure out that to make this work isLoggedIn() in the AdminService has to become an observable that listen to the changes in the URL, and the canActivate() in the guard has to subscribe to it, but can not make this works.
The below code is what I got so far
// Admin Service
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Rx';
import 'rxjs/Rx';
import {Router, ActivatedRoute, Params} from '#angular/router';
#Injectable()
export class AdminService {
token: string;
constructor(private activatedRoute: ActivatedRoute) {
activatedRoute.queryParams.subscribe(
(params) => {
console.log('queryParams', params);
if(localStorage.getItem('jwt')) {
this.token = localStorage.getItem('jwt');
}
else if(params['jwt']) {
localStorage.setItem('jwt', params['jwt']);
this.token = params['jwt'];
}
});
}
// Check that JWT is in local storage and valid
isLoggedin() {
return (localStorage.getItem('jwt') !== null && localStorage.getItem('jwt') !== 'undefined');
}
}
// Can Activate guard
// Note that this.authService.isLoggedIn() is called before the set JWT in the service is solved
#Injectable()
export class AuthGuard implements CanActivate {
constructor(
private authService: AdminService,
private router: Router
) {
}
canActivate() {
if (this.authService.isLoggedin()) {
console.log('all ok, proceed navigation to routed component')
return true;
}
else {
// start a new navigation to redirect to login page
this.router.navigate(['/unauthorized']);
return false;
}
}
}
using ActivatedRouteSnapshot and RouterStateSnapshot your problem will be get resolve, and you don't need to subscribe for JWT in your service.
Here is a sample of my code which i had used in my Angular2 application.
auth-guard.ts
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '#angular/router';
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { AuthCookie } from '../shared/services/auth-cookies-handler';
#Injectable()
export default class AuthGuard implements CanActivate {
constructor(private router: Router, private _authCookie: AuthCookie) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
if (this._authCookie.getAuth()) {
return true;
}
else {
this.router.navigate(['/login']);
return false;
}
}
}
Hope this will help you.
While making some edits to my Angular 2 app, I was able to get something working BEFORE I assumed it should be working. In other words, I'm a little perplexed as to why it's working in it's current configuration. Specifically, I have an authentication.service that handles my login auth. And I have both a login component and a chat component, both of which have a private instance of the authentication.service in their respective constructors. What I wanted to do was be able to pass the username from the login functionality down to the chat view, so I could display the logged-in user's username in the chatbox. I've got that working, but, strangely enough, I got it working by adding "this.authenticationService.username = this.model.username;" in the login.component, rather than in the authentication.service. So I'm perplexed as to how/why the chat.component even has access to that info. If I remove that one line of code from my login.component, the username will not be passed through to the chat.component. But I never actually import the login component into the chat component, so how is it working as is? Curious to see if someone can help me understand this. Here are the files in question. First, my authentication.service file:
import { ContextMenu } from './../ui/context-menu.component';
import { Router, RouterLinkActive } from '#angular/router';
import { Injectable } from '#angular/core';
import { Http, Headers, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
#Injectable()
export class AuthenticationService {
username;
constructor(private http: Http) {}
login(username: string, password: string) {
return this.http.post('/api/authenticate', JSON.stringify({ username: username, password: password }))
.map((response: Response) => {
// login successful if there's a jwt token in the response
let user = response.json();
if (user && user.token) {
// store user details and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('currentUser', JSON.stringify(user));
}
});
}
isAuthenticated() {
if (localStorage.getItem('currentUser')) {
//console.log('User successfully authenticated...');
return true;
} else {
// console.log('User is not authenticated...');
return false;
}
}
logout() {
// remove user from local storage to log user out
localStorage.removeItem('currentUser');
console.log('User successfully logged out');
}
}
And here's my login.component file:
import { UserService } from './../../data/user.service';
import { AuthenticationService } from './../../data/authentication.service';
import { AlertService } from './../../data/alert.service';
import { Component, OnInit, Input } from '#angular/core';
import { Router } from '#angular/router';
#Component({
selector: 'app-login',
templateUrl: 'app/views/login/login.component.html',
styleUrls: ['app/views/login/login.component.css']
})
export class LoginComponent implements OnInit {
model: any = {};
loading = false;
username;
password;
constructor(
private router: Router,
private authenticationService: AuthenticationService,
private alertService: AlertService,
private userService: UserService) { }
ngOnInit() {
// reset login status
this.authenticationService.logout();
}
login() {
this.loading = true;
this.authenticationService.login(this.model.username, this.model.password)
.subscribe(
data => {
this.router.navigate(['/']);
console.log('User logged in as: ' + this.model.username);
},
error => {
this.alertService.error(error);
this.loading = false;
});
this.authenticationService.username = this.model.username;
}
}
Here's my chat.component file:
import { AuthenticationService } from './../../data/authentication.service';
import { Router, ActivatedRoute } from '#angular/router';
import { ChatService } from './chat.service';
import { Component, OnInit, OnDestroy } from '#angular/core';
import { TabPage } from '../../ui/tab-navigation/tab-page';
#Component({
templateUrl: './chat.component.html',
styleUrls: ['./chat.component.less'],
})
export class ChatComponent extends TabPage implements OnInit, OnDestroy {
messages = [];
users = [];
routes;
connection;
userbase;
route;
message;
user;
constructor(private chatService:ChatService,
router: Router,
route: ActivatedRoute,
private authenticationService: AuthenticationService) {
super(router, route);
this._title = 'Chat Room';
this.addEventListener('paramsChange', function(params) {
this._title = 'Chat Room';
}.bind(this));
}
sendMessage() {
this.chatService.sendMessage(this.message);
this.message = '';
}
sendUser() {
this.chatService.sendUser(this.user);
this.user = '';
}
trackUser() {
this.chatService.trackUser(this.route);
console.log('A user just navigated to ' + this.route);
}
// For when user clicks "enter/return" to send message
eventHandler(event: KeyboardEvent): void {
if (event.key === 'Enter') {
this.chatService.sendMessage(this.message);
this.message = '';
}
}
ngOnInit() {
this.connection = this.chatService.getMessages().subscribe(message => {
this.messages.push(message);
});
this.userbase = this.chatService.getUsers().subscribe(user => {
this.users.push(user);
});
this.routes = this.chatService.getRoutes().subscribe(route => {
this.routes.push(route);
});
}
ngOnDestroy() {
this.connection.unsubscribe();
this.userbase.unsubscribe();
}
public getTabId(params): string {
return 'Chat Room';
}
}
Lastly, my chat.component.html file looks like this (this is where I'm actually using the string interpolation to display the username in the chat):
<div class="centered-display" align="center">
<h3>User: {{authenticationService.username}}</h3>
<div *ngFor="let message of messages" class="message">
{{authenticationService.username}}: {{message.text}}
</div>
<input class="form-group" [(ngModel)]="message" (keypress)="eventHandler($event)">
<div class="spacing">
<button class="submit-btn" md-button (click)="sendMessage()">SEND</button>
</div>
</div>
Can someone help me understand why this is working as is? The one line that makes this work is the last line in my login.component login function: "this.authenticationService.username = this.model.username;". But, again, my chat component doesn't have access to my login component (as as I understand). So why is this working?
Your chat component and login component may not directly relate to each other, but they do both have access to the authenticationService. Specifically, they both have a reference to the same instance of that service injected.
This means that when you do this.authenticationService.username = this.model.username in your login component, you are setting the username property on the same authenticationService object that you are accessing with your interpolation in the chat component view ({{authenticationService.username}}).