Search code examples
angulartypescriptangular7angular8angular-router

Angular router navigate perform before previous code lines


I'm facing a strange behavior with my angular code. I have a login page that calls an API.

login.component.ts:

export class LoginComponent implements OnInit {

  formData;

  constructor(private usersService: UsersService, private router: Router) { }

  ngOnInit() {
    this.formData = new FormGroup({
      login: new FormControl('', [Validators.required]),
      password: new FormControl('', [Validators.required])
    });
  }

  onClickSubmit(data) {
    this.usersService.login(data).subscribe((response) => {
      const token = response.token;
      const userLogin = response.user.userLogin;

      localStorage.setItem('token', token);
      localStorage.setItem('login', userLogin);

      this.router.navigate(['/home']);
    });
  }
}

login.component.html:

<div class="card mb-3">
    <div class="card-header">
      Login
    </div>
    <div class="card-body">
      <form [formGroup]="formData" (ngSubmit)="onClickSubmit(formData.value)" novalidate>
        <div class="form-group">
          <label for="inputLogin">Login</label>
          <input type="text" class="form-control" id="inputLogin" name="login" placeholder="Login" formControlName="login">
          <span *ngIf="(login.dirty || login.touched) && login.invalid && login.errors.required" class="error">Login is required</span>
        </div>

        <div class="form-group">
          <label for="inputPassword">Password</label>
          <input type="password" class="form-control" id="inputPassword" name="password" placeholder="Password" formControlName="password">
          <span *ngIf="(password.dirty || password.touched) && password.invalid && password.errors.required" class="error">Login is required</span>
        </div>

        <button type="submit" class="btn btn-primary" [disabled]="!formData.valid">Sign in</button>
      </form>
    </div>
    <p class="card-text login-text"><small class="text-muted">Don't have any account? <a routerLink="/register">Register</a></small></p>
  </div>

I noticed that this.router.navigate() is performed before my localStorage.setIem.

users.service.ts:

export class UsersService {
  private apiUrl = 'https://localhost:63939/api/v1/';
  private registerUrl = this.apiUrl + 'identity/register';
  private loginUrl = this.apiUrl + 'identity/login';

  constructor(private httpClient: HttpClient) { }

  register(user: User): Observable<AuthResponse> {
    return this.httpClient.post<AuthResponse>(this.registerUrl, user);
  }

  login(user: LoginRequest): Observable<AuthResponse> {
    return this.httpClient.post<AuthResponse>(this.loginUrl, user);
  }
}

menu.component.ts:

export class MenuComponent implements OnInit {
  showLoginLink = true;
  userLogin;

  constructor() { }

  ngOnInit() {
    this.userLogin = localStorage.getItem('login');
    if (this.userLogin !== null) {
      this.showLoginLink = false;
    }
  }

}

menu.component.html

<li class="nav-item" *ngIf="showLoginLink">
    <a class="nav-link" routerLink="/register">Login / Register</a>
</li>
<li class="nav-item" *ngIf="userLogin">
    <a class="nav-link" routerLink="/register">{{userLogin}}</a>
</li>

When the navigate is performed, the menu is displayed but localStorage is empty so my userLogin is null and my *ngIf="userLogin" does not work. localStorage is filled right after the redirect, if I refresh the page, I've my values and my menu works (display my second <li> instead of the first).

Is it a standard behavior? How I can achieve the menu changes without refresh my page? (I prefer an explanation rather than a ready-made solution but I don't' refuse a solution ^-^)

EDIT: Add some code due to comments :) I will edit again if more code is needed


Solution

  • It happens because your navigate to home component and "menu" component is loaded before login component.

    You can use observer to let menu component know that login preformed.

    First of all you will make a service let's call it AuthService

    @Injectable()
    export class AuthService {
      public loginPreformedObserver: Subject<any> = new Subject<any>();
    }
    

    then in your login component

        export class LoginComponent implements OnInit {
    
        formData;
    
        constructor(private usersService: UsersService, private router: 
        Router,private authService: AuthService) 
        { 
        }
    
        ngOnInit() {
        this.formData = new FormGroup({
          login: new FormControl('', [Validators.required]),
          password: new FormControl('', [Validators.required])
         });
        }
    
        onClickSubmit(data) {
          this.usersService.login(data).subscribe((response) => {
          const token = response.token;
          const userLogin = response.user.userLogin;
    
          localStorage.setItem('token', token);
          localStorage.setItem('login', userLogin);
          this.authService.loginPreformedObserver.next();
          this.router.navigate(['/home']);
          });
          }
    }
    

    then in your menu component

    export class MenuComponent implements OnInit {
      showLoginLink = true;
      userLogin;
      private _loginPreformed: Subject<any>;
    
      constructor(private authService: AuthService) { }
    
      ngOnInit() {
    
        this._loginPreformed= this.authService.loginPreformedObserver;
        this._loginPreformed.subscribe(
         value => 
            {
          this.userLogin = localStorage.getItem('login');
          if (this.userLogin !== null) 
          {
            this.showLoginLink = false;
          }
            });
    
      }
    
    }
    

    For more details please see this link