65.9K
CodeProject 正在变化。 阅读更多。
Home

在 Angular 5 应用中实现 Guard

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (5投票s)

2018年3月25日

CPOL

5分钟阅读

viewsIcon

13592

downloadIcon

171

在这篇文章中,我们将实现 Guard,它将帮助我们在 Angular 5 应用中限制用户在未登录的情况下访问某些页面。

引言

这篇文章是课程“开发 Angular 5 应用”系列的延续。如果您还没有阅读过前面的文章,我强烈建议您先阅读。您可以在下方找到前一篇文章的链接。在这篇文章中,我们将实现 Guard,它将帮助我们在 Angular 5 应用中限制用户在未登录的情况下访问某些页面。因此,在文章结束时,您将了解到如何处理未经授权的路由访问。希望您会喜欢这篇文章。

开发 Angular 5 应用系列

这些是本系列的上一篇文章。请继续阅读。

  1. 什么是新的,以及如何设置我们的第一个 Angular 5 应用程序
  2. Angular 5 基本演示项目概述
  3. 在 Angular 5 应用中生成你的第一个组件和模块
  4. 在 Angular 5 应用中实现验证

背景

我们的应用可能有很多页面,有些页面可能供已登录用户使用,有些页面供未经授权的用户使用。但是,如果您允许所有用户使用所有页面,而不管他们是否已登录?是的,这显然不是一个完美的应用程序。在我们的应用程序中,我们将只允许已登录用户访问主页。要实现这一点,我们可以使用 Guard。

生成 Guard

由于我们正在使用 Angular CLI,我们可以轻松地在我们的应用程序中生成 Auth Guard。您还记得我们在上一篇文章中创建了一些组件吗?我们可以用同样的方式创建 Guard。

PS F:\Visual Studio\ng5> ng generate guard auth

在上面的步骤中,我们在根级别生成了 Guard。运行上述命令后,它将生成两个新的 Typescript 文件,如下所示:

现在让我们来看看这两个文件。

auth.guard.ts

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } 
         from '@angular/router';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    return true;
  }
}

auth.guard.spec.ts

import { TestBed, async, inject } from '@angular/core/testing';

import { AuthGuard } from './auth.guard';

describe('AuthGuard', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [AuthGuard]
    });
  });

  it('should ...', inject([AuthGuard], (guard: AuthGuard) => {
    expect(guard).toBeTruthy();
  }));
});

这只是一个测试文件,我们将在另一篇文章中介绍如何在 Angular 应用上执行测试。现在,让我们专注于实现。

生成登录组件并进行设置

现在 Guard 已经准备好进行修改了,在进行修改之前,让我们创建一个登录组件。

PS F:\Visual Studio\ng5> ng g c login
  create src/app/login/login.component.html (24 bytes)
  create src/app/login/login.component.spec.ts (621 bytes)
  create src/app/login/login.component.ts (265 bytes)
  create src/app/login/login.component.css (0 bytes)
  update src/app/app.module.ts (1331 bytes)
PS F:\Visual Studio\ng5>

现在是时候在 app.module.ts 文件中编辑我们的路由,为登录添加新路由了。请注意,在 app.module.ts 文件中创建路由不是推荐的方式。我们应该为此创建一个单独的路由模块,并在 app.module.ts 中使用它。我将在另一篇文章中分享如何做到这一点。

import { LoginComponent } from './login/login.component';

const myRoots: Routes = [
  { path: '', component: HomeComponent, pathMatch: 'full' },
  { path: 'register', component: RegistrationComponent },
  { path: 'login', component: LoginComponent},
  { path: 'home', component: HomeComponent}]

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    NavComponent,
    RegistrationComponent,
    LoginComponent
  ],
  imports: [
    BrowserModule, BrowserAnimationsModule, FormsModule, ReactiveFormsModule,
    MatButtonModule, MatCardModule, MatInputModule, MatSnackBarModule, MatToolbarModule,
    RouterModule.forRoot(myRoots)
  ],
  providers: [],
  bootstrap: [AppComponent]
})

我们可能还需要通过添加一个用于登录的新按钮来编辑 nav.component.html 文件。

<mat-toolbar color="primary">
  <button mat-button routerLink="/">Home</button>
  <button mat-button routerLink="/register">Register</button>
  <button mat-button routerLink="/login">Login</button>
</mat-toolbar>

现在,转到您的 login.component.html 文件并添加以下标记。

<mat-card>
  <form [formGroup]="form" (ngSubmit)="login()">
    <mat-input-container>
      <input matInput type="email" placeholder="Email" formControlName="email" />
    </mat-input-container>
    <mat-input-container>
      <input matInput type="password" placeholder="Password" formControlName="password" />
    </mat-input-container>
    <button mat-raised-button type="submit" color="primary">Login</button>
  </form>
