10

Right, so i have a header component (navbar) which contains the following template:

<ng-template [ngIf] = "authService.isAuthenticated()">
  <li>
    <a routerLink="Landing" class="navbar-brand" (click)="Logout()"><span class="xvrfont">Logout</span><i class="fa fa-sign-in" aria-hidden="true"></i></a>
  </li>
  <li>
    <a routerLink="Profile" class="navbar-brand"><span class="xvrfont">{{authService.getUsername()}}</span><i class="fa fa-user-circle" aria-hidden="true"></i></a>
  </li>
</ng-template>

This part of the nav should be visible when the user is authenticated. to find out it checks via authService.

To check if a user is authenticated, the following code is ran on every route change:

checkAuthenticated(){
   if  (localStorage.getItem('token') != null){ this.authenticated = true; }
   else { this.authenticated = false; }
   console.log(this.authenticated); // for Debugging. 
}

The NgIf statement calls this method:

public isAuthenticated(){
     return this.authenticated;
}

According to the logs, 'authenticated' is changing between true and false correctly, but the Ngif isn't responding to the changes somehow.

the header component.ts looks like this:

import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import {AuthService} from "../auth/auth.service";

@Component({
  selector: 'app-header',
  providers: [AuthService],
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.css'],
  encapsulation: ViewEncapsulation.None
})
export class HeaderComponent implements OnInit {

  constructor(private authService: AuthService) { }

  ngOnInit() {
  }

  Logout(){
    this.authService.Logout();
  }

}

Any help would be appreciated. Thanks.

Edit:

auth.service.ts:

import { Injectable } from '@angular/core';
import {HttpClient, HttpHeaders} from '@angular/common/http';
import {Router} from "@angular/router";
import 'rxjs/add/operator/map';

@Injectable()
export class AuthService {

  public apiroot = 'http://localhost:3100/';
  public loginResponseMessage = '';
  public registerResponseMessage = '';
  public authenticated = false;


  public constructor(private http: HttpClient,
                     private router: Router) {

  }



  SignUp(username: string, password: string) {
    const User = JSON.stringify({username: username, password: password});
    let response: any;
    this.http.post(this.apiroot + 'register', User, {headers: new HttpHeaders()
      .set('content-type', 'application/json; charset=utf-8')})
      .subscribe(res => {
        response = res;
        this.registerResponseMessage = response.message;
        console.log(this.registerResponseMessage);
      });
  }

  Login(username: string, password: string) {
    const User = JSON.stringify({username: username, password: password});
    let response: any;
    this.http.post(this.apiroot + 'authenticate', User, {headers: new HttpHeaders()
      .set('content-type', 'application/json; charset=utf-8')})
      .subscribe(res => {
        response = res;
        this.loginResponseMessage = response.message;
        if (response.token) {
          localStorage.setItem('token', response.token);
          this.authenticated = true;
          localStorage.setItem('user', response.username);
          this.router.navigate(['/']);
        }
        else{  /* Do Nothing */  }
      });
  }


  Logout(): void{
    this.authenticated = false;
    localStorage.removeItem('token');
    console.log(this.isAuthenticated());
    this.router.navigate(['/Landing']);
  }

  isAuthenticated(){
    return this.authenticated;
  }

  checkAuthenticated(){
    if  (localStorage.getItem('token') != null){ this.authenticated = true; }
    else { this.authenticated = false; }
    console.log(this.authenticated); // for Debugging.
  }



  getUsername(){
    var result = localStorage.getItem('user');
    return result;
  }
}
Daanv z
  • 393
  • 1
  • 3
  • 16
  • 1
    it should be `*ngIf` – Rahul Singh Nov 24 '17 at 13:58
  • can you post the `AuthService` class ? What is the return type from the method `isAuthenticated()` ? – Niladri Nov 24 '17 at 14:05
  • `isAuthenticated()` returns a boolean. i've added the AuthService to the post. – Daanv z Nov 24 '17 at 14:10
  • @Daanvz yes but it's default value is false, and it's updated asynchronously by subscribing to an Observable returned from `this.http.post` in `Login` method. – Niladri Nov 24 '17 at 14:16
  • @Daanvz can you try with my answer? – Niladri Nov 24 '17 at 14:21
  • "authService.isAuthenticated()" won't have authService reference changed whenever authService.authenticated changes. So change detection won't happen. Also, calling a method of an instance in your *ngIf is a bad practice. Have a function calling the authservice inside your component. I would suggest you start using observables though... – Jan Somers JanS91 Nov 24 '17 at 14:30

4 Answers4

16

A good way is to share data through reactive coding with Observable.

In your service, create a BehaviorSubject and its Observable:

private _isAuthenticatedSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public isAuthenticatedObs: Observable<boolean> = _isAuthenticatedSubject.asObservable();

Each time you want to update your value, do a next on your subject:

_isAuthenticatedSubject.next(true); // authenticated
_isAuthenticatedSubject.next(false); // no more

Component side, just subscribe the observable to set the value locally for every subject changes:

this.authService.isAuthenticatedObs.subscribe(isAuth => this.isAuth = isAuth);

Or display value in your template with async pipe:

<ng-template *ngIf = "authService.isAuthenticatedObs | async">
Florian D
  • 221
  • 1
  • 5
  • @ Florian D - Hi, I have the same problem exposed and I solved by inserting the authentication service in "providers" as suggested by AJT82 but I want a performance app from all points of view, so I would like to implement the observables in this case. You wrote "Each time you want to update your value, I give you the following on your subject:" but I need to update the value when the login status changes, from logged to not logged and vice versa, how to intercept this event? –  Dec 13 '19 at 09:18
6

The problem is that you are providing the service at component level, that means, that all your components, that have the service added to providers array in component will have their own instance of service, so this is not a shared service at all. You want to have a singleton service, so only set the service in your providers array in your ngModule.

Also like mentioned by others, calling methods in template is a really bad idea, this method is called on each change detection, which is often, so it really hurts the performance of the app.

You could use Observables like suggested, or then just have a shared variable in your service. In the long run I'd suggest Observables, but depending on case just a shared variable is alright. Here's a sample for both: Passing data into "router-outlet" child components (angular 2)

AT82
  • 71,416
  • 24
  • 140
  • 167
3

template should be

<ng-template *ngIf = "authService.isAuthenticated()">
Sajeetharan
  • 216,225
  • 63
  • 350
  • 396
0

Another solution that worked for me was using rxjs firstValueFrom() function which replaces toPromise() and pass in your observable there https://rxjs.dev/deprecations/to-promise

const promise = firstValueFrom(this.OTPSentObservable);
    promise.then((data) => {
      console.log('OTP Sent', data);
      this.isLoading = false;
      this.isOtpReceived = true;
    })
Claudio Teles
  • 218
  • 3
  • 9