Home > front end >  Angular routing from login page onto further pages
Angular routing from login page onto further pages

Time:07-14

I have created an angular 13 app which works great (fairly new to Angular btw). I am now having to add authentication as a pre-requisite to being able to continue to use the site. So the routing should be:

  1. Initial load to login page (no nav bar)
  2. Once logged in we load the App and Nav Bar accordingly.
  3. If the token has expired further routing should force to login page
  4. On clicking log out we route back to login.

Parts 1 and 4 are working with AuthGuards etc but I am having issues routing from the login page onto the home page and showing the Navbar correctly.

Current code: app.component.html

    <div>
      <app-navbar *ngIf="isLoggedIn" [isLoggedIn]="this.isLoggedIn"></app-navbar>
      <router-outlet></router-outlet>
    </div>

app.component.ts

    ngOnInit(): void {
      if (this.cacheService.load("auth-token")) {
        this.isLoggedIn = true;
      }
    }

app-routing-module.ts

    import { NgModule } from '@angular/core';
    ....

    const routes: Routes = [{ path: 'login', component: LoginComponent },
      { path: '', component: HomeComponent, canActivate: [AuthGuardService] },
      { path: 'authed', component: AuthedComponent, canActivate: [AuthGuardService] },
      { path: '**', redirectTo: '', pathMatch: 'full' }
    ];

    @NgModule({
      imports: [RouterModule.forRoot(routes)],
      exports: [RouterModule]
    })
    export class AppRoutingModule { }

Login.component.ts

    ngOnInit(): void {
      if (this.cacheService.load("auth-token")) {
        this.isLoggedIn = true;
      }
    }

    onSubmit(): void {
      const { username, password } = this.form;
      const userReq: IUserRequest = {
        Email: username,
        Password: password,
      };
      this.userService.login(userReq).subscribe({
        next: res => {
          console.log(res);
          this.isLoggedIn = res.Success == true;
          this.isLoginFailed = false;
          this.route.navigate(['']);
        },
        error: err => {
          this.errorMessage = err;
          this.isLoginFailed = true;
        }
      });
    }

auth-interceptor.ts

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
      if (this.cacheService.load('auth-token')) {
        return true;
      }

      this.route.navigate(['login']);
      return false;
    }

So what is happening? After authenticating successfully, the app loads fine but the Navbar does not display (until I manually refresh the page). Adding in a window.location.reload in an oninit just continually refreshes the screen.

I have tried numerous ways to try and fix this, with no joy. Anyone have any ideas what could be wrong?

Thank you in advance.

CodePudding user response:

There are several different approaches.

Continuing with your approach (not recommended):

Your main problem seems to be that you are not able to set isLoggedIn of AppComponent. Instead you are setting it on the LoginComponent but there it doesn't seem to have any effect as the navbar/header logic is in the app component and not in the LoginComponent.

So you have to notify the AppComponent, that a login has occured.

You should be able to inject AppComponent into your LoginComponent as this is the host (top-most host) of your project.

Variant 1 - Not recommended - Injection of AppComponent

public constructor(private appComponent: AppComponent){
}

Then when you log in you may do the following:

this.userService.login(userReq).subscribe({
    next: res => {
      console.log(res);
      this.appComponent.isLoggedIn = res.Success == true; // THIS IS THE IMPORTANT LINE
      this.isLoginFailed = false;
      this.route.navigate(['']);
    },
    error: err => {
      this.errorMessage = err;
      this.isLoginFailed = true;
    }
  });

Variant 2 - Not recommended - Use a SharedService

Alternatively not really "better" you may use a service and inject the service in the AppComponent as well as LoginComponent.

@Injectable({
  providedIn: 'root'
})
export class GuiService {
  isLoggedIn: boolean = false;

  constructor() { }
}

As a service is a singleton the state is shared across components.

Now to the recommended way - Use of child-routes

Create a component which displays the layout you would like to see as soon as you are logged in. Use a router-outlet inside this component and all child - routes will be rendered where the router-outled is placed. Which means you may have simple layouts as the following:

  • Header
  • Router-Outlet (Your child-components)
  • Footer

All components which are not child routes of this basic layout won't have the header or footer.

authenticated-layout.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-authenticated-layout',
  templateUrl: './authenticated-layout.component.html',
  styleUrls: ['./authenticated-layout.component.css']
})
export class AuthenticatedLayoutComponent implements OnInit {

  constructor() { }

  ngOnInit(): void {
  }

}

authenticated-layout.component.html

<header>
  MY HEADER
</header>

<router-outlet></router-outlet>


<footer>
  MY FOOTER
</footer>

Manipulate the router module that all the components which should display a header and a footer are children of the newly created component:

app-routing-module.ts

RouterModule.forRoot([
      {
        path: '',
        component: AuthenticatedLayoutComponent,
        canActivate: [YourGuard],
        children: [ // THIS IS THE IMPORTANT PART
          {
            path: 'edit-user',
            component: EditUserComponent
          }
        ]
      },
      {
        path: 'login',
        component: ChildCompComponent
      }
    ])

If you navigate to /login you won't have the header and footer. If you navigate to edit-user you will have a header and footer. I hope this helps.

CodePudding user response:

The reason the navbar doesn't show up is because you never set isLoggedIn = true in the main component after authenticating.

I would recommend putting this variable in your userService. Services are instantiated on the first injection, so you can check the cache when initializing fields. This will check the cache on page load / refresh. You can also pipe your login observable to set this value according to the result.

export class UserService {
  isLoggedIn = !!this.cacheService.load("auth-token");

  constructor(private cacheService: CacheService) { }

  login( ... ) {
    // Your login logic
    return originalObservable.pipe(tap((res) => this.isLoggedIn = res.Success === true));
  }
}

You should also be caching the auth token in that pipe if you aren't already doing so.

Then just inject this service anywhere you want to check the state. Make sure you directly check the value, not just copy it onto another variable.

app.component

export class AppComponent {
  constructor(private userService: UserService) {};

  get isLoggedIn() {
     return this.userService.isLoggedIn;
  }
}
    <div>
      <app-navbar *ngIf="isLoggedIn"></app-navbar>
      <router-outlet></router-outlet>
    </div>

  • Related