</mat-card>

如您所见,我们在提交时实际上调用了一个 login 方法。在我们实现该函数之前,现在让我们在 login.component.ts 文件中创建一个 auth 服务并进行如下编辑:

import { Injectable } from '@angular/core';
import { Router } from '@angular/router';

@Injectable()
export class AuthService {

  constructor(private myRoute: Router) { }

  sendToken(token: string) {
    localStorage.setItem("LoggedInUser", token)
  }

  getToken() {
    return localStorage.getItem("LoggedInUser")
  }

  isLoggednIn() {
    return this.getToken() !== null;
  }

  logout() {
    localStorage.removeItem("LoggedInUser");
    this.myRoute.navigate(["login"]);
  }
}

现在是时候编辑我们的 login.component.ts 文件了。

import { Component, OnInit } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { AuthService } from '../auth.service';

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

  form;
  constructor(private fb: FormBuilder,
    private myRoute: Router,
    private auth: AuthService) {
    this.form = fb.group({
      email: ['', [Validators.required, Validators.email]],
      password: ['', Validators.required]
    });
  }

  ngOnInit() {
  }

  login() {
    if (this.form.valid) {
      this.auth.sendToken(this.form.value.email)
      this.myRoute.navigate(["home"]);
    }
  }
}

这不是首选的实现方式,相反,您应该创建一个 API 服务并连接到数据库。然后,检查此电子邮件和密码是否有效。最后,您应该使用从服务器 API 获得的令牌来设置 localStorage。这个主题足够写另一篇文章了,为了使这篇文章简短,我只在这里设置 email 的值。

如果您收到错误“ERROR Error: Uncaught (in promise): EmptyError: no elements in sequence”,请确保您的 rxjs 版本在 package.json 文件中大于或等于 5.5.4。rxjs 版本 5.5.3 在 Angular 5 中存在此问题。

现在运行您的应用程序,并确保它被重定向到主页。您也可以在浏览器控制台中检查 localStorage 条目。

设置生成的 Guard

让我们转到我们的 auth.guard.ts 文件并进行一些更改。

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } 
         from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { AuthService } from './auth.service';
import {Router} from '@angular/router';

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private auth: AuthService,
    private myRoute: Router){

  }

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    if(this.auth.isLoggednIn()){
      return true;
    }else{
      this.myRoute.navigate(["login"]);
      return false;
    }
  }
}

不要忘记在 app.module.ts 的 providers 中添加 AuthServiceAuthGuard

import { AuthService } from './auth.service';
import { AuthGuard } from './auth.guard';

在路由中使用 Auth Guard

太棒了,设置完成了,现在是时候行动了。让我们转到我们的路由并按照以下方式更改代码:

const myRoots: Routes = [
  { path: '', component: HomeComponent, pathMatch: 'full' , canActivate: [AuthGuard]},
  { path: 'register', component: RegistrationComponent },
  { path: 'login', component: LoginComponent},
  { path: 'home', component: HomeComponent, canActivate: [AuthGuard]}
];

运行您的应用程序,并在登录前尝试访问主页。如果一切顺利,它应该会将您重定向到登录页面。

让我们在 nav.component.htmlnav.component.ts 中进行更多编码,以使我们的应用程序更有意义。

nav.component.ts

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

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

  constructor(private auth: AuthService) { }

  ngOnInit() {
  }
}

nav.component.html

<mat-toolbar color="primary">
  <button mat-button routerLink="/home">Home</button>
  <button mat-button *ngIf="!auth.isLoggednIn()" 
   routerLink="/register">Register</button>
  <button mat-button *ngIf="!auth.isLoggednIn()" 
   routerLink="/login">Login</button>
  <button mat-button *ngIf="auth.isLoggednIn()" 
   (click)="auth.logout()">Logout</button>
</mat-toolbar>

在这里,我们只为已登录用户提供“登出”按钮,并隐藏已登录用户的“注册”和“登录”按钮。听起来不错吧?由于我们在 nav.component.ts 文件中注入了 auth 服务,因此 auth.isLoggedIn 函数将在此页面上可用。

结论

非常感谢你的阅读。我是否遗漏了你认为需要的东西?你觉得这篇文章有用吗?我希望你喜欢这篇文章。请分享你宝贵的建议和反馈。

现在轮到你了。你有什么想法?

没有评论的文章 isn't an article,但请尽量就事论事。如果您有与本文无关的问题,最好将其发布到 C# Corner、Code Project、Stack Overflow、ASP.NET Forum,而不是在这里评论。请在下面的评论部分发布您的问题,如果我能帮忙,我一定会尽力而为。

历史

  • 2018年3月25日:初始版本
© . All rights reserved.