Angular5 和 Firebase 全栈开发实用指南(一)
原文:
zh.annas-archive.org/md5/f113aa7bff00c98a8b38bd392d37f2d0译者:飞龙
前言
使用 Angular 5 和 Firebase 进行实战全栈开发将为你提供开发 Web 应用所需的实际知识。在这本书中,我们介绍了编写完整社交媒体应用所需的所有主要工具和技术。本书以这样的方式编写,你将从头开始构建应用,随着你在书中的进展,你将了解 Web 开发的主要概念。作为我们开发过程的一部分,本书严格遵守常见的软件原则和编码标准。我还介绍了对 Angular 组件、服务和管道进行单元测试。这些软件开发实践使我们成为更好的开发者。
作为本书的一部分,我们将使用 Angular 作为前端和 Firebase 作为后端,从开发到生产构建 Web 应用。Firebase 完全可扩展且实时,提供了开发丰富、协作应用所需的所有工具。使用 Firebase,可以轻松构建和原型化任何商业应用,而无需创建复杂的后端服务。
你将使用 Angular 框架,通过 HTML 和 CSS 构建客户端应用程序。Angular 提供了高级功能,可以将客户端代码模块化到 HTML、CSS、组件和服务中。作为我们开发过程的一部分,我们还将集成其他常用库,如 RxJS。
因此,请期待一段精彩的旅程。
本书面向对象
本书面向那些对 Angular 框架有一定了解的 JavaScript 开发者,他们希望使用 Angular 和 Firebase 开始开发实时应用程序。对于任何希望将业务或想法在线化的小型初创公司来说,本书非常实用,因为它提供了快速开发应用程序的实际技巧。本书也非常适合那些希望在不进行大量投资的情况下构建完整 Web 应用的大学生。如果你正在寻找一种更实用、理论性较少的方法来学习主要 Web 应用概念,那么这本书适合你。
本书涵盖内容
第一章,组织你的 Angular 项目结构,让你了解 Angular 项目结构。你将使用 Angular CLI 命令创建 Angular 项目,并了解 Angular 项目中的所有重要库。
第二章,创建注册组件,介绍了如何启用 Firebase 身份验证并创建注册组件、模板和服务。你还将了解 AngularFire2 库。
第三章,创建登录组件,教你如何创建登录组件和模板。你还将执行重置密码操作。
第四章,组件之间的路由和导航,帮助您为模块启用路由并创建导航栏以在组件之间导航。
第五章, 创建用户个人资料页面,专注于 RxJs 库并在组件模块之间传递数据。您还将创建带有编辑操作的个人信息页面。
第六章,创建用户好友列表,教您如何创建带有分页功能的好友列表页面。您还将创建好友服务和自定义 Angular 日期管道。
第七章, 探索 Firebase 存储,讨论 Firebase 存储并为您的应用配置存储。您还将从 Firebase 存储上传和下载图片到您的应用中。
第八章,创建聊天组件,帮助您创建带有子组件的聊天模块。我们还学习了更多关于 SCSS 变量和 mixin 概念。
第九章, 将聊天组件与 Firebase 数据库连接,涵盖了如何将您的聊天组件与 Firebase 数据库集成。您还将学习使用路由参数传递数据。
第十章, 对我们的应用进行单元测试,教您关于 Angular 测试的知识。您将为我们的组件、服务和管道编写单元测试用例,并了解代码覆盖率。
第十一章, 调试技术,涵盖了调试技术的不同方面。作为本章的一部分,我们将涵盖 Angular、Web、TypeScript、CSS 和网络调试。
第十二章, Firebase 安全和托管,教您关于 Firebase 安全并解释如何为用户和聊天消息添加安全规则。您还将学习如何创建多个环境并托管我们的好友应用。
第十三章,使用 Firebase 扩展我们的应用,涵盖了 Firebase 云消息。您还将了解 Google Analytics 和广告。
第十四章, 将我们的应用转换为 PWA,涵盖了 PWA 功能并展示了如何使您的应用符合 PWA 规范。您还将了解服务工作者。
要充分利用这本书
要充分利用这本书,您应该有一些使用 HTML 和 CSS 进行 JavaScript 开发的经验。随着您通过章节的进展,将提供所有重要工具、编辑器或框架的链接,这些是开发 Angular 应用所需的。
下载示例代码文件
您可以从www.packtpub.com的账户下载本书的示例代码文件。如果您在其他地方购买了本书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。
您可以通过以下步骤下载代码文件:
-
在www.packtpub.com登录或注册。
-
选择支持选项卡。
-
点击代码下载与勘误。
-
在搜索框中输入书籍名称,并遵循屏幕上的说明。
文件下载完成后,请确保使用最新版本解压缩或提取文件夹:
-
WinRAR/7-Zip for Windows
-
Zipeg/iZip/UnRarX for Mac
-
7-Zip/PeaZip for Linux
本书代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Hands-On-Full-Stack-Development-with-Angular-5-and-Firebase。如果代码有更新,它将在现有的 GitHub 仓库中更新。
我们还有其他来自我们丰富图书和视频目录的代码包可供选择,请访问**github.com/PacktPublishing/**。查看它们!
使用的约定
在本书中使用了多种文本约定。
CodeInText: 表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“将下载的WebStorm-10*.dmg磁盘映像文件作为系统中的另一个磁盘挂载。”
代码块按照以下方式设置:
public addUser(user: User): void {
this.fireDb.object(`${USERS_CHILD}/${user.uid}`).set(user);
}
任何命令行输入或输出都按照以下方式编写:
$ cd <your directory>\friends\src\app
粗体: 表示新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词在文本中显示如下。以下是一个示例:“点击认证。”
警告或重要说明看起来像这样。
小贴士和技巧看起来像这样。
联系我们
我们始终欢迎读者的反馈。
一般反馈: 发送电子邮件至feedback@packtpub.com,并在邮件主题中提及书籍标题。如果您对本书的任何方面有疑问,请通过questions@packtpub.com发送电子邮件给我们。
勘误: 尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,我们将非常感激您能向我们报告。请访问www.packtpub.com/submit-erra…,选择您的书籍,点击勘误提交表单链接,并输入详细信息。
盗版: 如果您在互联网上以任何形式发现我们作品的非法副本,我们将非常感激您能提供位置地址或网站名称。请通过copyright@packtpub.com与我们联系,并提供材料的链接。
如果您有兴趣成为作者:如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问 authors.packtpub.com。
评论
请留下评论。一旦您阅读并使用了这本书,为何不在您购买它的网站上留下评论呢?潜在读者可以查看并使用您的客观意见来做出购买决定,我们 Packt 可以了解您对我们产品的看法,我们的作者也可以看到他们对书籍的反馈。谢谢!
如需了解 Packt 的更多信息,请访问 packtpub.com。
第一章:组织您的 Angular 项目结构
在本章中,我们将使用 @angular/cli 命令创建项目结构。我们将使用 npm(Node 包管理器)下载所有必要的库。然后,我们将启动项目,查看在浏览器上运行的第一个 Angular 应用程序。我们将介绍开发高质量 Angular 应用程序所需的所有重要文件。
npm 是 JavaScript 的包管理器。它还帮助安装开发应用程序所需的包。
我们还将运行设置测试框架的过程,并为我们的社交应用的一些重要组件编写测试用例。我们的目标是通过对测试、开发和数据分析的开发,创建一个生产就绪的应用程序。
最后,我们将介绍编码风格的推荐指南,因为这是开发中最被忽视的部分。我们觉得在团队开发任何应用程序时,这是必需的,以确保在整个开发团队中遵循通用术语和术语。
本章将涵盖以下主题:
-
创建项目大纲
-
项目结构
-
设置 Firebase
-
Angular 术语
-
应用程序项目结构
-
常见原则
-
编码标准指南
创建项目大纲
Angular CLI(命令行界面)使创建项目变得容易。它还通过简单的命令帮助创建组件、路由、服务和管道。我们将使用 Angular CLI 创建一个示例项目大纲。这提供了开始构建您的应用程序所需的所有必要文件。
我们需要以下四个步骤来运行我们的第一个 Angular 应用程序,而无需编写任何代码,并获取我们的第一个欢迎页面。我们还需要 npm 来安装重要的库;您可以从 nodejs.org/en/download/ 下载 npm:
- 让我们使用
npm安装 Angular CLI:
$npm install -g @angular/cli
请注意,-g 标志全局安装 angular CLI,以便在任何项目中都可以访问。
- 使用
ng new命令创建 friends 项目的结构。由于我们使用 SASS 创建样式表,因此我们还将提供--style=sass选项,这将配置我们的应用程序中的 SASS。请查看以下命令:
$ng new friends --style=sass
不要忘记在最后给出您的项目名称,否则它将创建一个默认的项目名称。
- 进入新创建的
friends文件夹并执行npm install。这安装了构建我们的应用程序所需的所有包,并创建了node_modules文件夹。请查看以下命令:
$npm install
- 最后,使用
npm start部署新创建的 friends 项目,并查看在浏览器中运行的第一个 Angular 应用程序。请参考以下命令。默认端口是 4200,您可以在浏览器中输入http://localhost:4200来查看您的示例应用程序:
$npm start
恭喜您完成了您的第一个 Angular 应用程序!
项目结构
下一步是将新创建的项目映射到一个编辑器。我们使用 WebStorm 作为我们的编辑器,这是一个付费版本。您可以使用免费的 Visual Studio Code 或 Sublime 作为您的编辑器,您可以从以下 URL 下载它们:
-
Visual Studio Code:
code.visualstudio.com/download -
Sublime Text:
www.sublimetext.com/download
我们将项目映射到我们的 WebStorm 编辑器,并使用 npm install 安装依赖库,它创建了一个 node_modules 文件夹。这个文件夹包含所有依赖库。在编辑器中的项目结构如下截图所示:
package.json 概述
package.json 文件指定了运行应用的起始包。我们也可以在此文件中添加包,随着应用的演变。
package.json 文件包含以下两组依赖:
dependencies**: **"dependencies" 中的包包含运行 Angular 应用所需的所有基本库:
"dependencies": {
"@angular/animations": "⁵.2.0",
"@angular/common": "⁵.2.0",
"@angular/compiler": "⁵.2.0",
"@angular/core": "⁵.2.0",
"@angular/forms": "⁵.2.0",
"@angular/http": "⁵.2.0",
"@angular/platform-browser": "⁵.2.0",
"@angular/platform-browser-dynamic": "⁵.2.0",
"@angular/router": "⁵.2.0",
"core-js": "².4.1",
"rxjs": "⁵.5.6",
"zone.js": "⁰.8.19"
},
依赖包括以下库。以下列表中我们只解释了重要的库:
-
-
@angular/common: 它提供了常用功能,如管道、服务和指令。 -
@angular/compiler: 它理解模板,并将它们转换为浏览器可以理解的格式,以便我们的应用可以运行和渲染。我们不直接与此库交互。 -
@angular/core: 它提供了所有常用元数据,例如组件、指令、依赖注入和组件生命周期钩子。 -
@angular/forms: 它提供了一个基本的输入布局。这用于登录、注册或反馈。 -
@angular/http: 它是一个 Angular 服务,提供了一个用于 HTTP REST 调用的实用函数。 -
@angular/platform-browser: 它提供了用于生产构建启动应用的bootstrapStatic()方法。 -
@angular/platform-browser-dynamic: 它主要在开发过程中的启动阶段使用。 -
@angular/router: 它提供了一个组件路由器,用于组件之间的导航。 -
core-js: 它将 ES2015(ES6)的基本功能修补到全局上下文 window 中。 -
rxjs: 它是一个用于使用观察者进行响应式编程的库,有助于编写 HTTP 的异步代码。 -
zone.js: 它提供了一个在异步任务之间持续存在的执行上下文。
-
-
devDependencies:devDependencies中的包主要在开发过程中需要。您可以使用以下npm命令在生产构建中排除devDependencies:
$npm install my-application --production
设置 Firebase
在我们的项目中,我们使用 Firebase 作为后端服务,一个移动和网页应用平台。它提供了一整套集成到一个平台的产品,使得开发过程更加快速,主要部分由平台处理。其产品主要围绕两个主要主题:
-
开发和测试你的应用:这些套件提供了开发可扩展应用所需的所有服务
-
增长并吸引你的受众:这对于我们应用的增长是必需的
你可以参考以下链接获取有关 Firebase 的更多信息:firebase.google.com/。
设置 Firebase 账户
创建新 Firebase 项目的第一步是创建你的新 Google 账户或使用当前账户。
现在,打开 Firebase 门户,按照以下四个步骤开始你的 Firebase 项目:
-
点击右上角的“GO TO CONSOLE”。
-
点击带有加号(+)的“添加项目”。
-
在弹出的窗口中,输入项目名称和国家/地区。项目 ID 是可选字段,它将采用默认值。
-
点击 CREATE PROJECT。
以下截图显示了朋友项目:
最后,你在 Firebase 的欢迎页面上获取配置详情,然后点击将 Firebase 添加到你的 web 应用。你可以将此配置复制到 environments.ts 中,如下所示:
export const environment = {
production: false,
firebase: {
apiKey: 'XXXX',
authDomain: 'friends-4d4fa.firebaseapp.com',
databaseURL: 'https://friends-4d4fa.firebaseio.com',
projectId: 'friends-4d4fa',
storageBucket: '',
messagingSenderId: '321535044959'
}
};
Angular 术语
在本节中,我们将讨论 Angular 中的重要术语。你可能对其中大部分都很熟悉,但这将帮助你巩固知识:
-
模块:Angular 通过模块支持模块化。所有 Angular 项目至少有一个名为
AppModule的模块。当我们构建大型应用时,我们可以将应用划分为具有共同相关能力的多个功能模块。我们可以使用@NgModule注解创建模块。 -
组件:组件是一个控制器,它具有视图和逻辑来管理视图事件和组件之间的导航。它通过各种数据绑定技术与视图交互。你可以使用以下 CLI 命令生成
component:
$ng g component <component-name>
-
模板:模板代表网页的视图,它使用 HTML 标签创建。它还包含许多自定义标签,如 Angular 指令以及原生 HTML 标签。
-
元数据:它为任何类分配行为。这是位于类之上的元数据,它告诉 Angular 类的行为。例如,组件是通过
@Component注解创建的。 -
数据绑定:这是一个模板与组件交互的过程。数据通过各种数据绑定技术来回传递。Angular 支持以下三种类型的数据绑定:
-
插值:在这个绑定中,我们使用两个大括号来访问组件成员的属性值。例如,如果我们有一个组件中的类成员属性
name,那么我们可以在模板中定义{{name}}来访问名称值。 -
属性绑定:这种数据绑定技术有助于将值从父组件传递到子组件。例如,如果我们有一个父组件中的
name作为类成员属性,以及子组件中的userName,那么我们可以使用[userName] = "name"将父组件的值分配给子组件。 -
事件绑定:这种事件驱动的数据绑定有助于将值从模板传递到组件。例如,我们显示列表中的名称;当用户点击列表项时,我们使用事件绑定
(click)="clickName(name)"传递点击值。
-
-
指令:它是注入到模板中的行为,它修改了 DOM(文档对象模型)的渲染方式。基本上有两种类型的指令:
-
结构:它通过添加、删除或替换 DOM 元素来改变 DOM 布局。这类指令的示例有
ngFor和ngIf。 -
属性:它改变现有 DOM 元素的外观和行为。
ngModel指令是属性指令的示例,它通过响应事件来改变现有元素的行为。
-
-
服务:它是一个可用的实体,被任何 Angular 组件消费,有助于将视图逻辑与业务逻辑分离。我们通常在服务中为特定模块编写 HTTP 特定的调用,因为它有助于代码的可读性和可维护性。流行的服务示例包括日志服务或数据服务。您可以使用以下 CLI 命令创建
service:
$ng g service <service-name>
- 管道:它是 Angular 中最简单但最有用的功能之一。它提供了一种编写可跨应用程序重用实用功能的方法。Angular 提供了内置管道,如日期和货币。您可以使用以下 CLI 命令创建
pipe:
$ng g pipe <pipe-name>
应用程序的项目结构
当我们浏览我们的示例朋友应用程序时,我们会遇到 src 文件夹,它包含所有具有视图、业务逻辑和导航功能的应用程序核心文件。开发者的大部分时间都花在这个文件夹中:
我们组织文件夹时遵循的主要思想是功能模块。每个类似的功能都被组织到功能模块中。为了更好地理解它,我们将查看我们朋友应用程序中的一个认证功能示例,以及本书后续章节中的一些文件引用。
在我们的应用程序中,所有与认证相关的功能,如登录和注册,都被组合到一个名为 authentication 的模块中。这种相同的模式将应用于我们应用程序的所有功能。这使得我们的应用程序更加模块化和可测试。我们以一个认证功能模块为例,在以下章节中对其进行了更详细的解释。
我们的认证功能模块如下截图所示;认证功能具有登录和注册功能,所有组件都在模块中声明——它还有一个用于内部导航的自己的路由模块:
应用模块
应用模块是我们整个项目的根模块。所有 Angular 项目至少有一个应用模块,这是强制性的,它用于启动项目以启动应用程序。应用模块使用@NgModule装饰器声明。NgModule注解中的元数据如下:
-
imports:在imports标签中,我们声明所有依赖的功能模块。以下代码示例中,我们声明了BrowserModule、AuthenticationModule和AppRouting模块。 -
declarations:在declarations标签中,我们声明此根模块的所有组件。以下示例中,我们在AppModule中声明了AppComponent。 -
providers:在providers标签中,我们声明所有服务或管道。以下示例中,我们声明了AngularFireAuth和AngularFireDatabase。 -
bootstrap:在bootstrap标签中,我们声明根组件,这在index.html中是必需的。
以下示例app.module.ts是使用ng new命令创建的,如下所示:
import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {AppComponent} from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {
}
应用路由
根路由模块用于在不同的功能模块之间导航。我们将创建一个包含主应用程序导航流程的路由。以下示例包含路由的样本,展示了在应用程序中常见组件之间的导航,例如AboutComponent和PageNotFoundComponent。路径中的通配符'**'指定任何错误的 URL 都将重定向到页面未找到组件。应用路由使用@NgModule装饰器创建,并使用RouterModule配置路由。
以下示例app.routing.ts如下:
import {RouterModule, Routes} from '@angular/router';
import {NgModule} from '@angular/core';
import {PageNotFoundComponent} from './notfound/not-found.component';
import {AboutComponent} from './about/about.component';
export const ROUTES: Routes = [
{path: 'app-about', component: AboutComponent, pathMatch: 'full'},
{path: '**', component: PageNotFoundComponent},
];
@NgModule({
imports: [
RouterModule.forRoot(
ROUTES
)],
exports: [
RouterModule
]
})
export class AppRouting {
}
认证模块
authentication模块是一个功能模块,它包含登录和注册功能集。所有与认证相关的功能都包含在这个文件夹中。我们在声明元数据中包含了LoginComponent和SignupComponent。
import {NgModule} from '@angular/core';
import {AuthenticationRouting} from './authentication.routing';
import {LoginComponent} from './login/login.component';
import {SignupComponent} from './signup/signup.component';
import {FormsModule} from '@angular/forms';
import {AuthenticationService} from '../services/authentication.service';
/**
* Authentication Module
*/
@NgModule({
imports: [
FormsModule,
AuthenticationRouting
],
declarations: [
LoginComponent,
SignupComponent
],
providers: [
AuthenticationService
]
})
export class AuthenticationModule {
}
认证路由
此路由有助于在子组件内进行导航,因此我们为登录和注册组件创建路由。随着我们在后续章节中开始实现登录和注册组件,这些路由将逐步发展。
以下示例authentication.routing.ts如下:
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {LoginComponent} from './login/login.component';
import {SignupComponent} from './signup/signup.component';
export const ROUTES: Routes = [
{path: 'app-friends-login', component: LoginComponent},
{path: 'app-friends-signup', component: SignupComponent}
];
/**
* Authentication Routing Module
*/
@NgModule({
imports: [
RouterModule.forChild(ROUTES)
],
exports: [
RouterModule
]
})
export class AuthenticationRouting {
}
登录组件
组件是 Angular 应用程序中 UI(用户界面)的基本构建块。@Component装饰器用于将类标记为 Angular 组件。以下示例展示了使用@Component定义的登录组件,它包含SCSS(Sassy CSS)和模板文件:
import {Component} from '@angular/core';
@Component({
selector: 'friends-login',
styleUrls: ['./login.component.scss'],
templateUrl: './login.component.html'
})
export class LoginComponent {
}
登录组件 HTML
登录组件 HTML 是一个视图,包含登录表单数据。以下示例展示了包含两个标签的简单 HTML;Angular 中的模板遵循与 HTML 相同的语法:
<h3>Login Component</h3>
<p>This is the Login component!</p>
登录组件 scss
在我们的项目中,我们使用 SASS(语法优美的样式表),这是一个预处理器,将我们的 SCSS 文件转换为 CSS 文件。这有助于以简单和可读的格式组织我们的 CSS 元素。它提供了许多功能,如变量、嵌套、混合和继承,这些功能有助于以可重用的方式编写复杂的 CSS。我们将在创建功能模块时介绍这些功能。以下 SCSS 文件是一个简单的示例,其中黑色变量被外部化到变量 :black 中:此变量可以与其他 CSS 元素一起使用:
$black: #000;
h1.title {
color: $black;
}
登录组件规范
我们使用 Jasmine 测试框架来编写我们的单元测试用例。从开发初期开始编写测试用例是一个非常好的习惯。测试用例应该是我们开发过程的一部分。不幸的是,我们通常在生产后或在生产过程中看到大量问题时才编写测试用例。我们知道在开发初期编写测试用例需要时间,但它在后期可以节省大量时间。以前,软件行业交付新产品的基础是 市场时间,但现在焦点已经转移到 有质量的市场时间。因此,当编写测试用例成为开发的一部分时,我们就可以更快地编写测试用例,并按时交付高质量的产品。我们希望这解释了在开发过程中这一过程的重要性。
Chapter 10, *Unit Testing Our Application*:
describe('LoginComponent tests', () => {
});
当我们的测试用例准备就绪后,我们可以使用 npm 执行测试用例:
$npm test
认证服务
在 Angular 中,将 UI 逻辑与业务逻辑分离的最佳方式是使用服务。它提供了一个执行特定逻辑的类。在我们的项目中,我们通过服务中的 HTTP 与 Firebase 数据库交互,以便将重负载转移到服务上。在认证服务中,我们执行登录、注册和注销等操作。
当我们在一个定义良好的类中明确分离责任时,我们遵循 SRP(单一责任原则)。SRP 在下一节中将有更详细的解释。我们在服务上使用 Injectable 装饰器,以便 Angular 框架可以将此服务注入到任何组件或服务中,我们不需要创建对象。这种模式被称为依赖注入。
以下是一个 authentication.service.ts 的示例:
import { Injectable } from '@angular/core';
/**
* Authentication service
*
*/
@Injectable()
export class AuthenticationService {
}
日期管道
管道有助于在 Angular 应用程序中编写实用函数。
我们将通过扩展 PipeTransform 并重写超类中的 transform 方法来创建一个管道,以提供我们的功能。我们的应用程序的日期管道被装饰了管道元数据。我们将使用 name 标签为我们的管道提供一个名称,该标签用于在模板中转换数据。
以下是一个 friendsdate.pipe.ts 的示例:
import {Pipe, PipeTransform} from '@angular/core';
/**
* It is used to format the date
*/
@Pipe({
name: 'friendsdate'
})
export class FriendsDatePipe implements PipeTransform {
transform(dateInMillis: string) {
}
}
以下是在模板中使用管道的方式:
<div>{{<timeInMillis>: friendsdate}} </div>
常见原则
原则帮助我们以更好的方式设计我们的应用程序,并且这些原则并不特定于任何编程语言或平台。在本节中,我们将介绍 Angular 应用程序中最常用的几个原则:
-
单一职责原则:此原则指出,一个类应该只有一个改变的理由,并且责任应该封装在类中。我们将此原则应用于所有我们的组件、服务或路由器。这有助于代码的可维护性和可测试性。例如,组件负责管理与视图相关的逻辑。其他复杂的服务器逻辑委托给服务。我们主要在管道中实现实用函数。
-
单文件:将组件或服务定义在每个文件中是一种良好的实践。这使得我们的代码更容易阅读、维护和跨模块导出。
-
小函数:始终编写具有明确目的的小函数是很好的。这确实使使用您代码的其他开发者的生活变得更简单,因为新开发者可以轻松阅读和理解代码。
-
LIFT 原则:始终遵循提升原则,以便您可以快速定位代码,识别代码,保持结构扁平,并尝试保持简洁。这确保了应用程序中结构的一致性。这极大地提高了开发者的效率。
-
按功能划分文件夹:在应用程序的开发过程中,我们遵循了这一原则,并将所有功能分组到功能模块中。
-
共享功能:我们创建一个共享文件夹来声明所有可重用组件、指令和管道,并且这些在其他文件夹之间共享。创建
SharedModules以便在整个应用程序中引用。
编程标准指南
编程标准是我们编写应用程序时遵循的共同模式。这使得我们的代码在应用程序中保持一致。当多个开发者共同开发同一应用程序时,这非常有用。以下是一些编程标准:
-
命名约定:对于我们的文件名、类名等,保持一致的命名约定非常重要。从可维护性和可读性的角度来看,这是必不可少的。这也有助于更快地理解代码,并使调试变得更容易。
-
文件名:在 Angular 中,建议的文件名模式为 feature.type.ts。例如,认证组件文件的名称为
authentication.component.ts。 -
类名:类名遵循驼峰命名法,并从文件名中获取名称。例如,认证组件文件的类名为
AuthenticationComponent。 -
方法和属性名称:我们遵循驼峰命名法来命名类方法和属性。我们不使用下划线作为私有变量的前缀。
-
选择器名称:组件中的选择器名称是连字符分隔的,并且所有字符都是小写。我们的登录组件的名称如下所示:
selector: 'app-friends-login'管道名称:管道以其功能命名。例如,货币管道的名称是
'currency',如下所示:name: 'currency'
-
-
常量:常量是使用 const 关键字声明的最终变量,其值只赋值一次。常量变量以大写形式声明,如下所示:
export const USER_DETAILS_CHILD = 'user-details'; -
成员序列:所有成员变量首先声明,然后是方法。私有成员变量以公共成员变量命名。
摘要
在本章中,我们使用 Angular CLI 命令创建了我们的第一个 Angular 应用程序。我们浏览了 Angular 应用程序中的重要文件。我们学习了常见的 Angular 术语,然后了解了我们的应用程序项目结构和内部结构。最后,我们涵盖了常见的原则和编码指南,这些我们将作为我们开发过程的一部分来遵循。
在下一章中,我们将创建我们的第一个注册页面。我们将应用本章中的许多概念来创建我们的注册组件。
第二章:创建注册组件
在本章中,我们将开始我们的应用程序开发之旅。我们将构建一个注册页面。在这个过程中,我们还将探索 Firebase 的不同功能。我们将查看如何在控制台中启用 Firebase 身份验证,这是与身份验证功能模块交互所必需的。Firebase 支持许多身份验证机制,但在这个项目中,我们将启用电子邮件/密码身份验证。然后,我们将继续构建注册表单模板。我们将在注册组件中添加一个功能。在这个过程中,我们还将处理错误场景。这将使我们的应用程序更加健壮,减少错误的可能性。
在本章中,我们将涵盖以下主题:
-
在 Firebase 中启用身份验证
-
AngularFire2 简介
-
创建身份验证模块
-
创建服务
-
定义领域模型
-
创建注册模板
-
错误处理
-
创建自定义警报对话框
-
创建注册组件
在 Firebase 中启用身份验证
在我们的应用程序开发中,第一步是在 Firebase 门户中启用身份验证。我们已经在上一章中创建了我们的 Firebase 项目。按照以下步骤启用 Firebase 身份验证:
-
在 Firebase 控制台中打开你朋友的工程。
-
在左侧面板上展开 “开发”。
-
点击 “身份验证”。
-
点击右侧面板上的 “登录方法” 选项卡。
-
点击 “电子邮件/密码” 项并启用它。
Firebase 中启用的电子邮件/密码身份验证将如下截图所示:
这就是我们启用 “电子邮件/密码” 身份验证模式所需做的所有事情。Firebase 还支持其他身份验证模式。在这本书中,我们将仅实现电子邮件/密码身份验证。你可以作为练习与其他身份验证一起工作。
针对不同用户集需要不同的身份验证模式。了解其他身份验证模式是很好的。其他支持的模式如下:
-
电话:这是一种简单的身份验证形式,不需要从用户那里获取太多信息。只需用户的手机号码就足以进行用户身份验证。这种模式在移动应用程序中变得很受欢迎。
-
Google:启用 Google 身份验证是很好的,因为大多数目标用户都有 Google 账户。在这种模式下,身份验证是通过 Google 凭据进行的。
-
Facebook:这与 Google 身份验证类似。唯一的不同之处在于,身份验证是通过 Facebook 凭据而不是 Google 凭据进行的。
-
Twitter:这与前面的身份验证类似,但身份验证是通过你的 Twitter 账户进行的。
-
GitHub:这对开发者社区非常有帮助。他们有 GitHub 账户,你不需要提供任何个人账户信息。
-
匿名:这种认证对于用户不想注册的应用程序很有用。这主要用在电子商务应用程序上,用户可以使用这种认证浏览产品。
在所有前面的认证中,Firebase 生成一个唯一的 ID,称为用户 UID,用户使用 UID、标识符等在 Firebase 认证中注册。相同的 UID 也用于在 Firebase 数据库中注册用户,包含更多信息,如电子邮件、UID、姓名和电话号码。
介绍 angularfire2
angularfire2 是 Angular 和 Firebase 的官方库。我们将在我们的应用程序中使用这个库。这个库提供了以下功能:
-
它利用了 RxJS、Angular 和 Firebase 的力量
-
它提供了与 Firebase 数据库和认证交互的 API
-
它实时同步数据
-
它提供了与
AngularFirestore交互的 API
更多详情,请参阅 github.com/angular/angularfire2。
创建认证模块
在本节中,我们将使用 Angular CLI 命令创建我们的第一个模块。按照以下步骤创建认证模块:
- 导航到您的项目
src文件夹并执行模块 CLI 命令,如下所示:
$ cd <your directory>\friends\src\app
$ ng g module authentication --routing`
前面的命令创建了 authentication.module.ts 和 authentication.routing.ts 文件的骨架。您可以根据 Angular CLI 命令创建其他组件。
- 在应用程序模块中添加认证模块,并配置 Angular 和 Firebase 相关组件,如下所示;查看
app.module.ts文件:
import {NgModule} from '@angular/core';
import {AppComponent} from './app.component';
import {FormsModule} from '@angular/forms';
import {BrowserModule} from '@angular/platform-browser';
import {AuthenticationModule} from './authentication/authentication.module';
import {AngularFireModule} from 'angularfire2';
import {environment} from './environments/environment';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {AngularFireAuth} from 'angularfire2/auth';
import {AngularFireDatabase} from 'angularfire2/database';
import {CommonModule} from '@angular/common';
import {RouterModule} from '@angular/router';
@NgModule({
declarations: [
AppComponent
],
imports: [
CommonModule,
BrowserModule,
FormsModule,
AngularFireModule.initializeApp(environment.firebase),
BrowserAnimationsModule,
RouterModule.forRoot([]),
AuthenticationModule
],
providers: [
AngularFireAuth,
AngularFireDatabase,
],
bootstrap: [AppComponent]
})
export class AppModule {
}
- 在应用程序组件模板中添加占位符,如下所示的
app.component.html文件:
<router-outlet></router-outlet>
创建服务
Angular 应用程序中的服务包含核心业务逻辑。作为注册组件的一部分,我们创建了两个服务:
-
认证服务
-
用户服务
认证服务
我们在前一章中介绍了认证服务,所以我们将向认证服务中添加更多方法。在创建 Angular 服务时,请记住以下步骤。
- 编写服务:AngularFire2 有一个
AngularFireAuth类。这个类提供了对firebase.auth.Auth的访问,它有createUserWithEmailAndPasswordAPI 用于在 Firebase 中注册。
import {Injectable} from '@angular/core';
import {AngularFireAuth} from 'angularfire2/auth';
/**
* Authentication service
*
*/
@Injectable()
export class AuthenticationService {
/**
* Constructor
*
* @param {AngularFireAuth} angularFireAuth provides the
functionality related to authentication
*/
constructor(private angularFireAuth: AngularFireAuth) {
}
public signup(email: string, password: string): Promise<any> {
return
this.angularFireAuth.auth.createUserWithEmailAndPassword(email,
password);
}
}
- 注册服务:在使用 API 之前,我们需要在认证模块中包含此服务。查看以下详细信息。
providers tag to include the AuthenticationService.
@NgModule({
imports: [
...
],
declarations: [
...
],
providers: [
AuthenticationService
]
})
...
- 注入和使用服务:一旦服务注册,我们将在构造函数中声明它。实例将由 Angular 框架注入。最后,注册组件使用
AuthenticationService的signup()API 对 Firebase 进行用户认证。
以下示例显示了在注册组件中声明 AuthenticationService:
constructor(private authService: AuthenticationService) {}
用户服务
除了将新用户注册到 Firebase 身份验证之外,我们还需要在 Firebase 数据库中存储更多关于用户的信息,例如手机、电子邮件等。
用户服务用于在 Firebase 数据库中输入用户信息。我们使用angularfire2中的AngularFireDatabase将用户信息设置到 Firebase 数据库中。此类在UserService类的构造函数中注入:
constructor(private fireDb: AngularFireDatabase) {}
AngularFireDatabase提供了一个对象方法,它接受 Firebase 数据库中数据的路径;这返回AngularFireObject以将数据设置到 Firebase:
public addUser(user: User): void {
this.fireDb.object(`${USERS_CHILD}/${user.uid}`).set(user);
}
到目前为止,完整的user.service.ts文件如下所示:
import {Injectable} from '@angular/core';
import {AngularFireDatabase} from 'angularfire2/database';
import {User} from './user';
import 'firebase/storage';
import {USERS_CHILD} from './database-constants';
/**
* User service
*
*/
@Injectable()
export class UserService {
/**
* Constructor
*
* @param {AngularFireDatabase} fireDb provides the functionality for
Firebase Database
*/
constructor(private fireDb: AngularFireDatabase) {
}
public addUser(user: User): void {
this.fireDb.object(`${USERS_CHILD}/${user.uid}`).set(user);
}
}
如前所述,我们需要在authentication.module.ts中注册该服务:
@NgModule({
imports: [
...
],
declarations: [
...
],
providers: [
AuthenticationService,
UserService
]
})
定义域模型
对象模型包含有关我们应用程序关键域的信息。这有助于以更可读和结构化的方式存储我们的非结构化数据。在我们的应用程序中,我们将介绍许多对象模型来存储我们的域信息。
当我们注册新用户时,我们将用户详细信息存储在 Firebase 数据库中,并创建了一个具有与用户相关的属性的用户模型来存储用户数据。所有属性都声明为具有类型的成员变量,因为 TypeScript 支持变量的类型,如下面的user.ts所示:
export class User {
email: string;
name: string;
mobile: string;
uid: string;
friendcount: number;
image: string;
}
TypeScript 是 JavaScript 的类型版本,并编译成 JavaScript。在 TypeScript 中编程更容易、更快。您可以在www.typescriptlang.org/docs/home.html了解更多信息。
创建注册模板
注册模板表示网页视图。它提供了用于输入用户输入的表单元素。它还处理模板中的用户错误。在本节中,我们将介绍模板中使用的所有标签。模板中使用的错误处理将在下一节中介绍。
FormModule
我们使用FormModule中的<form>来创建注册模板。ngForm包含注册表单数据。这是检索用户填写数据所必需的。我们将此表单数据传递给注册组件中的onSignup()方法。此用户填写的数据可以通过例如signupFormData.value.email来检索,如下所示:
<form name="form" (ngSubmit)="onSignup(signupFormData)" #signupFormData='ngForm'></form>
Bootstrap 元素
我们使用 bootstrap 元素来设计我们的注册表单。我们在index.html文件中包含了 bootstrap 和其他依赖库,如 tether 和 jQuery,如下所示:
<!DOCTYPE html>
<html>
<head>
<meta charset=UTF-8>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="img/tether.min.js"></script>
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/
css/bootstrap.min.css">
<script src="img/
jquery.min.js"></script>
<script src="img/
js/bootstrap.min.js"></script>
<title>Friends - A Social App</title>
<base href="/">
</head>
<body>
<app-friends>
Loading...
</app-friends>
</body>
</html>
Bootstrap 是一个用于开发 HTML、CSS 和 JavaScript 的开源工具包。有关更多详细信息,请参阅getbootstrap.com/docs/4.0/getting-started/introduction/。
使用的元素如下:
- 网格样式:我们使用 bootstrap 网格样式将表单对齐到中间,如下所示:
<div class="col-md-6 col-md-offset-3"></div>
- 警告:此元素用于在用户对元素执行任何操作时提供上下文消息,如下所示:
<div class="alert alert-danger"></div>
更多关于 Bootstrap 的详细信息,请参阅getbootstrap.com/docs/4.0/components/alerts/。
Angular 数据绑定
如前一章所述,Angular 支持多种数据绑定方式。在这种情况下,我们将支持单向绑定使用 (ngModel)='name'。
现在看看完整的 signup.component.html 文件:
<div class="col-md-6 col-md-offset-3">
<h2>Signup</h2>
<app-error-alert *ngIf="showError" [errorMessage]="errorMessage">
</app-error-alert>
<form name="form" (ngSubmit)="onSignup(signupFormData)"
#signupFormData='ngForm'>
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" name="name"
(ngModel)="name" #name="ngModel" required id="name"/>
<div [hidden]="name.valid || name.pristine"
class="alert alert-danger">
Name is required
</div>
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="text" class="form-control" name="email"
(ngModel)="email" #email="ngModel"
required
pattern="^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*
(\.\w{2,3})+$"
id="email"/>
<div [hidden]="email.valid || email.pristine"
class="alert alert-danger">
<div [hidden]="!email.hasError('required')">Email is
required</div>
<div [hidden]="!email.hasError('pattern')">Email format
should be
<small><b>codingchum@gmail.com</b></small>
</div>
</div>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" class="form-control" name="password"
(ngModel)="password" #password="ngModel" required
id="password"/>
<div [hidden]="password.valid || password.pristine"
class="alert alert-danger">
Password is required
</div>
</div>
<div class="form-group">
<label for="name">Retype Password</label>
<input type="password" class="form-control"
id="confirmPassword"
required
passwordEqual="password"
(ngModel)="confirmPassword" name="confirmPassword"
#confirmPassword="ngModel">
<div [hidden]="confirmPassword.valid ||
confirmPassword.pristine"
class="alert alert-danger">
Passwords did not match
</div>
</div>
<div class="form-group">
<label for="mobile">Mobile</label>
<input type="text" class="form-control" name="mobile"
(ngModel)="mobile" #mobile="ngModel"
required
pattern="[0-9]*"
minlength="10"
maxlength="10"
id="mobile"/>
<div [hidden]="mobile.valid || mobile.pristine"
class="alert alert-danger">
<div [hidden]="!mobile.hasError('minlength')">Mobile
should be 10 digit</div>
<div [hidden]="!mobile.hasError('required')">Mobile is
required</div>
<div [hidden]="!mobile.hasError('pattern')">Mobile
number should be only numbers</div>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-success"
[disabled]="!signupFormData.form.valid">SIGNUP</button>
<a [routerLink]="['/app-friends-login']" class="btn btn-
link">CANCEL</a>
</div>
</form>
</div>
错误处理
错误处理是创建良好应用程序的重要步骤。它使我们的产品更加健壮和抗错误。
我们使用 Angular 验证器来验证用户输入的准确性和完整性。对于用户输入,我们可以使用一个常见的内置验证器或创建我们自己的自定义验证器。
请注意,这些验证器是内置在 HTML 中,而不是 Angular 本身。
以下是一些使用的内置验证器:
-
必需:这使得输入字段成为必填项。
-
最小长度:这定义了用户输入的下限。例如,我们可以将手机号码的最小长度限制为 10 位数字。
-
最大长度。这定义了用户输入的上限。例如,我们可以将手机号码的最大长度限制为 10 位数字。
-
模式:我们可以为用户输入创建一个模式。例如,手机号码只接受数字作为输入。
内置验证器可以在注册模板中使用,如下所示:
<div class="form-group">
<label for="mobile">Mobile</label>
<input type="text" class="form-control" name="mobile"
[(ngModel)]="model.mobile" #mobile="ngModel"
required
pattern="[0-9]*"
minlength="10"
maxlength="10"
id="mobile"/>
<div [hidden]="mobile.valid || mobile.pristine"
class="alert alert-danger">
<div [hidden]="!mobile.hasError('minlength')">Mobile should be
10 digit</div>
<div [hidden]="!mobile.hasError('required')">Mobile is
required</div>
<div [hidden]="!mobile.hasError('pattern')">Mobile number
should be only numbers</div>
</div>
</div>
自定义验证器是我们应用程序用例的定制验证器。在我们的应用程序中,我们将获取一个密码并让用户重新输入密码以进行确认,为了验证这两个密码,我们将创建一个自定义密码验证器。
要创建我们的验证器,我们将从我们的表单模块扩展 Validator,这提供了一个 validate 方法来编写我们的自定义实现:
import {Directive, forwardRef, Attribute} from '@angular/core';
import {Validator, AbstractControl, NG_VALIDATORS} from '@angular/forms';
@Directive({
selector: '[passwordEqual][formControlName],[passwordEqual]
[formControl],[passwordEqual][ngModel]',
providers: [
{provide: NG_VALIDATORS, useExisting: forwardRef(() =>
PasswordEqualValidator), multi: true}
]
})
export class PasswordEqualValidator implements Validator {
constructor(@Attribute('passwordEqual') public passwordEqual:
string) {
}
validate(control: AbstractControl): { [key: string]: any } {
let retypePassword = control.value;
let originalPassword = control.root.get(this.passwordEqual);
// original & retype password is egual
return (originalPassword && retypePassword !==
originalPassword.value)
? {passwordEqual: false} : null;
}
}
我们将验证器包含在我们的模块中,我们的authentication.module.ts看起来如下:
@NgModule({
imports: [
...
],
declarations: [
PasswordEqualValidator
],
providers: [
...
]
})
...
最后,我们在注册模板中使用PasswordEqualValidator和passwordEqual选择器来确认密码,如下面的代码所示:
<input type="password" class="form-control" id="confirmPassword"
required
passwordEqual="password"
[(ngModel)]="model.confirmPassword" name="confirmPassword"
#confirmPassword="ngModel">
Firebase 错误
一旦用户提供了正确的输入并点击了 SIGNUP 按钮,我们就可以调用 signup() 方法,并将用户导向其个人资料页面。如果任何用户输入有误,Firebase API 会将错误抛给应用程序,我们需要在我们的应用程序中处理它。
createUserWithEmailAndPassword of AngularFireAuth 会抛出以下错误:
-
auth/email-already-in-use:当用户在注册时提供了一个已使用的电子邮件地址时,会抛出此错误。
-
auth/invalid-email:当电子邮件地址无效时,会抛出此错误。
-
auth/operation-not-allowed:当我们为用户创建注册时,如果未启用 Firebase 身份验证,则会抛出此错误。大多数情况下,这发生在开发期间。
-
auth/weak-password:当提供的密码较弱时,会抛出此错误。
当我们调用注册 API 时,auth 返回 Promise<any>。这个类有 then 和 catch 方法用于成功和失败场景。在 catch 块中,我们读取错误信息并在警报对话框中显示错误。Firebase 错误信息有一个可读的错误消息,所以我们不需要将错误代码与消息映射。我们还可以创建自定义的警报对话框并显示错误消息。在下一节中,我们将创建自定义错误警报并将其集成到注册模板中。
onSignup(signupFormData): void { this.authService.signup(signupFormData.value.email, signupFormData.value.password).then((userInfo) => {
...
}).catch((error) => {
this.showError = true;
this.errorMessage = error.message;
});
}
创建自定义警报对话框
在本节中,我们将创建一个警报对话框组件。这个组件用于显示错误消息。它是一个可重用的组件,这意味着它可以在应用程序的任何地方使用。我们需要遵循以下步骤来创建和配置我们独立的警报对话框:
- 创建组件:这与创建任何其他组件相同。我们提供了
@Input errorMessage:any绑定以从其他集成组件接收错误消息。此消息将在注册页面上显示。
这里是完整的 error-alert.component.ts:
import {Component, Input} from '@angular/core';
@Component({
selector: 'app-error-alert',
templateUrl: './error-alert.component.html',
styleUrls: ['./error-alert.component.scss']
})
export class ErrorAlertComponent {
@Input() errorMessage: any;
}
- 创建模板:我们使用了来自 bootstrap 组件的警报。花括号中的
errorMessage变量接受文本并在注册页面上显示错误消息。
这里是完整的 error-alert.component.html:
<div class="error-alert-container">
<div class="alert alert-danger fade show" role="alert">
{{errorMessage}}
</div>
</div>
- 创建样式表:我们设置了
top和bottom边距以正确对齐警报。
这里是完整的 error-alert.component.scss:
.error-alert-container {
margin-top: 24px;
margin-bottom: 8px;
}
- 在注册模板中配置:一旦我们的错误警报准备就绪,我们就可以将其与其他组件集成。在本章中,我们将错误警报与我们的注册组件集成。这包含
*ngIf指令以启用错误警报,并将错误消息绑定到显示来自注册组件的文本。
这里是包含错误警报的修改后的 signup.component.html 文件,如下所示:
<div class="col-md-6 col-md-offset-3">
<h2>Signup</h2>
<app-error-alert *ngIf="showError" [errorMessage]="errorMessage">
</app-error-alert>
...
</div>
- 分配错误消息:我们创建了两个成员变量:
errorMessage和showError。这些变量在onSignup()方法中启用,当发生错误时。errorMessage从error.message分配。
这里是包含错误信息的完整 signup.component.ts:
export class SignupComponent {
errorMessage: string;
showError: boolean;
onSignup(signupFormData): void {
this.authService.signup(signupFormData.value.email,
signupFormData.value.password).then((userInfo) => {
// Register the new user
...
}).catch((error) => {
this.showError = true;
this.errorMessage = error.message;
});
}
}
在新用户注册过程中,如果您仅输入一个字符的密码,注册页面中的错误如下所示:
创建注册组件
注册组件是一个控制器,它用于响应用户的操作,例如注册或取消。它注入以下两个服务:
-
身份验证服务:它提供与身份验证相关的功能,例如登录、注册和注销
-
用户服务:它与 Firebase 数据库交互以存储额外的用户信息,例如手机号码和姓名。
注册组件的构造函数接受身份验证和用户服务,如下所示:
constructor(private authService: AuthenticationService, private userService: UserService) {
}
当用户点击“注册”按钮时,将调用onSignup方法。它接受表单数据作为参数,其中包含用户输入的信息。将检索电子邮件和密码,并将它们传递给身份验证服务的signup方法。在成功注册后,我们将从表单数据中检索其他信息,并将其存储在用户域模型中。最后,我们将这个新创建的User对象传递给用户服务,并在 Firebase 数据库中注册:
在成功注册后,用户信息包含 UID。这是特定用户的唯一标识符。它用作在 Firebase 数据库中存储数据的指示器。这也用于从数据库中检索用户信息。
onSignup(signupFormData): void {
this.authService.signup(signupFormData.value.email,
signupFormData.value.password).then((userInfo) => {
// Register the new user
const user: User = new User(signupFormData.value.email,
signupFormData.value.name, signupFormData.value.mobile,
userInfo.uid, 0, '');
this.writeNewUser(user);
}).catch((error) => {
this.showError = true;
this.errorMessage = error.message;
});
}
private writeNewUser(user: User): void {
this.userService.addUser(user);
}
在成功注册后,Firebase 身份验证将具有以下条目:
Firebase 数据库将具有以下条目:
这是到目前为止完整的signup.component.ts文件:
import {Component} from '@angular/core';
import {User} from '../../services/user';
import {AuthenticationService} from '../../services/authentication.service';
import {UserService} from '../../services/user.service';
@Component({
selector: 'app-friends-signup',
styleUrls: ['signup.component.scss'],
templateUrl: 'signup.component.html'
})
export class SignupComponent {
errorMessage: string;
showError: boolean;
constructor(private authService: AuthenticationService,
private userService: UserService) {
}
onSignup(signupFormData): void {
this.authService.signup(signupFormData.value.email,
signupFormData.value.password).then((userInfo) => {
// Register the new user
const user: User = new User(signupFormData.value.email,
signupFormData.value.name, signupFormData.value.mobile,
userInfo.uid, 0, '');
this.writeNewUser(user);
}).catch((error) => {
this.showError = true;
this.errorMessage = error.message;
});
}
private writeNewUser(user: User): void {
this.userService.addUser(user);
}
最后,按照以下方式在身份验证路由模块中注册signupComponent:
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {SignupComponent} from './signup/signup.component';
export const ROUTES: Routes = [
{path: 'app-friends-signup', component: SignupComponent}
];
/**
* Authentication Routing Module
*/
@NgModule({
imports: [RouterModule.forChild(ROUTES)],
exports: [RouterModule]
})
export class AuthenticationRouting {
}
现在,使用http://localhost:4200/app-friends-signup在浏览器中查看您的组件,组件如下所示:
摘要
最后,我们构建我们的注册组件。本章的关键学习点是可注入的服务。我们创建了身份验证和用户服务。这些服务使用 Angular fire 库与 Firebase 身份验证和数据库服务进行交互。这些新服务通过依赖注入添加到注册组件中。该组件使用这些服务注册新用户并将他们添加到 Firebase 数据库中。我们内置并自定义了消息警报。
在下一章中,我们将创建一个登录组件并登录新注册的用户账户。在成功登录后,用户将被导向用户个人资料页面。
第三章:创建登录组件
在本章中,我们将构建我们的第二个组件。我们将创建一个登录页面,该页面将与注册页面类似。我们还将向服务中添加更多功能。我们将使用电子邮件/密码身份验证来登录用户。用户详情已在注册过程中添加到 Firebase 数据库中。我们将从 Firebase 检索用户详情并将它们传递到用户个人资料页面。我们还将处理常见的错误场景,因为这将加强这一概念。
本章将涵盖以下主题:
-
将登录功能添加到现有服务中
-
重复使用域模型
-
创建登录模板
-
登录错误处理
-
创建登录组件
-
重置密码
将登录功能添加到现有服务中
在上一章中,我们使用了电子邮件/密码身份验证并将我们的用户添加到 Firebase 数据库中。我们获得了将数据推送到 Firebase 的基本知识。在本节中,我们将登录用户并从 Firebase 检索用户详情。我们将在身份验证和用户服务中添加登录功能。
身份验证服务
在注册过程中,用户注册到 Firebase。AngularFireAuth有signInWithEmailAndPassword方法来登录用户。此方法返回firebase.Promise<any>。此类有then和catch方法来处理成功和失败场景。成功时,我们将用户重定向到用户个人资料页面,并在失败时显示错误消息。
考虑以下方法:
- 登录:此方法验证用户并在登录成功时传递用户信息,如下所示:
public login(email: string, password: string): Promise<any> {
return this.angularFireAuth.auth.signInWithEmailAndPassword(email, password);
}
- 重置:
AngularFireAuth提供了一个重置密码的 API。Firebase 提供了密码重置的基础设施,例如密码电子邮件通知。我们只需在身份验证服务中调用resetPasswordAPI,如下所示:
public resetPassword(email: string): Promise<any> {
return this.angularFireAuth.auth.sendPasswordResetEmail(email);
}
现在这是完整的authentication.service.ts:
import {Injectable} from '@angular/core';
import {AngularFireAuth} from 'angularfire2/auth';
/**
* Authentication service
*
*/
@Injectable()
export class AuthenticationService {
/**
* Constructor
*
* @param {AngularFireAuth} angularFireAuth provides the
functionality related to authentication
*/
constructor(private angularFireAuth: AngularFireAuth) {
}
public signup(email: string, password: string): Promise<any> {
return
this.angularFireAuth.auth.createUserWithEmailAndPassword(email,
password);
}
public login(email: string, password: string): Promise<any> {
return this.angularFireAuth.auth.signInWithEmailAndPassword(email,
password);
}
public resetPassword(email: string): Promise<any> {
return this.angularFireAuth.auth.sendPasswordResetEmail(email);
}
}
用户服务
在这里,我们介绍如何通过执行读取操作从 Firebase 检索值。AngularFire2有AngularFireDatabase类,该类提供了以下两种方法从 Firebase 读取数据:
-
object:此方法检索 JSON 对象。它返回AngularFireObject<T>,该对象提供了valueChanges方法以返回 Observable。例如,如果我们想从 Firebase 获取用户对象,则使用此方法。 -
list:此方法检索 JSON 对象的数组。它返回AngularFireList<T>,该对象提供了valueChanges方法以返回包含对象的 Observable。例如,如果我们想获取在我们应用程序中注册的所有用户,则此方法非常有用。
一旦用户输入正确的凭据,我们就使用AngularFireDatabase的object方法检索用户详情。
AngularFireDatabase类中的方法列表如下:
export declare class AngularFireDatabase {
app: FirebaseApp;
database: database.Database;
constructor(app: FirebaseApp);
list<T>(pathOrRef: PathReference, queryFn?: QueryFn):
AngularFireList<T>;
object<T>(pathOrRef: PathReference): AngularFireObject<T>;
createPushId(): string | null;
}
在我们的应用程序中,我们主要使用AngularFireDatabase的list和object方法。这些方法接受pathOrRef参数。list方法接受一个额外的QueryFn参数。这些参数的目的是如下:
pathOrRef:此参数接受 Firebase 数据库中数据的路径。如下例所示,要访问用户数据,我们提供直到用户uid的路径。
public getUser(uid: string): Observable<User> {
return this.fireDb.object<User>(`${USERS_CHILD}/${uid}`).valueChanges();
}
例如,如果我们想检索用户 ID 为qu3bXn9tTJR7j4PBp9LzBGKxHAe2的用户信息,那么在这种情况下路径是/users/qu3bXn9tTJR7j4PBp9LzBGKxHAe2:
QueryFn:这是list方法中的可选参数,根据过滤条件过滤列表。例如,如果我们想获取前三个注册的用户,那么我们使用limitToFirst查询。
现在是完整的user.service.ts:
import {Injectable} from '@angular/core';
import {AngularFireDatabase} from 'angularfire2/database';
import {User} from './user';
import 'firebase/storage';
import {USERS_CHILD} from './database-constants';
import {Observable} from 'rxjs/Observable';
/**
* User service
*
*/
@Injectable()
export class UserService {
/**
* Constructor
*
* @param {AngularFireDatabase} fireDb provides the functionality for
Firebase Database
*/
constructor(private fireDb: AngularFireDatabase) {
}
public addUser(user: User): void {
this.fireDb.object(`${USERS_CHILD}/${user.uid}`).set(user);
}
public getUser(uid: string): Observable<User> {
return this.fireDb.object<User>
(`${USERS_CHILD}/${uid}`).valueChanges();
}
}
重新使用域模型
一旦用户成功登录,我们将从我们的 Firebase 数据库中检索用户对象。在成功登录后,我们获取用户的字符串uid,并使用这个uid从 Firebase 数据库中用户节点检索用户详细信息。正如前文所述,我们以以下 JSON 格式获取数据:
{
"email": "uttamagarwal.slg@gmail.com",
"mobile": "9972022242",
"name": "Uttam Agarwal",
"uid": "qu3bXn9tTJR7j4PBp9LzBGKxHAe2"
}
此 JSON 对象需要映射到用户对象。当我们使用AngularFireDatabase从 Firebase 检索 JSON 对象时,我们在尖括号中提供类型对象<User>,这会将 JSON 映射到用户对象:
public getUser(uid: string): Observable<User> {
return this.fireDb.object<User>(`${USERS_CHILD}/${uid}`).valueChanges();
}
构造函数接受分配给其成员变量的所有参数,如下所示;以下是完整的user.ts:
export class User {
email: string;
name: string;
mobile: string;
uid: string;
constructor(email: string,
name: string,
mobile: string,
uid: string) {
this.email = email;
this.name = name;
this.mobile = mobile;
this.uid = uid;
}
}
创建登录模板
登录模板是视图,这部分与注册模板相似。我们重用了注册中的电子邮件和密码元素。它包含以下三个部分:
-
输入表单:这是一个文本框,接受用户输入的值
-
提交操作:它触发带有登录表单数据的
onLogin()方法到组件 -
导航:这部分将在第四章中详细说明,即组件之间的路由和导航,因此在这里我不会涉及这部分内容。
以下为完整的login.component.html:
<div class="col-md-6 col-md-offset-3 container">
<h2>Login</h2>
<app-error-alert *ngIf="showError" [errorMessage]="errorMessage"></app-error-alert>
<form name="form" (ngSubmit)="onLogin(loginFormData)"
#loginFormData='ngForm'>
<div class="form-group">
<label for="email">Email</label>
<input type="text" class="form-control" name="email"
(ngModel)="email" #email="ngModel"
required
pattern="^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*
(\.\w{2,3})+$"
id="email"/>
<div [hidden]="email.valid || email.pristine"
class="alert alert-danger">
<div [hidden]="!email.hasError('required')">Email is
required</div>
<div [hidden]="!email.hasError('pattern')">Email format
should be
<small><b>codingchum@gmail.com</b></small>
</div>
</div>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" class="form-control" name="password"
(ngModel)="password" #password="ngModel"
required id="password"/>
<div [hidden]="password.valid || password.pristine"
class="alert alert-danger">
Password is required
</div>
</div>
<div class="form-group">
<button type="submit" id="login-btn" class="btn btn-
success" [disabled]="!loginFormData.form.valid">
LOGIN</button>
<button routerLink="/app-friends-signup" data-tag="signup-
tag" routerLinkActive="active"
class="btn btn-success">
SIGNUP
</button>
</div>
</form>
</div>
登录错误处理
如前一章所述,我们将处理用户输入和服务器错误。在登录组件中,用户输入错误与注册组件中的相同。在这个组件中,我们使用了相同的内置 Angular 错误。此错误消息有助于用户输入正确信息。
Firebase 错误
当用户登录到朋友的应用程序时,登录 Firebase API 会抛出错误。我们使用AngularFireAuth的signInWithEmailAndPassword()方法来注册用户。此 API 会抛出以下错误:
-
auth/invalid-email:正如其名所示,当用户提供一个无效的电子邮件地址时,会发生此错误。 -
auth/user-disabled:当注册用户账户在 Firebase 中被禁用时,将发生此错误。当注册用户不遵守应用程序的条款和条件时,需要此功能。然后,我们可以向用户显示有意义的消息。
您可以通过以下三个步骤禁用用户账户:
-
前往 Firebase。
-
在左侧面板转到 DEVELOP|认证。
-
在右侧面板上突出显示用户。点击溢出图标,然后选择禁用账户选项。
查看以下 Firebase 中的禁用账户选项:
-
auth/user-not-found:当用户未在我们的应用程序中注册时,将发生此错误。在这种情况下,我们可以将用户直接导向注册页面。 -
auth/wrong-password:当密码不正确时,将发生此错误。在这种情况下,用户有两个选择:提供正确的密码或重置密码。
认证服务中的登录方法返回Promise<any>。我们在catch块中处理错误。
Promise 是任何异步操作的结果。在成功或失败操作之后,我们使用 Promise 对象检索存储的数据。
然后,我们将重用第二章中创建的“创建注册组件”错误警报,并显示错误:
onLogin(loginFormData): void {
this.authService.login(loginFormData.value.email,
loginFormData.value.password).then((userInfo) => {
...
}).catch((error) => {
this.errorMessage = error.message;
this.showError = true;
});
}
当用户输入错误密码时,将显示以下自定义错误消息:
创建登录组件
登录组件处理用户与 UI 的交互。它注入了三个服务以执行各种操作:
-
认证服务:它提供登录 API 以供注册用户登录。
-
用户服务:它提供了一种从 Firebase 数据库中检索用户详细信息的方法。
-
路由器:此服务用于在应用程序的不同页面之间进行路由。在登录组件中,我们将使用此服务将用户路由到注册页面。这将在下一章中详细介绍。
服务被注入到登录组件的constructor中,如下所示:
constructor(
private userService: UserService,
private router: Router,
private authService: AuthenticationService
) {}
登录组件也处理用户点击事件。当用户点击登录按钮时,将调用onLogin方法。此方法接受登录表单数据作为参数,包含用户输入的数据。我们通过loginFormData.value.email和loginFormData.value.password检索电子邮件和密码。这些数据被传递给认证服务进行登录。登录成功后,我们获取用户的uid,然后使用此uid从我们的 Firebase 数据库中检索用户详细信息。我们还在用户服务中缓存这些用户详细信息,以便在其他页面中将来参考:
onLogin(loginFormData): void {
this.authService.login(loginFormData.value.email,
loginFormData.value.password).then((userInfo) => {
// Login user
const uid: string = userInfo.uid;
this.getUserInfo(uid);
}).catch((error) => {
...
});
}
private getUserInfo(uid: string) {
this.userService.getUser(uid).subscribe(snapshot => {
this.user = snapshot;
});
}
最后,我们看到了如何以注册用户身份登录。现在,唯一缺少的部分是密码恢复,我们将在下一节中介绍。
现在的完整login.component.ts如下所示:
import {Component} from '@angular/core';
import {User} from '../../services/user';
import {Router} from '@angular/router';
import {AuthenticationService} from '../../services/authentication.service';
import {UserService} from '../../services/user.service';
import {AngularFireAuth} from 'angularfire2/auth';
@Component({
selector: 'app-friends-login',
styleUrls: ['login.component.scss'],
templateUrl: 'login.component.html',
})
export class LoginComponent {
errorMessage: string;
showError: boolean;
private user: User;
constructor(private userService: UserService,
private router: Router,
private authService: AuthenticationService,
private angularFireAuth: AngularFireAuth) {
this.angularFireAuth.auth.onAuthStateChanged(user => {
if (user) {
this.getUserInfo(user.uid);
}
});
}
onLogin(loginFormData): void {
this.authService.login(loginFormData.value.email,
loginFormData.value.password).then((user) => {
// Login user
const uid: string = user.uid;
this.getUserInfo(uid);
}).catch((error) => {
this.errorMessage = error.message;
this.showError = true;
});
}
private getUserInfo(uid: string) {
this.userService.getUser(uid).subscribe(snapshot => {
this.user = snapshot;
});
}
}
最后,我们在认证路由模块中注册LoginComponent:
{path: 'app-friends-login', component: LoginComponent}
因此,将 URL http://localhost:4200/app-friends-login 粘贴到浏览器中,我们的登录组件看起来如下:
重置密码
在我们的应用程序中提供密码恢复选项是个好主意,这个过程提高了我们应用程序的可用性。令人兴奋的是,Firebase 提供了执行此操作所需的所有基础设施。我们将逐步在我们的应用程序中添加此功能。
添加模态模板
密码重置动作的第一步是获取用户电子邮件地址,我们将在此场景中使用模态框。模态框是一个出现在当前页面视图之上的弹出窗口/对话框。我们将使用模态框来显示弹出窗口以获取用户的电子邮件地址。
在登录 html 中添加模态模板:我们已经修改了 login.component.html 文件以添加重置密码按钮,如下面的代码所示:
<div id="password_reset" class="modal fade" role="dialog">
<div class="modal-dialog modal-sm">
<form name="form" (ngSubmit)="onReset(resetFormData)"
#resetFormData='ngForm'>
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Forgot Password?</h4>
<button type="button" class="close" data-
dismiss="modal">×</button>
</div>
<div class="modal-body">
<p>Please enter your registered email to sent you
the password reset instructions.</p>
<div class="form-group">
<label for="reset_email">Email</label>
<input type="text" class="form-control"
name="email" (ngModel)="email"
#email="ngModel"
required
pattern="^\w+([\.-]?\w+)*@\w+([\.-]?
\w+)*(\.\w{2,3})+$"
id="reset_email"/>
</div>
</div>
<div class="modal-footer form-group">
<button type="submit" class="btn btn-default"
[disabled]="!resetFormData.form.valid"
>Reset
</button>
<button type="button" class="btn btn-default" data-
dismiss="modal">Close</button>
</div>
</div>
</form>
</div>
</div>
当用户点击重置密码时,以下模态框会出现:
到目前为止,这是完整的 login.component.html:
<div class="col-md-6 col-md-offset-3 container">
<h2>Login</h2>
<app-error-alert *ngIf="showError" [errorMessage]="errorMessage"></app-error-alert>
<form name="form" (ngSubmit)="onLogin(loginFormData)"
#loginFormData='ngForm'>
<div class="form-group">
<label for="email">Email</label>
<input type="text" class="form-control" name="email"
(ngModel)="email" #email="ngModel"
required
pattern="^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*
(\.\w{2,3})+$"
id="email"/>
<div [hidden]="email.valid || email.pristine"
class="alert alert-danger">
<div [hidden]="!email.hasError('required')">Email is
required</div>
<div [hidden]="!email.hasError('pattern')">Email format
should be
<small><b>codingchum@gmail.com</b></small>
</div>
</div>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" class="form-control" name="password"
(ngModel)="password" #password="ngModel"
required id="password"/>
<div [hidden]="password.valid || password.pristine"
class="alert alert-danger">
Password is required
</div>
</div>
<div class="form-group">
<button type="submit" id="login-btn" class="btn btn-
success" [disabled]="!loginFormData.form.valid">
LOGIN</button>
<button routerLink="/app-friends-signup" data-tag="signup-
tag" routerLinkActive="active" class="btn btn-success">
SIGNUP
</button>
</div>
</form>
<button type="button" data-tag="password-reset-tag" class="btn btn-
info" data-toggle="modal" data-target="#password_reset">
Reset Password
</button>
</div>
<div id="password_reset" class="modal fade" role="dialog">
<div class="modal-dialog modal-sm">
<form name="form" (ngSubmit)="onReset(resetFormData)"
#resetFormData='ngForm'>
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Forgot Password?</h4>
<button type="button" class="close" data-
dismiss="modal">×</button>
</div>
<div class="modal-body">
<p>Please enter your registered email to sent you
the password reset instructions.</p>
<div class="form-group">
<label for="reset_email">Email</label>
<input type="text" class="form-control"
name="email" (ngModel)="email"
#email="ngModel"
required
pattern="^\w+([\.-]?\w+)*@\w+([\.-]?
\w+)*(\.\w{2,3})+$"
id="reset_email"/>
</div>
</div>
<div class="modal-footer form-group">
<button type="submit" class="btn btn-default"
[disabled]="!resetFormData.form.valid"
>Reset
</button>
<button type="button" class="btn btn-default" data-
dismiss="modal">Close</button>
</div>
</div>
</form>
</div>
</div>
添加 onReset() 功能
下一步是在登录组件中添加重置方法。登录组件中的 onReset() 方法重置密码并将重置指令发送到注册的电子邮件。此电子邮件包含重置链接;当我们点击链接时,它会在另一个浏览器标签页中打开一个警报对话框以提供新密码。
我们在登录组件中添加了 onReset() 方法,如下所示:
onReset(resetFormData): void {
this.authService.resetPassword(resetFormData.value.email).then(() =>
{
alert('Reset instruction sent to your mail');
}).catch((error) => {
this.errorMessage = error.message;
this.showError = true;
});
}
在 Firebase 中编辑密码重置模板
Firebase 提供了更改电子邮件模板的选项。我们可以自定义电子邮件正文。在此应用程序中,我们使用默认模板,尽管您可以通过此过程进行更改:
-
前往 Firebase 身份验证
-
在右侧面板上,点击 TEMPLATES 选项卡
-
点击左侧面板上的密码重置选项
-
点击铅笔图标进行编辑
考虑以下 Firebase 中的密码重置模板:
摘要
在本章中,我们实现了登录组件并增强了身份验证和用户服务。登录模板和组件看起来与登录模板和组件相似。然后我们在应用程序中实现了密码重置功能。Firebase 为实现重置密码功能提供了必要的元素。我们还实现了我们应用程序中的第一个模态框。
在下一章中,我们将介绍不同组件之间的导航流程。我们还将添加 Angular 守卫以根据守卫条件限制或启用对组件的导航。
第四章:组件之间的路由和导航
在本章中,我们将涵盖 Angular 应用程序中的导航。我们将为我们的应用程序组件和认证实现路由器。我们还将涵盖组件视图的路由出口,并为认证创建一个路由模块。这将使我们的认证功能完全独立于其他模块。认证模块中的组件导航将由子路由模块处理。我们还将讨论 Angular 守卫,它将根据组件的条件限制导航。这增强了我们应用程序的安全性。最后,我们将深入探讨 Firebase 会话生命周期,并将用户重定向到登录或我的个人资料页面。
在本章中,我们将涵盖以下主题:
-
在应用程序组件中启用路由
-
为认证创建一个路由模块
-
添加路由链接
-
使用认证守卫
-
Firebase 会话生命周期
-
到目前为止,我们的项目结构如下:
在应用程序组件中启用路由
在本节中,我们启用路由并创建应用程序的主要导航栏。启用路由的步骤如下:
- 添加基本参考 URL:我们需要在
index.html中添加一个基本元素,以告诉 Angular 路由器如何组合导航 URL。
我们在index.html的头部标签中添加了href:
<base href="/">
- 创建主要导航栏:大多数 Web 应用程序在页面顶部都有一个导航栏,用于在应用程序的不同页面之间导航。我们使用 Bootstrap 的
nav bar组件为我们的朋友应用程序添加了一个主要导航栏。
- 第一步是在
app.component.html中包含nav标签,如下所示:
<nav class="navbar navbar-expand-lg navbar-light bg-color"></nav>
- 第二步是使用
ul标签创建项目列表:
<ul class="navbar-nav"></ul>
-
第三步是使用
li标签创建项目。在我们的应用程序中,我们必须根据用户登录条件激活标签页,例如,当用户登录时,将显示用户个人资料标签页。我们遵循以下条件来激活导航栏中的标签页:-
用户未登录:我们只激活了关于和登录标签页。用户可以通过点击 SIGNUP 按钮导航到注册页面。
-
用户已登录:我们停用了登录页面并激活了用户个人资料页面。
-
为了实现这些场景,我们将访问认证服务对象,检查用户登录状态,并激活标签页。我们使用ngIf指令进行条件检查:
<li class="nav-item" *ngIf="authenticationService?.isAuthenticated()"><a class="nav-link" routerLink="/app-friends-userprofile" routerLinkActive="active">My Profile</a></li>
模板中AuthenticationService的authenticationService?中的问号确保对象不为空。该对象在app.component.ts中定义,如下所示:
authenticationService: AuthenticationService;
constructor(private authService: AuthenticationService) {
this.authenticationService = authService;
}
到目前为止的完整app.component.html如下所示:
<h1 class="title">Friends - A Social App</h1>
<div class="nav-container">
<nav class="navbar navbar-expand-lg navbar-light bg-color">
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item"
*ngIf="authenticationService?.isAuthenticated()"><a
class="nav-link" routerLink="/app-friends-userprofile"
routerLinkActive="active">My Profile</a></li>
<li class="nav-item"
*ngIf="authenticationService?.isAuthenticated()">
<a class="nav-link" routerLink="/app-friends-userfriends"
routerLinkActive="active">Friends</a></li>
<li class="nav-item" ><a class="nav-link" routerLink="/app-
friends-about" routerLinkActive="active">About</a></li>
<li class="nav-item" active
*ngIf="!authenticationService?.isAuthenticated()">
<a class="nav-link" routerLink="/app-friends-login"
routerLinkActive="active">Login</a></li>
</ul>
<div class="form-container">
<form class="form-inline my-2 my-lg-0">
<input class="form-control mr-sm-2" type="text"
placeholder="Search friends..." aria-label="Search">
<button class="btn btn-success my-2 my-sm-0"
type="submit">Search</button>
</form>
</div>
</div>
</nav>
</div>
<router-outlet></router-outlet>
- 创建页面未找到和关于组件:
PageNotFoundComponent组件用于显示错误 URL 的视图,而AboutComponent用于显示有关网站的信息。
这两个组件看起来很相似,都有一个带有 h2 标签的消息标题。在这些组件中,我们在组件注释中定义了模板和样式表。这是创建组件的一种简单方法。
以下为完整的 page-not-found.component.ts:
import {Component} from '@angular/core';
@Component({
selector: 'app-friends-page-not-found',
template: '<h2>Page not found</h2>'
})
export class PageNotFoundComponent {}
以下为完整的 about.component.ts:
import {Component} from '@angular/core';
@Component({
selector: 'app-friends-about',
template: '<h2>Friends is a social app</h2>'
})
export class AboutComponent {}
- 为应用组件创建路由:我们为主应用模块创建关于和页面未找到组件的路由。
我们使用以下路径为两个组件创建路由;双星号(**)是一个通配符,因此当用户提供任何错误的 URL 时,路由器将导航到“页面未找到”组件:
export const ROUTES: Routes = [
{path: 'app-friends-about', component: AboutComponent,
pathMatch: 'full'},
{path: '**', redirectTo: 'app-friends-page-not-found'},
];
我们在 Angular 的 RouterModule 中注入应用模块的路由;现在完整的 app.routing.ts 如下所示:
import {RouterModule, Routes} from '@angular/router';
import {NgModule} from '@angular/core';
import {PageNotFoundComponent} from './notfound/page-not-found.component';
import {AboutComponent} from './about/about.component';
export const ROUTES: Routes = [
{path: 'app-friends-about', component: AboutComponent,
pathMatch: 'full'},
{path: '**', redirectTo: 'app-friends-page-not-found'},
];
@NgModule({
imports: [
RouterModule.forRoot(
ROUTES
)],
exports: [
RouterModule
]
})
export class AppRouting {
}
最后,我们将应用路由模块集成到我们的主应用模块中。我们在导入标签中添加 AppRouting,如下所示:
@NgModule({
declarations: [
AppComponent,
PageNotFoundComponent,
AboutComponent
],
imports: [
...
AuthenticationModule,
AppRouting
],
bootstrap: [AppComponent]
})
export class AppModule {}
当用户未登录时,导航栏将显示“关于”和“登录”标签页:
当用户登录时,导航栏将显示“我的资料”、“朋友”和“关于”标签页:
创建认证的路由模块
如前所述,我们将为每个功能模块构建单独的路由。在认证方面,我们有两个组件:
-
登录组件
-
注册组件
为了定义路由模块,我们需要创建导航路由常量:
export const ROUTES: Routes = [
{path: 'app-friends-login', component: LoginComponent},
{path: 'app-friends-signup', component: SignupComponent}
];
由于这些路由是主应用组件的子组件,我们将路由注入到子路由模块中:
RouterModule.forChild( ROUTES )
现在是完整的 authentication.routing.ts:
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {LoginComponent} from './login/login.component';
import {SignupComponent} from './signup/signup.component';
export const ROUTES: Routes = [
{path: 'app-friends-login', component: LoginComponent},
{path: 'app-friends-signup', component: SignupComponent}
];
/**
* Authentication Routing Module
*/
@NgModule({
imports: [
RouterModule.forChild(ROUTES)
],
exports: [
RouterModule
]
})
export class AuthenticationRouting {}
创建路由模块后,我们将 AuthenticationRouting 模块包含在主认证模块中;这使得我们的认证模块独立于主应用模块。
下面是一个示例 authentication.module.ts:
import { NgModule } from '@angular/core';
import { AuthenticationRouting } from './authentication.routing';
/**
* Authentication Module
*/
@NgModule({
imports: [
...
AuthenticationRouting
],
declarations: [
...
],
providers: [
...
]
})
export class AuthenticationModule {
}
探索更多路由技术
在本节中,我们将探索我们应用程序中的两种导航方法:
- 静态路由:在静态路由中,我们将在 HTML 模板中提供导航。Angular 路由器提供了一个指令来执行导航操作。我们将包含
routerLink指令,并将路由注入到子路由模块中:导航路径。如以下代码所示,当您点击“注册”按钮时,Angular 框架使用Router导航到注册组件:
<button routerLink="/app-friends-signup" data-tag="signup-tag" routerLinkActive="active" class="btn btn-success">
SIGNUP
</button>
- 动态路由:在动态路由中,我们使用 Angular 框架的
Router组件。该实例在构造函数中使用依赖注入:
constructor(
...
private router: Router,
){}
路由器提供了 navigateByUrl() 方法来导航到不同的组件;在以下场景中,登录成功后,我们导航到用户资料页面:
private navigateToUserProfile() {
this.router.navigateByUrl('/app-friends-userprofile');
}
现在我们已经为我们的应用程序添加了导航;因此,在下一节中,我们将添加基于条件的导航守卫。
添加认证守卫
守卫是 Angular 中一个非常有用的功能,用于保护路由。它们提供了用于限制我们应用程序中资源的安全功能,这样用户就不能在没有适当权限的情况下消费资源。
在 Angular 中有不同类型的守卫:
-
CanActivate:这个用于决定路由是否可以被激活 -
CanActivateChild:这个用于决定子路由是否可以被激活 -
CanDeactivate:这个用于决定路由是否可以被停用 -
CanLoad:这个用于决定模块是否可以懒加载
我们将查看 CanActivate 守卫在认证中的示例。我们将在用户认证成功后允许用户访问用户资料和好友页面。这意味着用户在没有认证的情况下将不允许访问 http://localhost:4200/app-friends-userprofile,并将被重定向到登录页面。激活守卫涉及的步骤如下:
- 守卫条件:我们需要为激活守卫提供条件。在这个场景中,我们正在检查当前用户的状态。这个条件也用于应用程序组件中,根据条件显示各种标签页:
public isAuthenticated(): boolean {
let user = this.angularFireAuth.auth.currentUser;
return user ? true : false;
}
现在的完整 authentication.service.ts 如下所示:
import {Injectable} from '@angular/core';
import {AngularFireAuth} from 'angularfire2/auth';
/**
* Authentication service
*
*/
@Injectable()
export class AuthenticationService {
/**
* Constructor
*
* @param {AngularFireAuth} angularFireAuth provides the
functionality related to authentication
*/
constructor(private angularFireAuth: AngularFireAuth) {
}
public signup(email: string, password: string): Promise<any> {
return
this.angularFireAuth.auth.createUserWithEmailAndPassword(email,
password);
}
public login(email: string, password: string): Promise<any> {
return
this.angularFireAuth.auth.signInWithEmailAndPassword(email,
password);
}
public resetPassword(email: string): Promise<any> {
return this.angularFireAuth.auth.sendPasswordResetEmail(email);
}
public isAuthenticated(): boolean {
const user = this.angularFireAuth.auth.currentUser;
return user ? true : false;
}
public signout() {
return this.angularFireAuth.auth.signOut();
}
}
- 守卫实现:我们通过扩展
CanActivate接口并重写canActivate方法来实现守卫。在这个方法中,当用户认证无效时,我们将导航到登录页面,这有助于根据守卫条件将路由导航到登录页面。
现在的完整 authentication.guard.ts 如下所示:
import {Injectable} from '@angular/core';
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router';
import {AuthenticationService} from './authentication.service';
@Injectable()
export class AuthenticationGuard implements CanActivate {
constructor(private authService: AuthenticationService,
private router: Router) {
}
canActivate(route: ActivatedRouteSnapshot, state:
RouterStateSnapshot): boolean {
const isLoggedIn: boolean = this.authService.isAuthenticated();
if (!isLoggedIn) {
this.router.navigateByUrl('/app-friends-login');
}
return isLoggedIn;
}
}
- 向用户模块添加守卫:这个守卫是在用户模块中添加的。用户模块将在下一章中更详细地介绍。在这里,我们配置了用户路由模块中的守卫,以限制用户访问用户资料和好友页面:
import {AuthenticationGuard} from '../services/authentication.guard';
/**
* User Module
*/
@NgModule({
imports: [
...
],
declarations: [
...
],
providers: [
AuthenticationGuard
]
})
export class UserModule {
}
- 添加守卫以保护组件:如以下代码所示,我们可以将这个守卫添加到任何需要此条件检查的组件中。我们将守卫添加到用户资料和用户好友列表组件中。这意味着这些页面受到非法访问的保护。
这是完整的 user-routing.module.ts:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import {UserProfileComponent} from './user-profile/user-profile.component';
import {AuthenticationGuard} from '../services/authentication.guard';
const ROUTES: Routes = [
{path: '', redirectTo: '/app-friends-userprofile', pathMatch:
'full' , canActivate: [AuthenticationGuard]},
{path: 'app-friends-userprofile', component: UserProfileComponent
, canActivate: [AuthenticationGuard]}
];
@NgModule({
imports: [
RouterModule.forChild(ROUTES)
],
exports: [
RouterModule
],
providers: [
AuthenticationGuard
]
})
export class UserRoutingModule { }
- 测试守卫:您可以通过将用户资料 URL (
http://localhost:4200/app-friends-userprofile) 粘贴到浏览器中来测试这个守卫,用户将被重定向到登录页面进行认证。
Firebase 会话生命周期
Firebase 会持久化用户状态,因此即使用户刷新或重新启动页面,用户也始终处于登录状态,页面将被重定向到主页而不是登录页面。我们将涵盖 Firebase 会话生命周期的两种导航场景:
- 用户令牌存在:在这种情况下,用户令牌仍然有效,用户将被重定向到用户资料页面。
AngularFireAuth.auth 提供了 onAuthStateChanged 方法来了解用户状态信息。我们订阅此方法,检查我们的用户,并将他们重定向到个人资料页面。
下面是 login.component.ts 的示例:
import {Component} from '@angular/core';
import {User} from '../../services/user';
import {Router} from '@angular/router';
import {AuthenticationService} from '../../services/authentication.service';
import {UserService} from '../../services/user.service';
import {AngularFireAuth} from 'angularfire2/auth';
@Component({
selector: 'app-friends-login',
styleUrls: ['login.component.scss'],
templateUrl: 'login.component.html',
})
export class LoginComponent {
...
private user: User;
constructor(private userService: UserService,
private router: Router,
private authService: AuthenticationService,
private angularFireAuth: AngularFireAuth) {
this.angularFireAuth.auth.onAuthStateChanged(user => {
if (user) {
this.getUserInfo(user.uid);
}
});
}
private navigateToUserProfile() {
this.router.navigateByUrl('/app-friends-userprofile');
}
private getUserInfo(uid: string) {
this.userService.getUser(uid).subscribe(snapshot => {
this.user = snapshot;
this.navigateToUserProfile();
});
}
}
-
用户令牌过期:在这种情况下,用户令牌过期,用户被重定向到登录页面。通常,用户令牌在以下条件下过期:
-
用户清除浏览历史:用户令牌可以通过清除浏览器历史记录而过期。这将清除令牌,并将用户重定向到登录页面。
-
用户更改密码:当用户更改密码时,用户令牌过期,并将他们重定向到登录页面。此场景将在下一章中介绍。
-
用户注销:当用户注销时,用户令牌过期,并将他们重定向到登录页面。我们将在本节中介绍此场景。
-
我们已在用户资料页面实现了注销功能。用户资料组件将在下一章中更详细地介绍。在本节中,我们仅添加注销功能。
我们在用户资料模板中创建了一个按钮。当用户点击 LOGOUT 按钮时,用户会话将被清除,并将用户重定向到登录页面。
下面是 user-profile.component.html 的示例:
<div class="user-profile">
<div class="user-profile-btn">
<button type="button" (click)='onLogout()' class="btn btn-
info">LOGOUT</button>
</div>
</div>
当用户点击 LOGOUT 按钮时,UserProfileComponent 的 onLogout() 方法被调用,我们在认证服务中调用 signout()。
下面是目前 user-profile.component.ts 的示例:
import {Component, OnInit} from '@angular/core';
import {AuthenticationService} from '../../services/authentication.service';
import {Router} from '@angular/router';
@Component({
selector: 'app-friends-userprofile',
styleUrls: ['user-profile.component.scss'],
templateUrl: 'user-profile.component.html'
})
export class UserProfileComponent {
constructor(private authService: AuthenticationService,
private router: Router) {
}
onLogout(): void {
this.authService.signout().then(() => {
this.navigateToLogin();
});
}
navigateToLogin() {
this.router.navigateByUrl('/app-friends-login');
}
}
认证服务在 authentication.service.ts 中具有注销功能:
public signout(): Promise<any> {
return this.angularFireAuth.auth.signOut();
}
目前我们的项目结构如下:
因此,我们来到了第一个认证模块的结尾。我们的认证模块将如下所示:
摘要
在本章中,我们学习了页面视图之间的导航。我们在基础模块中启用了导航。我们使用路由链接指令创建我们的主要导航栏。我们看到了如何在模板中访问组件变量,并基于条件启用了导航项。我们构建了第一个守卫来限制用户导航到我们的用户资料页面,以便只有经过身份验证的用户才能查看页面。最后,我们介绍了 Firebase 会话生命周期,并基于用户令牌实现了导航。最后,我们探讨了认证模块的项目结构。
在下一章中,我们将构建更复杂的模块,并探索 Firebase 和 Angular 的更多功能。