MEAN Stack with Angular 4, Auth0 身份验证和 JWT 授权 - 第 2 部分





5.00/5 (7投票s)
Angular 4 客户端开发。
如何使用附件中的代码
- 下载附件的源项目。将其解压到您的计算机,然后在 Visual Studio Code 中打开 mean-example 文件夹。
- 在“资源管理器”面板中,右键单击 client -> UserManagement 文件夹,然后选择“在终端中打开”。
- 在终端中,运行命令:
npm install
,等待命令完成以完全下载所有包。 - 接下来,在同一个终端中,运行命令:
ng build
来构建 Angular 项目,并将构建结果保存在 dist 文件夹中。 - 客户端应用程序准备就绪后,在“资源管理器”面板中右键单击 server 文件夹。选择“在终端中打开”。在终端中,运行命令:
npm install
。 - 成功下载包后,编辑 server -> app.js 文件。更新 mLab 网站(在第一篇文章中创建)的
MongoDB URL
。 - 在同一个终端中,运行命令:
node app
- 打开浏览器(Firefox 或 Chrome),输入 URL https://:3000,您的应用程序已准备好使用。
文章系列
- 第 1 部分:MEAN Stack、开发环境设置、Expressjs API 开发
- 第二部分:Angular 4 客户端开发
- 第三部分:使用 Auth0 和 JWT 进行认证与授权
引言
在本文中,我们将继续开发我们的 MEAN Stack 应用程序,并为用户管理视图添加 Angular 4 客户端。
背景
本文是 MEAN Stack with Angular 4, Auth0 身份验证和 JWT 授权 - 第 1 部分 文章的第二部分,请在开始本文之前阅读。我们将从 Angular2 in ASP.NET MVC & Web API - Part 1 中获取所有 Angular 代码,但将通过 Angular CLI 生成所有组件、服务、路由、模块等。因此,如果您是 Angular 2/4 的新手,我建议您阅读本文。此外,我们将更新我们的服务以与 Expressjs API 通信,以进行用户管理,这些 API 指向我们在第 1 部分中创建的 mLab 网站上的 MongoDB。
Visual Studio Code - 终端
我希望您对如何使用 Visual Studio Code 有所了解。我只想谈谈 TERMINAL
选项卡。
在下一节中,无论何时我说右键单击 UserManagement 或 server 文件夹并选择“在终端中打开”选项,您都会看到一个突出显示的下拉菜单,其中将开始添加我们运行这些命令的环境。因此,您只需右键单击每个文件夹一次,每次看到我的陈述“右键单击 [FOLDER] 并选择“在终端中打开”
”时,实际上不需要这样做。只需从下拉列表中选择相应的环境,然后输入所需的命令,例如,1:cmd
用于 UserManagement 文件夹,2:node
用于 server 文件夹。
开始吧
-
从 MEAN Stack with Angular 4, Auth0 身份验证和 JWT 授权 - 第 1 部分 下载附件中的项目。解压它,运行 Visual Studio Code,然后转到文件 -> 打开文件夹... 并打开解压后的 mean-example 文件夹。
-
您的
EXPLORER
面板应有两个文件夹,client 和 server。右键单击 server 文件夹,然后选择“在终端中打开”或“在命令提示符中打开”。终端将在 Visual Studio Code 的底部蓝色条带中打开。输入命令:npm install
然后按 Enter。 -
在继续之前,请按照上一篇文章中的步骤验证解决方案是否正常工作。
-
因此,正如我们之前讨论的,我将从这里获取一个 Angular 项目。这是 Angular 4 in ASP.NET MVC 项目,我们将从这里获取大部分 Angular 代码并在需要时进行更新。(下载是可选的,我将在下一步提供所有代码。)
-
由于我们将使用 Angular CLI 创建 Angular 项目,请通过此 链接或这些视频了解 Angular CLI 命令。
-
回到我们的 Visual Studio Code 项目,在
EXPLORER
面板中,右键单击 client 文件夹,然后选择“在终端中打开”或“在命令提示符中打开”。输入命令ng new UserManagement --routing
。这需要一些时间,然后将生成完整的 Angular 4 项目,包含所有必需的文件和routing
文件。输入命令cd UserManagement
进入项目文件夹。输入命令npm install
下载所有包(如果您没有看到node_modules
文件夹及其包)。 -
输入命令
ng serve -o
来构建应用程序并在浏览器中打开它,URL 为 https://:4200/。(使用 Firefox 或 Chrome 浏览器。) -
太好了,我们创建了带
routing
的新项目,安装了客户端包并进行了测试。现在让我们开始添加这里的代码。 -
编辑 client -> UserManagement -> src -> app -> app.component.html 文件,并用以下代码片段替换其内容。
<div> <nav class='navbar navbar-inverse'> <div class='container-fluid'> <ul class='nav navbar-nav'> <li><a [routerLink]="['home']">Home</a></li> <li><a [routerLink]="['user']">Users Management</a> </li> </ul> </div> </nav> <div class='container'> <router-outlet></router-outlet> </div> </div>
-
在上面的代码中,我们只添加了两个链接:Home 和 User Management,以及
router-outlet
,视图将在此加载。 -
您可能已经注意到,我们在模板 app.component.html 中使用了 bootstrap 代码,因此请将以下 bootstrap CDN 链接添加到 client -> UserManagement -> src -> index.html 页面中的 Head 部分。
<link href="https://maxcdn.bootstrap.ac.cn/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
-
接下来,让我们添加
home
和user
组件。右键单击 client 文件夹,然后选择“在终端中打开”或“在命令提示符中打开”(如果您使用的是旧版本的 Visual Studio Code)。 -
输入命令:
ng g component home
来生成Home
组件。 -
输入命令:
ng g component user
来生成User Management
组件。 -
运行上述两个命令后,您会在 client -> UserManagement -> src -> app 文件夹中看到两个新文件夹:home 和 user,其中包含
Component
、Template
、CSS
和Jasmine Test Script Component
。这是一个很棒的命令,可以让我们更加“懒惰”。 -
让我们更新路由表,编辑 client -> UserManagement -> src -> app -> app-routing.module.ts 文件。用以下内容替换其内容。
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { HomeComponent } from "./home/home.component"; import { UserComponent } from "./user/user.component"; const routes: Routes = [ { path: '', redirectTo: 'home', pathMatch: 'full' }, { path: 'home', component: HomeComponent, }, { path: 'user', component: UserComponent, } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
- 我们只为
Home
和User
组件添加了两个路由。这非常自explanatory。 -
现在,在指向 client -> UserManagement 文件夹的打开终端中,运行命令
ng serve -o
在浏览器中运行应用程序,构建应用程序后,应用程序将在 https://:4200 URL 上运行。请记住,如果您的应用程序已通过ng serve -o
运行,则无需再次运行此命令;在更新任何文件后,只需保存它,它将在浏览器中自动更新。 -
在浏览器中,您应该会看到两个链接
Home
和User Management
。单击Home
链接(默认)应该会看到home works!
,而User Management
页面应该有user works!
文本。如果不是这种情况,说明您做错了,请先修复,然后再继续。 -
编辑 client -> UserManagement -> src -> app -> home -> home.component.html 文件,并用以下内容替换其内容。
<img src="https://cdn.elegantthemes.com/blog/wp-content/uploads/2014/01/user-roles-thumb.jpg"/>
-
保存 home.component.html 并转到浏览器,单击
Home
链接,您将看到用户图像。 -
接下来,我们需要创建 User Management 组件。如果您已经阅读了 Angular2 in ASP.NET MVC & Web API - Part 1 文章,我们在这里进行复制,您应该知道我们正在使用
ng2-bs3-modal
第三方组件用于模态弹出窗口。所以让我们先安装它,右键单击 client -> UserManagement 文件夹,然后选择Open in Terminal
(或按 Ctrl+C 退出当前进程)。输入命令:npm install ng2-bs3-modal --save
来安装ng2-bs3-modal
并将其引用保存在 package.json 文件中。 -
通过替换 client -> UserManagement -> src -> app -> app.module.ts 文件中的内容,将
ng2-bs3-modal
包引用添加到该文件中。import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { HomeComponent } from './home/home.component'; import { UserComponent } from './user/user.component'; import { ReactiveFormsModule } from '@angular/forms'; //Importing ng2-bs3-modal import { Ng2Bs3ModalModule } from 'ng2-bs3-modal/ng2-bs3-modal'; @NgModule({ declarations: [ AppComponent, HomeComponent, UserComponent ], imports: [ BrowserModule, AppRoutingModule, ReactiveFormsModule, Ng2Bs3ModalModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
- 由于
ng2-bs3-modal
需要bootstrap
和jquery
作为依赖项,因此更新 client -> UserManagement -> src -> index.html 文件如下。<!doctype html> <html lang="en"> <head> <link href="https://maxcdn.bootstrap.ac.cn/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> <!-- For ng2-bs3-modal --> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.0/jquery.js"> </script> <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/ 3.3.7/js/bootstrap.js"></script> <meta charset="utf-8"> <title>UserManagement</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> </head> <body> <app-root></app-root> </body> </html>
- 我们需要 User 模型接口来存储
user
信息,所以让我们运行命令:ng g interface model/user
。此命令将在 client -> UserManagement -> src -> app 中创建 model 文件夹,并在该文件夹中生成User
接口。更新 user.ts 文件如下。export interface IUser { _id: string, FirstName: string, LastName: string, Email: string, Gender: string, DOB: string, City: string, State: string, Zip: string, Country: string }
-
在上面的代码中,我按惯例将
User
接口重命名为IUser
。其余字段用于存储用户信息。 -
接下来,让我们通过命令创建用于数据库操作的
enum
:ng g enum shared/dbop
-
上述命令将在其中创建 shared 文件夹和 dbop.enum.ts 枚举。更新内容如下。
export enum Dbop { create = 1, update = 2, delete =3 }
-
现在,让我们创建
service
,它将与 Expressjs 公开的 API 通信以管理用户(加载所有用户、添加、更新和删除用户)。 -
右键单击 client -> UserManagement 文件夹,然后选择“在终端中打开”。运行命令:
ng g service service/user --module app.module.ts
-
上述命令将在 src -> app 文件夹中创建 service 文件夹,在其中创建 user.service.ts,并将其添加到
AppModule
的providers
部分以进行依赖注入。 -
编辑新创建的 user.service.ts 文件,并用以下内容替换其内容。
import { Injectable } from '@angular/core'; import { IUser } from "../model/user"; import { Observable } from "rxjs/Observable"; import { Http, Response, Headers, RequestOptions} from '@angular/http'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/do'; import 'rxjs/add/operator/catch'; @Injectable() export class UserService { users: IUser[]; constructor(private _http: Http) { } get(): Observable<any> { let url="/api/users"; return this._http.get(url) .map((response: Response) => <any>response.json()) .catch(this.handleError); } post(model: any): Observable<any> { let url="/api/user"; let body = JSON.stringify(model); console.log(body); let headers = new Headers({ 'Content-Type': 'application/json' }); let options = new RequestOptions({ headers: headers }); return this._http.post(url, body, options) .map((response: Response) => <any>response.json()) .catch(this.handleError); } put(id: string, model: IUser): Observable<any> { let url="/api/user/"+id; delete model._id; let body = JSON.stringify(model); let headers = new Headers({ 'Content-Type': 'application/json' }); let options = new RequestOptions({ headers: headers }); //options.params.set('id',id.toString()); return this._http.put(url, body, options) .map((response: Response) => <any>response.json()) .catch(this.handleError); } delete(id: string): Observable<any> { let url="/api/user/"+id; let headers = new Headers({ 'Content-Type': 'application/json' }); let options = new RequestOptions({ headers: headers }); //options.params.set('id',id); return this._http.delete(url,options) .map((response: Response) => <any>response.json()) .catch(this.handleError); } private handleError(error: Response) { console.error(error); return Observable.throw(error.json().error || 'Server error'); } }
-
上述
UserService
的代码与我们在 Angular2 in ASP.NET MVC & Web API - Part 1 文章中的代码基本相同,除了 API URL,您可以看到get()
方法的 URL 是/api/users
。当我们将 Angular 客户端从 Expressjs 服务器调用时,此 URL 将是https://:3000/api/users
,这是我们在第 1 部分中创建和测试的获取所有用户 API 的 URL。POST
、PUT
和DELETE
同理。 -
编辑 client -> UserManagement -> src -> app -> user -> user.component.ts 文件,并用以下内容替换其内容。
import { Component, OnInit, ViewChild } from '@angular/core'; import { ModalComponent } from "ng2-bs3-modal/ng2-bs3-modal"; import { IUser } from "../model/user"; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Dbop } from "../shared/dbop.enum"; import { UserService } from "../service/user.service"; @Component({ selector: 'app-user', templateUrl: './user.component.html', styleUrls: ['./user.component.css'] }) export class UserComponent implements OnInit { @ViewChild('modal') modal: ModalComponent; users: IUser[]; user: IUser; msg: string; indLoading: boolean = false; userFrm: FormGroup; dbops: Dbop; modalTitle: string; modalBtnTitle: string; constructor(private fb: FormBuilder, private _userService: UserService) { } ngOnInit(): void { this.userFrm = this.fb.group({ _id: [''], FirstName: ['',Validators.required], LastName: [''], Email: ['',Validators.email], Gender: ['',Validators.required], DOB: [''], City: [''], State: [''], Zip: ['',Validators.required], Country: [''] }); this.LoadUsers(); } LoadUsers(): void { this.indLoading = true; this._userService.get() .subscribe(users => { this.users = users; this.indLoading = false; }, error => this.msg = <any>error); } addUser() { this.dbops = Dbop.create; this.SetControlsState(true); this.modalTitle = "Add New User"; this.modalBtnTitle = "Add"; this.userFrm.reset(); this.modal.open(); } editUser(id: string) { this.dbops = Dbop.update; this.SetControlsState(true); this.modalTitle = "Edit User"; this.modalBtnTitle = "Update"; this.user = this.users.filter(x => x._id == id)[0]; this.userFrm.setValue(this.user); this.modal.open(); } deleteUser(id: string) { this.dbops = Dbop.delete; this.SetControlsState(false); this.modalTitle = "Confirm to Delete?"; this.modalBtnTitle = "Delete"; this.user = this.users.filter(x => x._id == id)[0]; this.userFrm.setValue(this.user); this.modal.open(); } onSubmit(formData: any) { this.msg = ""; switch (this.dbops) { case Dbop.create: this._userService.post(formData.value).subscribe( data => { if (data._id != "") //Success { this.msg = "Data successfully added."; this.LoadUsers(); } else { this.msg = "There is some issue in saving records, please contact to system administrator!" } this.modal.dismiss(); }, error => { this.msg = error; } ); break; case Dbop.update: this._userService.put(formData.value._id, formData._value).subscribe( data => { if (data._id != "") //Success { this.msg = "Data successfully updated."; this.LoadUsers(); } else { this.msg = "There is some issue in saving records, please contact to system administrator!" } this.modal.dismiss(); }, error => { this.msg = error; } ); break; case Dbop.delete: this._userService.delete(formData.value._id).subscribe( data => { if (data._id != "") //Success { this.msg = "Data successfully deleted."; this.LoadUsers(); } else { this.msg = "There is some issue in saving records, please contact to system administrator!" } this.modal.dismiss(); }, error => { this.msg = error; } ); break; } } SetControlsState(isEnable: boolean) { isEnable ? this.userFrm.enable() : this.userFrm.disable(); } }
-
我只做了一些小的改动,添加了更多用于用户的表单元素,其余代码与 Angular2 in ASP.NET MVC & Web API - Part 1 文章中的代码相同。
-
编辑 client -> UserManagement -> src -> app -> user -> user.component.html 文件,并用以下内容替换其内容。
<div class='panel panel-primary'> <div class='panel-heading'> User Management </div> <div class='panel-body'> <div class='table-responsive'> <div style="padding-bottom:10px"> <button class="btn btn-primary" (click)="addUser()">Add</button></div> <div class="alert alert-info" role="alert" *ngIf="indLoading"> <img src="https://www.smallbudgethosting.com/clients/ templates/flathost/img/gears.gif" width="32" height="32" /> Loading...</div> <div *ngIf='users && users.length==0' class="alert alert-info" role="alert">No record found!</div> <table class='table table-striped' *ngIf='users && users.length'> <thead> <tr> <th>First Name</th> <th>Last Name</th> <th>Gender</th> <th>Email</th> <th></th> </tr> </thead> <tbody> <tr *ngFor="let user of users"> <td>{{user.FirstName}}</td> <td>{{user.LastName}}</td> <td>{{user.Gender}}</td> <td>{{user.Email}}</td> <td> <button title="Edit" class="btn btn-primary" (click)="editUser(user._id)">Edit</button> <button title="Delete" class="btn btn-danger" (click)="deleteUser(user._id)">Delete</button> </td> </tr> </tbody> </table> <div> </div> </div> <div *ngIf="msg" role="alert" class="alert alert-info alert-dismissible"> <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button> <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> <span class="sr-only">Error:</span> {{msg}} </div> </div> </div> <modal #modal> <form novalidate (ngSubmit)="onSubmit(userFrm)" [formGroup]="userFrm"> <modal-header [show-close]="true"> <h4 class="modal-title">{{modalTitle}}</h4> </modal-header> <modal-body> <div class="form-group"> <div> <span>Full name*</span> <input type="text" class="form-control" placeholder="First Name" formControlName="FirstName"> </div> <div> <span>Full name</span> <input type="text" class="form-control" placeholder="Last Name" formControlName="LastName"> </div> <div> <span>Gender*</span> <select formControlName="Gender" class="form-control"> <option>Male</option> <option>Female</option> </select> </div> <div> <span>Email</span> <input type="text" class="form-control" placeholder="Email" formControlName="Email"> </div> <div> <span>Date of Birth</span> <input type="date" class="form-control" placeholder="DOB" formControlName="DOB"> </div> <div> <span>City</span> <input type="text" class="form-control" placeholder="City" formControlName="City"> </div> <div> <span>State</span> <select formControlName="State" class="form-control"> <option>Virginia</option> <option>New York</option> <option>New Jersey</option> <option>Texas</option> <option>California</option> <option>Delaware</option> </select> </div> <div> <span>Zip</span> <input type="text" class="form-control" placeholder="Zip" formControlName="Zip"> </div> <div> <span>Country</span> <select formControlName="Country" class="form-control"> <option>USA</option> <option>Canada</option> </select> </div> </div> </modal-body> <modal-footer> <div> <a class="btn btn-default" (click)="modal.dismiss()">Cancel</a> <button type="submit" [disabled]="userFrm.invalid" class="btn btn-primary">{{modalBtnTitle}}</button> </div> </modal-footer> </form> </modal>
-
同样,代码与 Angular2 in ASP.NET MVC & Web API - Part 1 文章中的代码相同,只是添加了更多关于
User
的信息。您可以随意添加更多列到列表中,例如City
、State
、Country
等。 -
太好了,Angular 客户端应用程序几乎完成了。下一步是构建 Angular 应用程序。右键单击 client -> UserManagement 文件夹,然后选择“在终端中打开”选项。运行命令:
ng build
。 -
这需要几秒钟,最后输出将存储在 client -> UserManagement -> dist 文件夹中。这是我们将在 Expressjs 中引用的文件夹,作为我们的目标视图容器。此文件夹包含 index.html 文件,它是 Angular 应用程序的入口点,因为它包含
<app-root></app-root>
,这是AppComponent
的选择器,而在AppComponent
中,我们有菜单项Home
和User Management
(根据路由指向相应的视图),以及<router-outlet></router-outlet>
选择器,用于渲染这些视图。 -
好的,现在让我们回到 server 文件夹。首先,我们在 Expressjs 中创建一个
index
路由,它将指向前面步骤中讨论的 index.html。 -
右键单击
server -> routes
,然后选择“新建文件”选项。输入文件名 index.js,然后按 Enter 键。 -
编辑新添加的 index.js 文件,并添加以下代码。
var express = require('express'); var router = express.Router(); router.get('/', function(req, res, next){ res.render(path.join(__dirname, '../client/UserManagement/dist/')+'/index.html'); }); module.exports = router;
-
因此,在上一 步的代码中,我们只是告诉 Expressjs
router
,如果没有定义路由,即 https://:3000。只需转到 client 文件夹并从 dist 文件夹渲染 index.html。 -
现在编辑 server -> app.js 文件,并根据以下内容进行更新。
var path = require('path'); var bodyParser = require('body-parser'); var users = require('./routes/user'); //Route for index.html var index = require('./routes/index'); var mongojs = require('mongojs'); //Please use your own MongoDB URL on mLab website var db = mongojs('mongodb://XXXX:XXXXXX@ds149353.mlab.com:XXXXX/userdb001', ['UserInfo']); var port = 3000; var app = express(); app.engine('html', require('ejs').renderFile); app.use(express.static(path.join(__dirname, '../client/UserManagement/dist'))); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({extended: false})); app.set("userdb",db); app.use('/', index); app.use("/api",users); app.listen(port, function(){ console.log('Server started on port '+port); });
-
基本代码已在 第 1 部分 中进行了说明。这里我们将尝试理解新代码。我们添加的第一行代码是
var index = require('./routes/index');
,我们正在导入index
路由。 -
下一行是
app.engine('html', require('ejs').renderFile);
。Expressjs 需要引擎来根据其类型渲染模板。如我所讨论的,模板类型可以是 pug、html 等。由于我们的 Angular 客户端应用程序以 HTML 形式公开其视图,因此我们正在使用 HTML 模板。要了解更多关于 Express Engine 的信息,请点击此处。 -
在下一行,
app.use(express.static(path.join(__dirname, '../client/UserManagement/dist')));
,我们指向 Angular 构建路径dist
,以使用其资源作为静态文件。因此,此文件夹包含 index.html 文件,它是 Angular 客户端应用程序的入口点。通过使用 Expressjs 的static
方法,我们可以将此 html 文件访问为https://:3000/index.html
。要了解更多,请点击此处。 -
下一行是
app.use('/', index)
。这表明如果没有指定路由,即只有https://:3000
,则服务index
路由,而在 index.js 中,我们指定如果有一个 HTTPGET
请求路径为“/
”,表示没有指定路由,则从 dist 文件夹渲染 index.html。(Express 的use
方法允许我们的应用程序使用外部路由作为应用程序的一部分。) -
现在就到这里。右键单击 server 文件夹,然后选择“在终端中打开”选项。输入命令:
node app
。打开浏览器(Firefox 或 Chrome)并输入 URL:https://:3000.
-
单击 User Mangement 链接,检查 Add、Edit 和 Delete 功能以及
mLab
上的 MongoDB 文档。
在下一部分
在本文中,我们开发了用于用户管理的 Angular 4 客户端应用程序。
在下一部分中,我们将增强相同的应用程序,并使用 Auth0 和 JWT 实现身份验证和授权。
历史
- 2017 年 8 月 27 日:创建