Angular 专家级编程(五)
原文:
zh.annas-archive.org/md5/EE5928A26B54D366BD1C7A331E3448D9译者:飞龙
第十章:实现 Angular 管道
在本章中,您将学习关于 Angular 管道。将 Angular 管道视为过滤器的现代化版本,包括帮助我们在模板中格式化值的函数。Angular 中的管道基本上是 Angular v1 中过滤器的扩展。我们可以在模板中轻松使用许多有用的内置管道。您将学习内置管道,我们还将创建自定义用户定义的管道。
在本章结束时,您将学习并实现以下内容:
-
介绍 Angular 管道
-
定义和实现管道
-
了解各种内置管道
-
DatePipe
-
DecimalPipe
-
CurrencyPipe
-
LowerCasePipe 和 UpperCasePipe
-
JSON 管道
-
SlicePipe
-
async 管道
-
学习实现自定义用户定义的管道
-
为管道参数化
-
链接管道
-
了解纯管道和不纯管道
Angular 管道-概述
管道允许我们在模板视图中显示值之前格式化值。例如,在大多数现代应用程序中,我们希望显示诸如今天、明天等术语,而不是系统日期格式,例如 2017 年 4 月 13 日 08:00。让我们看看更多现实世界的场景。
您希望应用程序中的提示文本始终为小写吗?没问题;定义并使用LowercasePipe。在天气应用程序中,如果您希望显示月份名称为 MAR 或 APR 而不是其全名,请使用DatePipe。
很酷,对吧?你明白了。管道帮助您添加业务规则,因此您可以在模板中实际显示数据之前转换数据。
与 Angular 1.x 过滤器建立联系的一个好方法是通过 Angular 管道,但管道不仅仅是过滤。
我们已经使用了 Angular 路由器来定义路由路径,因此我们在一个页面中拥有所有管道的功能;您可以在相同或不同的应用程序中创建它。随意发挥您的创造力。
在 Angular 1.x 中,我们有过滤器--管道是过滤器的替代品。
在下一节中,您将学习如何定义和使用 Angular 管道。
定义管道
管道运算符用管道符号(|)定义,后跟管道的名称:
{{ appvalue | pipename }}
以下是一个简单的lowercase管道的示例:
{{"Sridhar Rao" | lowercase}}
在上述代码中,我们使用lowercase管道将文本转换为小写。
现在,让我们编写一个使用lowercase管道示例的示例Component:
@Component({
selector: 'demo-pipe',
template: `
Author name is {{authorName | lowercase}}
`
})
export class DemoPipeComponent {
authorName = 'Sridhar Rao';
}
让我们详细分析上述代码:
-
我们定义了一个
DemoPipeComponent组件类 -
我们创建了一个字符串变量
authorName,并赋予了值'Sridhar Rao' -
在模板视图中,我们显示了
authorName;然而,在将其打印到 UI 之前,我们使用了lowercase管道进行转换
运行上述代码,您应该看到以下输出:
干得好!在前面的例子中,我们使用了内置管道。在接下来的部分,您将学习更多关于内置管道,并创建一些自定义管道。
请注意,管道运算符仅在模板中起作用,而不在控制器内部。
内置管道
Angular 管道是 Angular 1.x 过滤器的现代化版本。Angular 带有许多预定义的内置管道。我们可以直接在视图中使用它们,并在运行时转换数据。
以下是 Angular 内置支持的所有管道的列表:
-
DatePipe
-
DecimalPipe
-
CurrencyPipe
-
LowercasePipe 和 UppercasePipe
-
JSON 管道
-
SlicePipe
-
异步管道
在接下来的部分,让我们实现并学习更多关于各种管道,并看到它们的实际应用。
DatePipe
DatePipe,顾名思义,允许我们格式化或转换与日期相关的值。DatePipe 也可以根据运行时传递的参数以不同格式转换值。
一般语法如下代码片段所示:
{{today | date}} // prints today's date and time
{{ today | date:'MM-dd-yyyy' }} //prints only Month days and year
{{ today | date:'medium' }}
{{ today | date:'shortTime' }} // prints short format
让我们详细分析前面的代码片段:
-
如前一节所述,一般语法是变量后跟着一个(
|)管道运算符,然后是管道运算符的名称 -
我们使用 DatePipe 来转换
today变量 -
此外,在前面的例子中,您会注意到我们向管道运算符传递了一些参数;我们将在下一节中介绍向管道传递参数
现在,让我们创建一个完整的DatePipe组件示例;以下是实现DatePipe组件的代码片段:
import { Component } from '@angular/core';
@Component({
template: `
<h5>Built-In Pipes</h5>
<ol>
<li>
<strong class="packtHeading">DatePipe example 1</strong>
<p>Today is {{today | date}}
</li>
<li>
<strong class="packtHeading">DatePipe example 2</strong>
<p>{{ today | date:'MM-dd-yyyy' }}
<p>{{ today | date:'medium' }}
<p>{{ today | date:'shortTime' }}
</li>
</ol>
`,
})
export class PipeComponent {
today = new Date();
}
让我们详细分析前面的代码片段:
-
我们创建了一个
PipeComponent组件类。 -
我们定义了一个
today变量。 -
在视图中,我们根据不同的参数将变量的值转换为各种表达式。
现在运行应用程序,我们应该看到以下输出:
您在本节中学习了DatePipe。在接下来的部分,您将继续学习和实现其他内置管道,并创建一些自定义用户定义的管道。
DecimalPipe
在本节中,您将了解另一个内置管道--DecimalPipe。
DecimalPipe 允许我们根据区域规则格式化数字。 DecimalPipe 也可以用于以不同格式转换数字。
一般的语法如下:
appExpression | number [:digitInfo]
在上述代码片段中,我们使用了数字管道,可以选择性地传递参数。
让我们看看如何创建一个实现小数点的DatePipe,以下是相同的示例代码:
import { Component } from '@angular/core';
@Component({
template: `
<h5>Built-In Pipes</h5>
<ol>
<li>
<strong class="packtHeading">DecimalPipe example</strong>
<p>state_tax (.5-5): {{state_tax | number:'.5-5'}}</p>
<p>state_tax (2.10-10): {{state_tax | number:'2.3-3'}}</p>
</li>
</ol>
`,
})
export class PipeComponent {
state_tax: number = 5.1445;
}
让我们详细分析上述代码片段:
-
我们定义了一个组件类,即
PipeComponent。 -
我们定义了一个
state_tax变量。 -
然后我们在视图中转换了
state_tax。 -
第一个管道操作符告诉表达式将小数打印到小数点后五位。
-
第二个管道操作符告诉表达式将值打印到小数点后三位。
上述管道组件示例的输出如下:
毫无疑问,数字管道是各种应用程序中最有用和常用的管道之一。我们可以转换数字值,特别是处理小数和浮点数。
CurrencyPipe
对于希望迎合多国地理位置的应用程序,我们需要显示特定国家的代码及其相应的货币值--这就是CurrencyPipe派上用场的地方。
CurrencyPipe操作符用于在数字值前附加国家代码或货币符号。
看一下实现CurrencyPipe操作符的代码片段:
{{ value | currency:'USD' }}
Expenses in INR: {{ expenses | currency:'INR' }}
让我们详细分析上述代码片段:
-
第一行代码显示了编写
CurrencyPipe的一般语法。 -
第二行显示了货币的语法,我们用它来转换
expenses的值,并在其后附加了印度货币符号。
现在我们知道如何使用CurrencyPipe操作符,让我们组合一个示例来显示多种货币和国家格式;以下是实现CurrencyPipe操作符的完整组件类:
import { Component } from '@angular/core';
@Component({
selector: 'currency-pipe',
template: `
<h5>CurrencyPipe Example</h5>
<ol>
<li>
<p>Salary in USD: {{ salary | currency:'USD':true }}</p>
<p>Expenses in INR: {{ expenses | currency:'INR':false }}</p>
</li>
</ol>
`
})
export class CurrencyPipeComponent {
salary: number = 2500;
expenses: number = 1500;
}
让我们详细分析上述代码:
-
我们创建了一个组件类
CurrencyPipeComponent,并声明了几个变量,即salary和expenses。 -
在组件模板中,我们通过添加
国家和货币详情来转换变量的显示。 -
在第一个管道操作符中,我们使用了
'currency: USD',这将在变量之前附加($)美元符号。 -
在第二个管道操作符中,我们使用了
'currency : 'INR':false',这将添加货币代码,false将告诉它不要打印符号。
现在,启动应用程序,我们应该看到以下输出:
在本节中,我们讨论并实现了CurrencyPipe。在接下来的几节中,我们将继续探索和学习其他内置管道以及更多内容。
LowerCasePipe 和 UpperCasePipe
LowerCasePipe 和 UpperCasePipe,顾名思义,分别用于将文本转换为小写和大写。
看一下以下代码片段:
Author is Lowercase {{authorName | lowercase }}
Author in Uppercase is {{authorName | uppercase }}
让我们详细分析前面的代码:
-
第一行代码使用
lowercase管道将authorName的值转换为小写。 -
第二行代码使用
uppercase管道将authorName的值转换为大写。
现在我们已经看到如何定义小写和大写管道,是时候创建一个完整的组件示例了,该示例实现了管道以显示作者姓名的小写和大写形式。
看一下以下代码片段:
import { Component } from '@angular/core';
@Component({
selector: 'textcase-pipe',
template: `
<h5>Built-In LowercasPipe and UppercasePipe</h5>
<ol>
<li>
<strong>LowercasePipe example</strong>
<p>Author in lowercase is {{authorName | lowercase}}
</li>
<li>
<strong>UpperCasePipe example</strong>
<p>Author in uppercase is {{authorName | uppercase}}
</li>
</ol>
`
})
export class TextCasePipeComponent {
authorName = "Sridhar Rao";
}
让我们详细分析前面的代码:
-
我们创建了一个组件类,
TextCasePipeComponent,并定义了一个authorName变量。 -
在组件视图中,我们使用了
lowercase和uppercase管道。 -
第一个管道将变量的值转换为小写文本。
-
第二个管道将变量的值转换为大写文本。
运行应用程序,我们应该看到以下输出:
在本节中,您学会了如何使用lowercase和uppercase管道来转换值。
JSON Pipe
类似于 Angular 1.x 中的 JSON 过滤器,我们有 JSON 管道,它可以帮助我们将字符串转换为 JSON 格式的字符串。
在小写或大写管道中,我们转换了字符串;使用 JSON 管道,我们可以将字符串转换并显示为 JSON 格式的字符串。
通用的语法如下代码片段所示:
<pre>{{ myObj | json }}</pre>
现在,让我们使用前面的语法并创建一个完整的Component示例,其中使用了 JSON Pipe:
import { Component } from '@angular/core';
@Component({
template: `
<h5>Author Page</h5>
<pre>{{ authorObj | json }}</pre>
`
})
export class JSONPipeComponent {
authorObj: any;
constructor() {
this.authorObj = {
name: 'Sridhar Rao',
website: 'http://packtpub.com',
Books: 'Mastering Angular2'
};
}
}
让我们详细分析前面的代码:
-
我们创建了一个组件类,
JSONPipeComponent和authorObj,并将 JSON 字符串赋给了这个变量。 -
在组件模板视图中,我们转换并显示了 JSON 字符串。
运行应用程序,我们应该看到以下输出:
JSON 很快就成为了 Web 应用程序之间集成服务和客户端技术的事实标准。因此,每当我们需要将值转换为视图中的 JSON 结构时,JSON 管道都非常方便。
SlicePipe
SlicePipe 与数组切片 JavaScript 函数非常相似。Slice 管道从字符串中提取两个指定索引之间的字符,并返回新的子字符串。
定义 SlicePipe 的一般语法如下:
{{email_id | slice:0:4 }}
在前面的代码片段中,我们正在对电子邮件地址进行切片,以仅显示变量值email_id的前四个字符。
既然我们知道如何使用 SlicePipe,让我们在组件中将其放在一起。
以下是实现 SlicePipe 的完整代码片段:
import { Component } from '@angular/core';
@Component({
selector: 'slice-pipe',
template: `
<h5>Built-In Slice Pipe</h5>
<ol>
<li>
<strong>Original string</strong>
<p> Email Id is {{ emailAddress }}
</li>
<li>
<strong>SlicePipe example</strong>
<p>Sliced Email Id is {{emailAddress | slice : 0: 4}}
</li>
</ol>
`
})
export class SlicePipeComponent {
emailAddress = "test@packtpub.com";
}
让我们详细分析前面的代码片段:
-
我们创建了一个
SlicePipeComponent类。 -
我们定义了一个字符串变量
emailAddress并为其赋值test@packtpub.com。 -
然后,我们将 SlicePipe 应用于
{{emailAddress | slice : 0: 4}}变量。 -
我们从
0位置开始获取子字符串,并从变量值emailAddress中获取4个字符。
运行应用程序,我们应该看到以下输出:
SlicePipe 肯定是一个非常有用的内置管道,特别是处理字符串或子字符串。
异步管道
异步管道允许我们直接将 promise 或 observable 映射到我们的模板视图中。为了更好地理解异步管道,让我先介绍一下 observable。
Observables 是 Angular 可注入的服务,可用于将数据流式传输到应用程序中的多个部分。在下面的代码片段中,我们使用async管道作为一个 promise 来解析返回的作者列表:
<ul id="author-list">
<li *ngFor="let author of authors | async" >
<!-- loop the object here -->
</li>
</ul>
async管道现在订阅Observable(作者)并检索最后一个值。
让我们看一下如何使用async管道作为Promise和Observable的示例。
在我们的app.component.ts文件中添加以下代码行:
getAuthorDetails(): Observable<Author[]> {
return this.http.get(this.url).map((res: Response) => res.json());
}
getAuthorList(): Promise<Author[]> {
return this.http.get(this.url).toPromise().then((res: Response) =>
res.json());
}
让我们详细分析前面的代码片段:
-
我们创建了一个
getAuthorDetails方法,并附加了一个相同的 observable。该方法将返回来自url的响应,这是一个 JSON 输出。 -
在
getAuthorList方法中,我们绑定了一个需要在通过http请求调用的url返回的输出中解析或拒绝的 promise。
在本节中,我们已经看到了async管道的工作原理。您会发现它与处理服务非常相似。我们可以映射一个 promise 或一个 observable,并将结果映射到模板上。
参数化管道
管道也可以带参数。我们可以在管道后面传递参数。参数在管道后用冒号符号(:)分隔:
{{appValue | Pipe1: parameter1: parameter2 }}
让我们快速构建一个简单的管道示例,看看它的运行情况。以下是带有MM-dd-yyyy参数的DatePipe的示例:
{{today | date:'MM-dd-yyyy' }}
另一个带参数的管道示例如下:
{{salary | currency:'USD':true}}
让我们详细分析前面的代码片段:
-
我们向
CurrencyPipe传递了 USD 作为参数,这将告诉管道显示货币代码,例如美元的USD和欧元的EUR。 -
true参数表示显示货币符号($)。默认情况下,它设置为 false。
让我们通过组件的完整代码来看它们的运行情况:
import { Component } from '@angular/core';
@Component({
template: `
<h5>Parametrizing pipes</h5>
<p>Date with parameters {{ today | date:'MM-dd-yyyy' }}
<p>Salary in USD: {{salary | currency:'USD':true}}</p>
`,
})
export class ParamPipeComponent {
today = new Date();
salary: number = 1200;
}
在前面的代码片段中,我们创建了一个ParamPipeComponent类,并定义了today和salary变量。
在Component模板视图中,我们为DatePipe传递了date:'MM-dd-yyyy'参数,为CurrencyPipe传递了currency:'USD' :true参数。
以下是前面代码的输出:
在前面的示例中,我们传递了自定义参数,如currency和date格式,给管道,并相应地查看了输出。
在大多数应用用例中,我们需要向管道传递参数,以根据业务逻辑转换值。在本节中,我们重点介绍了通过传递值来对管道进行参数化。
到目前为止,我们一直在使用内置管道并向管道传递参数。
在接下来的章节中,您将学习如何链接管道、创建自定义管道,以及向自定义用户定义的管道传递参数。
链式管道
我们可以将多个管道链接在一起。这在我们需要关联多个需要应用的管道,并且最终输出将被所有应用的管道转换的情况下特别有帮助。
工作流或链将被触发,并依次应用管道。链管道语法的示例如下:
{{today | date | uppercase | slice:0:4}}
我们在前面的代码中应用了两个链式管道。首先,DatePipe应用于today变量,然后立即应用uppercase管道。以下是ChainPipeComponent的整个代码片段:
import {Component } from '@angular/core';
@Component({
template: `
<h5>Chain Pipes</h5>
<p>Month is {{today | date | uppercase | slice:0:4}}
`,
})
export class ChainPipeComponent {
today = new Date();
}
我们使用了 slice 来仅显示月份的前四个字符。以下截图显示了前面组件的输出:
应用链式管道时需要记住的一些关键事项如下:
-
执行顺序是从左到右
-
管道是依次应用的。
在本节中,您了解了如何在我们的应用程序中将多个管道链接在一起。在下一节中,您将详细了解如何创建自己的自定义管道。
创建自定义管道
到目前为止,一切都很好。管道确实给我们留下了深刻的印象,但等等,我们还可以用管道做更棒的事情。内置管道,正如您所见,非常有限且少。我们当然需要创建自己的自定义管道,以满足我们应用程序的功能。
在本节中,您将学习如何为我们的应用程序创建自定义管道。
在这个例子中,我们将构建一个管道,它将是一个税收计算器。我们传递产品的价格,并使用管道功能自动计算并显示销售税。神奇,对吧?
要创建自定义管道,我们需要执行以下步骤:
-
创建一个模板来应用到管道上(在我们的例子中,它是
updateTaxPipe)。 -
创建一个管道文件,即
update-tax.pipe.ts。 -
每个管道文件都必须从 Angular 核心中导入管道。
-
定义管道元数据。
-
创建
Component类。它应该有transform函数,其中包含管道应该执行的业务逻辑。
在下面的代码片段中,我们正在定义一个名为UpdateTaxPipe的自定义管道,它将接受一个percentage参数,并进行销售税计算并在我们的模板中显示:
{{ productPrice | UpdateTaxPipe: percentage }}
让我们创建我们的update-tax.pipe.ts文件:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name : "UpdateTaxPipe"
})
export class UpdateTaxPipe implements PipeTransform{
transform(value:number, taxVal: number):number{
return (value*taxVal)/100;
}
}
让我们详细分析前面的代码片段:
-
为了告诉 Angular 这是一个管道,我们应用了
@Pipe装饰器,它是从核心 Angular 库中导入的。 -
我们创建了一个自定义管道,名为
UpdateTaxPipe,使用了name管道元数据。 -
我们创建了一个
transform方法,这对于管道是必需的,并在方法内定义了我们的业务逻辑和规则。 -
我们向
transform方法传递了两个参数,它返回了更新后的值。
无论我们是否包括接口 PipeTransform,Angular 都会寻找并执行transform方法。
运行应用程序,我们应该看到如下截图所示的输出:
在本节中,您学习了如何创建自定义管道。创建用户定义的管道非常简单和容易。自定义管道确实帮助我们轻松地集成应用程序的业务逻辑。
尝试创建自定义管道,可以适应一次编写,多次使用逻辑,也可以在许多组件视图中使用;例如,验证电话号码、地址等。
纯管道和非纯管道
管道还接受一个名为 Pure 的元数据。管道有两种状态:
-
纯管道
-
非纯管道
纯管道
纯管道只有在输入参数的值发生变化时才会执行。它不会记住或追踪任何先前的值或状态。Angular 内置管道都是pure管道。
到目前为止我们看到的所有管道示例都是纯管道。
非纯管道
无论值或参数是否改变,非纯管道都会在每次变更检测周期中调用。为了使用非纯管道,我们应该将管道修饰符pure设置为false。
默认情况下,所有管道修饰符的pure都设置为true。
将管道修饰符的值设置为pure将检查管道的输出,无论其值是否改变,都会保持更新管道提供的值。
定义非纯管道与创建任何自定义用户定义管道相同,唯一的区别在于在@Pipe修饰符中,我们将通过将值设置为false来明确指定管道为非纯的。
以下是通过将管道的值设置为 false 来定义非纯管道的语法:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'authorName'
pure: false
})
在本节中,您了解了不同类型的 Angular 管道,即纯管道和非纯管道。只有在输入组件的值发生变化时才会调用纯管道。无论值是否改变,非纯管道都会在每次变更检测时调用。
摘要
在本章中,您了解了关于 Angular 管道的一切。Angular 管道在转换视图模板中的数据方面非常有用。Angular 管道是 Angular 1.x 中可用的过滤器的现代化版本。
我们可以在模板中轻松使用许多有用的内置管道操作符。在本章中,您了解了内置管道以及创建自定义用户定义管道。
在处理数字时,我们可以使用DatePipe、DecimalPipe和CurrencyPipe。在专门处理字符串时,我们可以始终使用SlicePipe、LowercasePipe和UppercasePipe。
当我们主要处理服务器端响应或进行异步调用并处理响应时,我们可以使用JSONPipe和asyncPipe。我们还涵盖了向管道传递参数,并根据应用程序的需要进行定制。
我们探讨了如何创建和实现自定义用户定义的管道,这些管道还可以接受参数,以根据我们应用程序的需求更好地定制它们。
所以继续,用管道转换你的视图。
在下一章中,您将学习如何实现 Angular 服务。您将学习有关服务和工厂的知识,创建 Angular 服务,使用服务从组件中访问数据以及创建异步服务。
第十一章:实现 Angular 服务
服务在任何 Angular 应用程序中都扮演着重要的角色。我们可以通过充分利用 Angular 中的许多内置服务来设计我们自己的 Angular 服务。在本章中,我们将讨论如何做到这一点,以便您了解如何创建和管理 Angular 服务。
在本章中,我们将涵盖以下主题:
-
为什么要使用服务或工厂?
-
创建服务
-
使用服务从组件中访问数据
-
创建异步服务
为什么要使用服务或工厂?
我们已经讨论了单向数据绑定、双向数据绑定以及组件之间的数据共享。我们可能已经定义了非常清晰的视图并实现了整洁的组件,但是业务逻辑和数据获取/存储逻辑必须存在于某个地方。构建出色的 Angular 应用程序来自于充分利用内置服务。Angular 框架包括帮助您进行网络、缓存、日志记录、承诺等方面的服务。
编写我们自己的服务或工厂有助于实现代码的可重用性,并使我们能够在应用程序块(如组件、指令等)之间共享特定于应用程序的逻辑。将特定于应用程序的逻辑组织到服务或工厂中会导致更清晰、更明确定义的组件,并帮助您以更易维护的代码组织项目。
在 AngularJS 中,我们为此目的实现服务或工厂。服务是在运行时使用 new 关键字调用的,比如构造函数。以下代码片段显示了服务实现的 AngularJS 代码:
function MovieService($http) {
this.getMovieList = function getMovieList() {
return $http.get('/api/movies');
};
}
angular.module('moviedb').service('MovieService', MovieService);
MovieService函数可以注入到任何需要从 API 获取电影列表的控制器中。
在 Angular 中,可以使用工厂来实现相同的功能,并具有额外的功能。工厂是处理创建对象的一种设计模式。我们可以从工厂返回新的类、函数或闭包。与服务类似,工厂也可以注入到控制器中。以下代码片段显示了工厂实现的 AngularJS 代码:
function MovieService($http) {
return {
getMovieList: function() {
return $http.get('/api/movies');
}
};
}
angular.module('moviedb').factory('MovieService', MovieService);
服务和工厂都可以注入到控制器中,并且可以调用getMovieList函数,如下所示:
function MovieController(MovieService service) {
service.getMovieList().then(function (response) {
// manage response
});
}
angular.module('moviedb').controller('MovieController',
MovieController);
虽然工厂是灵活的,但服务是使迁移到 ES6 更容易的最佳选择。使用服务时,ES5 中的构造函数可以在迁移到 ES6 的过程中顺利替换为 ES6 类。我们可以将MovieService服务重写为 ES6 如下:
class MovieService {
getMovieList() {
return $http.get('/api/movies');
}
}
app.service('MovieService', MovieService);
服务是用户定义的类,用于解决特定目的,并可以注入到组件中。Angular 建议在组件中只包含与视图相关的代码,以丰富 Angular 应用程序中的 UI/UX。组件是服务的消费者,它们作为应用程序数据的来源和业务逻辑的库。保持组件清晰并注入服务使我们能够针对模拟服务测试组件:
创建一个服务
应用程序特定或业务逻辑函数,如持久化应用程序数据、记录错误、文件存储等,应该委托给服务,组件应该消费相应的服务来处理适当的业务或应用程序特定逻辑:
让我们创建一个简单的名为BookService的服务,处理获取源中可用的书籍集合。源可以是从 Web API 服务返回的数据或 JSON 文件。
首先,让我们创建一个Book模型来持久化领域对象值。下面显示了Book类的代码片段:
export class Book {
id: number;
title: string;
author: string;
publisher: string;
}
前面的代码片段显示了一个Book的 TypeScript 类,其中包括id、title、author和publisher等属性。现在让我们创建一个名为BookService的服务,处理与Book相关的操作:
import { Injectable } from '@angular/core';
import {Book} from './book';
@Injectable()
export class BookService {
getBooks() {
var books: Book[] = [
{ "id": 1, "title": "ASP.NET Web API Security Essentials", author:
"Rajesh Gunasundaram", publisher: "Packt Publishing" },
{ "id": 2, "title": "Learning Angular for .Net Developers", author:
"Rajesh Gunasundaram", publisher: "Packt Publishing" },
{ "id": 3, "title": "Mastering Angular", author: "Rajesh
Gunasundaram", publisher: "Packt Publishing" },
];
return books;
}
}
在这里,我们首先导入了Book模型类。然后,我们定义了BookService类,其中包含一个getBooks方法,返回书籍的集合。
现在我们需要一个组件来注入BookService并消费。让我们创建一个BookListComponent,通过调用BookService的getBooks方法来检索书籍列表。以下代码片段显示了BookListComponent类:
import { Component, OnInit } from '@angular/core';
import { Book } from './book';
import { BookService } from './book.service';
@Component({
selector: 'book-list',
template: `
<div *ngFor="let book of books">
{{book.id}} - {{book.title}}<br/>
Author: {{book.author}}<br/>
Publication: {{book.publisher}}
</div>
`,
providers: [BookService]
})
export class BookListComponent implements OnInit {
books: Array<Book>;
constructor(private bookService: BookService) { }
ngOnInit() {
this.books = this.bookService.getBooks();
}
}
在这里,我们首先从@angular/core中导入Component和OnInit,然后导入Book模型类和BookService类。然后我们用@Component属性对BookListComponent类进行了注释,以及选择器和模板等元数据信息。BookListComponent类定义了一个Book数组的books变量和一个构造函数,它被注入了BookService。请注意,BookListComponent实现了OnInit生命周期钩子,并且它通过使用注入到构造函数中的BookService实例来调用BookService类的getBooks方法。getBooks返回的书籍列表被赋给了BookListComponent类的books变量。
现在让我们创建一个根组件AppComponent。将BookListComponent作为指令传递,并将BookService作为提供者。以下是AppComponent的代码片段:
import { Component } from '@angular/core';
import { BookService } from './book.service';
@Component({
selector: 'my-books',
template: '
<h2>Book Library</h2>
<book-list></book-list>
'
})
export class AppComponent { }
在这里,我们首先从@angular/core中导入Component,BookListComponent和BookService。然后我们用@Component属性对AppComponent进行了注释,以及选择器和模板等元数据。请注意,模板中有一个特殊的 HTML 标签<book-list/>。在某个地方,我们需要指示 Angular 初始化BooklistComponent并相应地渲染视图。我们还需要告诉 Angular,AppComponent是根组件,通过引导它来实现这一点。我们可以通过为我们的 Angular 应用程序创建一个入口点来实现这一点。
创建一个名为AppModule的类,并用NgModule进行注释(app.module.ts)。这指示 Angular 模块,这个类是应用程序的入口点。这里给出了AppModule的代码片段:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { BookListComponent } from './book-list.component';
@NgModule({
imports: [ BrowserModule ],
declarations: [ AppComponent, BooklistComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
在这里,我们首先从 Angular 核心中导入NgModule。然后我们从 Angular 平台浏览器中导入BrowserModule,因为我们的应用程序在 Web 浏览器上运行。然后我们导入应用程序组件,比如AppComponent,它是一个引导根组件,以及BooklistComponent,导入并添加到声明中。请注意,AppModule被装饰为NgModule,同时具有元数据,如导入、声明和引导。
现在让我们创建一个index.html页面,其中包含以下代码片段:
<!DOCTYPE html>
<html>
<head>
<base href="/">
<title>Book Library</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-
scale=1">
</head>
<body>
<h1>TodoList Angular app for Packt Publishing...</h1>
<my-books>Loading...</my-books>
</body>
</html>
在这里,我们没有引用任何必要的库来自node_modules,因为它们将由 Webpack 加载。Webpack 是一个用于捆绑资源并从服务器提供给浏览器的工具。Webpack 是 systemJS 的推荐替代方案。
使用服务从组件中访问数据
随着 Angular 应用程序的发展,我们不断引入更多的组件,这些组件将处理应用程序的核心数据。因此,我们可能会写重复的代码来访问数据。然而,我们可以通过引入可重用的数据服务来避免编写冗余的代码。需要数据的组件可以注入数据服务,并且可以用来访问数据。通过这种方式,我们可以重用逻辑,编写更少的代码,并在设计组件时有更多的分离。
我们将使用 Angular 的HttpModule,它作为一个npm包进行发布。为了在我们的应用程序中使用HttpModule,我们需要从@Angular/http导入HttpModule,并且 HTTP 服务应该被注入到控制器或应用程序服务的构造函数中。
实施服务
应用程序可以在组件之间共享数据。考虑一个电影数据库应用程序,其中Movies列表或单个Movie对象将在组件之间共享。我们需要一个服务来在任何组件请求时提供Movies列表或单个Movie对象。
首先,让我们使用 Angular CLI 创建一个电影服务。在命令提示符中执行以下命令以生成movie.service的样板代码:
e:\Explore\packt\MovieDB>ng generate service Movie
installing service
create src\app\movie.service.spec.ts
create src\app\movie.service.ts
e:\Explore\packt\MovieDB>
在这里,Angular CLI 创建了两个文件,即movie.service.ts和movie.service.spec.ts。生成的movie.service.ts的样板代码如下所示:
import { Injectable } from '@angular/core';
@Injectable()
export class MovieService {
constructor() { }
}
请注意,MovieService类被装饰为@Injectable属性,以便依赖注入来实例化并将此服务注入到任何需要它的组件中。我们通过从 Angular 核心导入它,使这个Injectable函数可用。
接下来,我们需要向生成的MovieService添加getMovies函数。将getMovies()函数引入到MovieService类中如下:
import { Injectable } from '@angular/core';
@Injectable()
export class MovieService {
constructor() { }
getMovies(): void {}
}
请注意,我们现在将返回类型设置为 void,但是当我们进行进一步实现时,我们需要进行更改。
我们需要引入一个领域模型,Movie,来表示整个应用程序中的电影。让我们使用 Angular CLI 生成Movie类的样板代码如下:
e:\Explore\packt\MovieDB>ng generate class Movie
installing class
create src\app\movie.spec.ts
create src\app\movie.ts
e:\Explore\packt\MovieDB>
在这里,这个命令创建了两个文件,分别是movie.ts和movie.spec.ts。实际上,在领域模式下,我们可能不会编写任何测试方法来断言它,所以你可以安全地删除movie.spec.ts。生成的movie.ts的代码片段如下所示:
export class Movie {
}
让我们添加一些属性来使其代表电影的特征。代码如下所示:
export class Movie {
public constructor(
private _movie_id:number,
private _title: string,
private _phase: string,
private _category_name: string,
private _release_year: number,
private _running_time: number,
private _rating_name: string,
private _disc_format_name: string,
private _number_discs: number,
private _viewing_format_name: string,
private _aspect_ratio_name: string,
private _status: string,
private _release_date: string,
private _budget: number,
private _gross: number,
private _time_stamp:Date){
}
public toString = () : string => {
return `Movie (movie_id: ${this._movie_id},
title: ${this._title},
phase: ${this._phase},
category_name: ${this._category_name},
release_year: ${this._release_year},
running_time: ${this._running_time},
rating_name: ${this._rating_name},
disc_format_name: ${this._disc_format_name},
number_discs: ${this._number_discs},
viewing_format_name: ${this._viewing_format_name},
aspect_ratio_name: ${this._aspect_ratio_name},
status: ${this._status},
release_date: ${this._release_date},
budget: ${this._budget},
gross: ${this._gross},
time_stamp: ${this._time_stamp})`;
}
}
我们已经准备好领域模型。现在让我们更新MovieService中getMovies()函数的返回类型如下:
getMovies(): Movie[] {
let movies: Movie[] = [
{
"movie_id" : 1,
"title" : "Iron Man",
"phase" : "Phase One: Avengers Assembled",
"category_name" : "Action",
"release_year" : 2015,
"running_time" : 126,
"rating_name" : "PG-13",
"disc_format_name" : "Blu-ray",
"number_discs" : 1,
"viewing_format_name" : "Widescreen",
"aspect_ratio_name" : " 2.35:1",
"status" : 1,
"release_date" : "May 2, 2008",
"budget" : "140,000,000",
"gross" : "318,298,180",
"time_stamp" : "2015-05-03"
},
{
"movie_id" : 2,
"title" : "Spiderman",
"phase" : "Phase One",
"category_name" : "Action",
"release_year" : 2014,
"running_time" : 126,
"rating_name" : "PG-13",
"disc_format_name" : "Blu-ray",
"number_discs" : 1,
"viewing_format_name" : "Widescreen",
"aspect_ratio_name" : " 2.35:1",
"status" : 1,
"release_date" : "May 2, 2008",
"budget" : "140,000,000",
"gross" : "318,298,180",
"time_stamp" : "2015-05-03"
}
];
return movies;
}
MovieService的完整代码片段如下所示:
import { Injectable } from '@angular/core';
import { Movie} from './movie';
@Injectable()
export class MovieService {
getMovies(): Movie[] {
let movies: Movie[] = [
{
"movie_id" : 1,
"title" : "Iron Man",
"phase" : "Phase One: Avengers Assembled",
"category_name" : "Action",
"release_year" : 2015,
"running_time" : 126,
"rating_name" : "PG-13",
"disc_format_name" : "Blu-ray",
"number_discs" : 1,
"viewing_format_name" : "Widescreen",
"aspect_ratio_name" : " 2.35:1",
"status" : 1,
"release_date" : "May 2, 2008",
"budget" : "140,000,000",
"gross" : "318,298,180",
"time_stamp" : "2015-05-03"
},
{
"movie_id" : 2,
"title" : "Spiderman",
"phase" : "Phase One",
"category_name" : "Action",
"release_year" : 2014,
"running_time" : 126,
"rating_name" : "PG-13",
"disc_format_name" : "Blu-ray",
"number_discs" : 1,
"viewing_format_name" : "Widescreen",
"aspect_ratio_name" : " 2.35:1",
"status" : 1,
"release_date" : "May 2, 2008",
"budget" : "140,000,000",
"gross" : "318,298,180",
"time_stamp" : "2015-05-03"
}
];
return movies;
}
}
在这里,getMovies()函数返回类型为Movie[]的电影集合。
消费服务
我们已经准备好消费MovieService。让我们在一个组件中消费它。使用 Angular CLI,我们将通过执行以下命令来创建一个组件:
e:\Explore\packt\MovieDB>ng generate component movie
installing component
create src\app\movie\movie.component.css
create src\app\movie\movie.component.html
create src\app\movie\movie.component.spec.ts
create src\app\movie\movie.component.ts
e:\Explore\packt\MovieDB>
这将创建四个文件,分别是movie.component.ts、movie.component.html、movie.component.css和movie.component.spec.ts。movie.component.ts文件的代码片段如下所示:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-movie',
templateUrl: './movie.component.html',
styleUrls: ['./movie.component.css']
})
export class MovieComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
MovieComponent被@Component装饰器修饰,以及元数据,比如选择器、templateUrl和styleUrls。MovieService将被挂钩在ngOnInit方法下。让我们继续修改MovieComponent来消费MovieService。
首先,我们需要将MovieService导入到我们的组件MovieComponent中。这个导入语句为MovieComponent提供了对MovieService的引用。但是要消费MovieService,我们需要创建MovieService的实例。我们该如何做呢?在标准方式中,我们可以实例化MovieService如下:
let movieService = new MovieService();
在OnInit生命周期钩子方法中导入MovieService并实例化MovieService后的MovieComponent的代码片段如下所示:
import { Component, OnInit } from '@angular/core';
import { MovieService } from './movie.service';
import { Movie } from './movie';
@Component({
selector: 'app-movie',
templateUrl: './movie.component.html',
styleUrls: ['./movie.component.css']
})
export class MovieComponent implements OnInit {
movies : Movie[];
constructor() { }
ngOnInit() {
let movieService = new MovieService();
this.movies = movieService.getMovies();
}
}
在这里,当OnInit事件被触发时,MovieService被实例化,并且通过调用getMovies()函数来检索电影集合。电影列表将被分配给MovieComponent的movies属性,以便在模板中进一步使用。
创建一个异步服务
我们刚刚创建了一个名为MovieService的服务,它同步调用getMovies()方法来检索电影集合。由于我们正在消费外部来源,比如 Web API,来检索电影集合,我们的应用程序必须等待服务器响应电影列表,因为getMovies函数是同步的。
因此,我们需要实现一种异步机制来检索电影集合。通过这种方式,我们可以避免使我们的应用程序等待 Web API 响应电影集合。我们可以通过使用 Promise 来实现这一点。
什么是 Promise?
Promise是一个真诚的保证,表示将执行某个操作。当服务器响应结果时,它会回调一个函数。我们请求一个异步服务,并使用回调函数执行某些操作,服务会用结果或错误调用我们的回调函数。您可以在第七章中了解更多关于 Promise 的内容,使用可观察对象进行异步编程。
在服务中使用 Promise
让我们更新MovieService中的getMovies函数,以返回一个已解决的Promise,如下所示:
getMovies(): Promise<Movie[]> {
let movies: Movie[] = [
{
"movie_id" : 1,
"title" : "Iron Man",
"phase" : "Phase One: Avengers Assembled",
"category_name" : "Action",
"release_year" : 2015,
"running_time" : 126,
"rating_name" : "PG-13",
"disc_format_name" : "Blu-ray",
"number_discs" : 1,
"viewing_format_name" : "Widescreen",
"aspect_ratio_name" : " 2.35:1",
"status" : 1,
"release_date" : "May 2, 2008",
"budget" : "140,000,000",
"gross" : "318,298,180",
"time_stamp" : "2015-05-03"
},
{
"movie_id" : 2,
"title" : "Spiderman",
"phase" : "Phase One",
"category_name" : "Action",
"release_year" : 2014,
"running_time" : 126,
"rating_name" : "PG-13",
"disc_format_name" : "Blu-ray",
"number_discs" : 1,
"viewing_format_name" : "Widescreen",
"aspect_ratio_name" : " 2.35:1",
"status" : 1,
"release_date" : "May 2, 2008",
"budget" : "140,000,000",
"gross" : "318,298,180",
"time_stamp" : "2015-05-03"
}
];
return Promise.resolve(movies);
}
请注意,我们从getMovies函数中返回电影集合作为已解决的Promise。现在我们需要修改将电影集合分配给MovieComponent中的 movies 属性的代码。
MovieComponent中的现有代码将Promise分配给movies属性,而不是电影集合,因为MovieService中的getMovies现在返回已解决的Promise。因此,让我们修改ngOnInit事件的代码如下:
ngOnInit() {
let movieService = new MovieService();
movieService.getMovies().then(movies => this.movies = movies);
}
我们将我们的回调函数提供给Promise的then方法,所以getMovies中的链式函数then有命令将从 Web API 返回的电影集合分配给MovieComponent的属性this.movies。
在这里,应用程序不会等待MovieService返回电影集合。movies属性从回调函数中获取分配的电影列表。
摘要
很酷!这就是本章的结束。我们了解了在应用程序中实现服务的重要性和优势。我们还学习了如何在组件中使用服务。
然而,直接实例化MovieService是一个不好的方法。组件不需要知道如何实例化服务;它们的唯一目的是知道如何使用服务。服务还使组件能够与MovieServices的类型和它们的实例化方式紧密耦合。这是不可接受的;组件应尽可能松散耦合。
在下一章中,我们将讨论使用依赖注入将服务注入到组件中,这样我们就可以拥有松散耦合的组件。
第十二章:应用依赖注入
在本章中,您将学习关于 Angular 依赖注入。依赖注入是 Angular 中最引人注目的特性之一;它允许我们创建可注入对象,可以在各种组件之间作为共享资源使用。
在本章中,我们将讨论以下内容:
-
探索依赖注入
-
详细了解提供者类
-
了解分层依赖注入
-
创建可注入对象
-
学习将提供者注入到服务中
-
学习将提供者注入到组件中
-
学习为提供者类解析依赖项
-
使用
@Inject、provide和useValue装饰器创建示例
没有依赖注入的应用程序
如果没有依赖注入框架,开发人员的生活将非常艰难。看看不使用依赖注入的以下缺点:
-
每次需要传递构造函数参数时,我们都需要编辑类的构造函数定义
-
我们需要创建构造函数,并单独注入每个所需的依赖类
让我们看一个没有依赖注入的应用程序,以了解其中的挑战和不足之处:
class products {
available;
category;
constructor() {
this.available = new warehouse();
this.category = new category();
}
}
让我们分析前面的代码片段以更好地理解:
-
我们创建了一个名为
products的class。 -
在
constructor方法中,我们实例化了依赖类warehouse和category。 -
请注意,如果
warehouse或category类的构造函数定义发生更改,我们将需要手动更新所有类的实例。
由于作为开发人员,我们的任务是手动定义所有依赖项,因此前面的代码并不完全可测试和可维护。这就是 Angular 依赖注入的用武之地。
依赖注入 - 介绍
依赖注入(DI)是一种编码模式,其中一个类接收依赖项而不是自己创建它们。一些开发人员和技术狂人也将其称为设计模式。
它被广泛使用,通常被称为 DI。我们将在所有章节中将依赖注入系统称为 DI。
以下是我们绝对需要 DI 的原因:
-
DI 是一种软件设计模式,其中一个类接收其依赖项而不是创建对象本身
-
DI 创建并提供动态所需的对象
-
我们可以将可注入对象视为应用程序的可重用存储库
-
DI 允许远程开发团队独立开发依赖模块。
没有使用 DI,无法完全编写任何 Angular 应用程序。现在,让我们重新审视一下之前没有使用 DI 编写的代码,并使用 Angular DI 编写它:
class products {
constructor(private _warehouse: warehouse, private _category: category) {
// use _warehouse and _category now as reference
}
}
在前面的代码中发生了什么:
-
我们创建了一个
products类。 -
在
constructor中,我们将依赖类--warehouse和category--作为参数传递。 -
我们现在可以在整个类中使用实例
_warehouse和_category。 -
请注意,我们没有创建依赖类的对象;相反,我们只是通过 DI 系统接收它们。
-
我们不必担心
warehouse或category所需的依赖关系;这将由 Angular DI 在内部解决。
现在我们知道了什么是 Angular DI,让我们专注于它是如何在我们的 Angular 应用程序中实现和使用的。在学习提供者类和更多内容之前,我们应该了解一些关于 Angular DI 框架的基本知识。
当然,我们将在接下来的几节中详细介绍这些。了解基本概念是很好的:
-
@Injectable:这个装饰器标记一个类可供注入器实例化。 -
@Inject:使用@Inject装饰器,我们可以将配置对象注入到任何需要它的构造函数中。 -
Provider:提供者是我们注册需要注入的依赖项的方式。
现在让我们开始学习提供者类。
理解提供者类
要在我们的应用程序中开始使用 DI,我们需要了解提供者的概念。组件装饰器中的提供者配置告诉 Angular 需要提供哪些类给组件。
在提供者配置中,DI 接受一个类的数组,即我们要提供给组件的注入标记。我们还可以使用useClass指定要为注册的标记实例化的class。
快速查看使用提供者配置的语法:
@Component({
templateUrl: './calculate-tax.component.html',
styleUrls: ['./calculate-tax.component.css'],
providers: [MyTax]
})
在前面的代码中,我们告诉 Angular 前面的组件需要由MyTax类提供。
以下是使用提供者类的优点:
-
提供者是每个注入器维护的
-
每个
provider提供一个 Injectable 的单个实例 -
提供者类提供了调用方法的返回值
我们还可以明确提到应该从服务中使用的类。
这是一般的语法:
@Component({
templateUrl: './calculate-tax.component.html',
styleUrls: ['./calculate-tax.component.css'],
providers: [
{ provide: MyTax, useClass: MyTax }
]
})
在前面的代码片段中,我们明确告诉 Angular 注入MyTax提供者并使用useClass配置使用MyTax类。
让我们更多地了解提供者类如何注册和使用;让我们看一下以下的图表:
让我们详细分析前面的图表,以了解关键要点:
-
组件共享资源是通过提供者类提供的
-
提供者类可以注册到多个组件中(一个或多个)
-
我们还可以将提供者类注册到其他提供者类中
-
在前面的图表中,
组件#1依赖于提供者类#1 -
在前面的图表中,
组件#2依赖于提供者类#1和提供者类#2 -
在前面的图表中,
组件#3依赖于提供者类#2和提供者类#3
到目前为止,我们了解了 DI 对我们的应用程序有多么关键。DI 确实有助于组织数据,并且是实现独立模块或组件的最合适的设计模式。
这个想法是保持组件独立开发,并在提供者或可注入的地方编写更通用的共享或常用功能。
让我们快速创建一个提供者类的示例,它可以被注入到一个组件中。我们创建一个提供者类--MyTax.ts文件--并添加以下代码片段:
export class MyTax {
public taxValue: string;
constructor () {
}
getTaxes() {
this.taxValue=Math.round(Math.random()*100);
return this.taxValue;
}
}
让我们详细分析前面的代码片段:
-
我们创建了一个名为
MyTax的提供者类。 -
我们将一个
taxValue变量定义为数字。 -
我们创建了一个
getTaxes方法,它将返回一个随机数。 -
我们给
taxValue变量赋值,并通过getTaxes方法返回值。
现在,我们需要在我们组件的提供者数组配置中注册这个提供者类,并显示taxValue的值。
我们需要创建一个component类--calculate-tax.component.ts,并添加以下代码行:
import { Component } from '@angular/core';
import { MyTax } from './my-tax';
@Component({
template: `<p>tax option: {{ taxName }}</p>`,
styleUrls: ['./calculate-tax.component.css'],
providers: [MyTax]
})
export class CalculateTaxComponent{
public taxName: string;
constructor( _myTax : MyTax) {
this.taxName = _myTax.getTaxes();
}
}
让我们详细分析前面的代码:
-
我们导入了最近创建的提供者类--
MyTax。 -
我们创建并定义了
CalculateTax组件。 -
我们定义了一个
taxName变量,并使用数据绑定在模板中映射了这个变量。 -
在构造函数中,我们在应用程序模块的提供者数组中注册了
MyTax,Angular DI 将创建提供者类的实例并将其分配给_myTax。 -
使用提供类的实例,我们调用了
getTaxes方法。
运行应用程序,我们应该看到以下截图中显示的输出:
在本节中,您学习了如何创建提供程序类并在组件中注册它们以供使用。您可以将相同的提供程序类注册到多个组件中;在我们想要共享多个可重用方法的情况下,这无疑是理想的。
在下一节中,您将学习有关分层 DI 的知识--当我们有多个嵌套组件时。
理解分层 DI
在前面的部分中,我们介绍了通过提供程序类进行 DI,还介绍了在各个独立组件之间共享提供程序类。在本节中,您将学习如何在分层组件之间使用带有 DI 的提供程序类。
Angular 在内部创建了一个索引树,跟踪所有组件和正在创建的树结构,并维护其依赖矩阵,该矩阵在实时加载以提供所有必要的模块、服务和组件。
分层组件和各种组件之间的 DI 最好的部分是,我们不必担心这些依赖项是如何创建的,或者它们自身内部需要什么依赖项。
概述-分层组件和 DI
Angular 内部维护组件的树结构是一个公开的秘密。它还维护依赖项的树索引。
在任何真实的 Angular 应用程序中,我们将使用许多组件和服务。这些组件将具有从根组件到子组件和内部子组件等的树结构。
这在内部形成了一个组件树结构。由于我们的组件也将具有依赖项和可注入项,Angular 将在内部形成一个依赖项树矩阵,以跟踪和解析服务或组件所需的所有依赖项。
以下是您需要了解有关分层 DI 的关键事项:
-
Angular 框架在内部为组件创建了一个分层树结构的 DI
-
提供程序类需要注册到组件中
-
我们可以将提供程序类注册到其他提供程序类中
在下一节中,您将创建可注入的服务,并在组件中使用它们。
创建可注入项
我们不必创建 Angular 注入器,它是默认注入的。Angular 在引导过程中创建了一个应用程序范围的注入器。
我们使用@Injectable装饰器定义可注入的类,并在类中定义方法。@Injectable使得一个类可以被注入器实例化。
以下是创建@Injectable服务的示例代码:
import { Injectable } from '@angular/core';
@Injectable()
export class ListService {
getList() {
console.log("Demo Injectable Service");
}
}
让我们详细分析代码片段:
-
我们从 Angular 核心模块中导入了
Injectable。 -
我们使用
@Injectable装饰器告诉 Angular 以下类可以被注入,并且可以被注入器实例化。 -
我们创建了一个名为
ListService的类。 -
我们实现了一个名为
getList的方法,目前只是在console.log中打印一条消息。
注册提供者
注入器使用提供者创建依赖项。提供者需要在消费服务或组件中注册。通过注册它们,提供者类允许我们创建独立的可重用功能,可以由各个团队成员使用。
配置和注册提供者类还可以将功能分解为更小的模块,这样更容易维护和修改。我们可以以不同的方式将提供者类注册到服务和组件中。关于注入器,始终要牢记的重要点如下:
-
我们必须在我们的
NgModule、组件构造函数或指令中创建一个提供者 -
在组件的构造函数中注册服务
我们在前面的部分创建了一个ListService服务,它有一个方法,现在可以被注册并在多个组件中使用:
让我们详细分析前面的图表,以了解我们正在构建的用例:
-
我们将创建一个
@Injectable服务类ListService。 -
我们将创建一个名为
TestDiComponent的组件。 -
我们需要将
ListService注册到TestDiComponent中。
那么,现在让我们立即开始学习如何在ngModule中注册提供者。看一下ngModule文件:
import { ListService } from "./shared/list.service";
@NgModule({
providers: [
{
provide: ListService,
useClass: ListService
}
]
})
简而言之,上面的代码通常写成如下形式:
import { ListService } from "./shared/list.service";
@NgModule({
providers: [
ListService
]
})
让我们详细分析前面的代码片段:
-
我们已经将
ListService服务类导入到ngModule中。 -
请注意,我们在提供者中注册了
ListService。Angular 将在运行时内部解析并创建一个注入器。 -
在简写表示法中,我们只提到提供者的名称,Angular 将
provide属性映射到useClass的值以进行注册和使用。
在前面的部分中,您学会了如何在ngModule的提供者配置数组中注册服务。
在 AppModule 中注册提供者与在组件中注册提供者的主要区别在于服务的可见性。在 AppModule 中注册的服务在整个应用程序中都可用,而在特定组件内注册的服务只在该组件内可用。
在组件内注册提供者
现在,您将学习如何在组件中注册提供者并在组件内使用可注入的服务类。
首先,让我们使用 Angular CLI 的ng命令快速生成一个组件和服务:
ng g component ./test-di
这将生成组件和所需的文件。命令的输出如下所示:
现在,我们必须在同一文件夹中生成一个 Angular 服务。
ng g service ./test-di
上述命令的输出如下:
我们看到 Angular CLI 生成了一个警告消息,指出服务已生成但未提供。
到目前为止,我们已经分别创建了组件和服务,但现在我们需要在组件中注册提供者,以便我们可以使用该服务。
在继续在组件中注册提供者之前,让我们快速查看一下 CLI 工具生成的服务代码。
这是我们的test-di.service.ts文件代码:
import { Injectable } from '@angular/core';
@Injectable()
export class TestDiService {
constructor() { }
}
这是由脚手架 Angular CLI 工具生成的默认代码。让我们添加一个我们想在组件内访问的方法:
import { Injectable } from '@angular/core';
@Injectable()
export class TestDiService {
getAuthors() {
let Authors =[
{name :"Sridhar"},
{name: "Robin"},
{name: "John"},
{name: "Aditi"}
];
return Authors;
}
}
现在让我们在组件test-di.component.ts文件的 providers 数组中注册服务:
import { Component } from '@angular/core';
import { TestDiService } from './test-di.service';
@Component({
selector: 'app-test-di',
templateUrl: './test-di.component.html',
styleUrls: ['./test-di.component.css'],
providers: [TestDiService]
})
export class TestDiComponent{
constructor(private _testDiService: TestDiService) {}
authors = this._testDiService.getAuthors();
}
让我们详细分析上述代码:
-
我们创建了一个名为
TestDiComponent的组件。 -
我们将新创建的服务
TestDiService导入到组件中。 -
我们在 providers 中注册了
TestDiService,告诉 Angular 动态创建服务的实例。 -
Angular DI 将创建一个我们在
constructor中传递的_testDiService服务类的新private实例。 -
我们使用了
_testDiService服务的实例,并调用了getAuthors方法来获取作者列表。
运行应用程序,我们应该看到如下截图所示的输出:
到目前为止,您已经学会了创建一个Injectable服务,将服务注册到组件装饰器内的提供者数组中,并使用服务的实例来调用方法,这很棒。
在本节中,您学会了如何使用相同的一组共享提供者类创建多个组件。
带有依赖关系的提供者类
在前面的部分中,我们讨论了将服务注册到组件中,但是如果我们的服务本身需要一些依赖怎么办?在本节中,您将学习并实现解决服务所需依赖的方法。
为了更好地理解带有依赖关系的提供者类,让我们了解以下用例。我们有两个服务——CityService和TestDiService,以及一个组件——TestDiComponent。
让我们可视化这些服务和组件的依赖树:
让我们详细分析前面的图表,以了解我们正在构建的用例:
-
我们将创建一个
Injectable服务——CityService。 -
我们将创建一个
Injectable服务——TestDiService。 -
我们需要将
CityService注册到TestDiService类中。 -
我们将创建一个
TestDiComponent。 -
我们需要将
TestDiService注册到TestDiComponent中。
在本节中,我们将继续使用之前创建的服务TestDiService和组件TestDiComponent。
现在,我们将创建一个名为CityService的额外服务,并将文件保存为city.service.ts。
将以下代码片段添加到服务文件中:
import { Injectable } from '@angular/core';
@Injectable()
export class CityService {
getCities() {
let cities =[
{ name :"New York" },
{ name: "Dallas" },
{ name: "New Jersey" },
{ name: "Austin" }
];
return cities;
}
}
让我们分析前面的代码片段:
-
我们创建并导出了一个名为
CityService的新服务。 -
我们实现了一个
getCities方法,该方法返回一个城市列表。
创建服务后,我们导入文件并在app.module.ts文件中将服务注册为提供者:
import { CityService } from "./test-di/city.service";
@NgModule({
providers: [
CityService
]
})
由于我们在app.module.ts文件的 providers 数组中注册了该服务,它现在可以在整个应用程序中使用。
要在TestDiService中使用该服务,我们必须导入该服务并在构造函数中创建CityService的实例:
import { Injectable } from '@angular/core';
import { CityService } from './city.service';
@Injectable()
export class TestDiService {
constructor(private _city: CityService) { }
getAuthors() {
let Authors =[
{name :"Sridhar"},
{name: "Robin"},
{name: "John"},
{name: "Aditi"}
];
return Authors;
}
getCityList() {
let cities = this._city.getCities();
return cities;
}
}
在前面的部分提到的示例中,我们使用服务来显示作者列表。
现在,让我们分析前面的代码:
-
我们创建了一个名为
CityService的服务,并在TestDiService中导入了该类。 -
我们在构造方法中创建了
CityService类的一个实例——_City。 -
我们定义了一个方法,即
getAuthors。 -
使用
this运算符,我们在getCityList方法中调用了CityService类的getCities方法。 -
getCities方法返回城市列表。
运行应用程序,您将看到前面代码的输出,如下面的屏幕截图所示:
在本节中,您学习并实现了如何通过使用 @Injectable 装饰器注册其他提供者类来解决提供者类的依赖关系。
使用 @Inject、provide 和 useValue
让我们快速回顾一下学习 DI 的进展。我们讨论了如何编写提供者类和层次组件的依赖注入,以及如何使用 @injectable 装饰器编写可重用的提供者。
在本节中,您将学习如何使用 @Inject、provide 和 useValue 来在不同组件之间共享数据。
要声明一个服务可以在类中被注入,我们需要一个 @Injectable 装饰器。该装饰器告诉 Angular 将使用 @Injectable 定义的类可用于注入器,以便实例化到其他类、服务或组件中,并且该类应通过 DI 动态解析。我们主要用它们来编写通用服务并创建我们自己的存储库。
正如我们之前提到的,即使服务需要在其中注入依赖项,我们也使用 @Injectable 装饰器。我们还可以将服务注册到另一个服务或任何组件中。
每当我们需要注入构造函数参数的类型时,我们将使用 @inject 装饰器。
看一下 app.module.ts 文件中 ngModule 的以下示例代码:
import { ListService } from "./shared/list.service";
@NgModule({
providers: [
{
provide: ListService,
useClass: ListService
}
]
})
关于前面的代码,有一些要注意的要点:
-
我们导入了之前创建的服务,即
ListService。 -
现在我们已经导入了服务,我们需要将其添加到
providers列表中。 -
我们明确说明需要注册服务名
ListService。 -
使用
useClass,我们将指示 Angular 实例化并使用ListService类。
如果我们仔细注意,我们主要处理的是服务/提供者类。但是,如果我们需要注入某些变量,以便我们可以在不同的组件和服务之间共享值呢?
太棒了!这就是我们可以轻松使用 @Inject 装饰器并创建一个变量或类名,我们可以在其他组件和服务中重用。
现在看一下ngModule文件;我们已经修改它以适应我们想要在各种服务和组件之间共享的变量:
import { ListService } from "./shared/list.service";
@NgModule({
providers: [
{
provide : 'username',
useValue: 'Sridhar@gmail.com'
}
]
})
让我们分析前面的代码:
-
在提供者中,我们创建了一个新的条目,对于
provide,我们应用了一个名称username。请记住,无论您在这里提到的名称是什么,我们都需要在其他服务或组件中始终使用它。 -
我们为
username变量提供了一个值。 -
请注意,这个值不会被更改或更新;把它想象成应用程序中的一个常量值。
现在我们已经创建了一个值常量提供者,让我们看看如何在我们的组件中使用它。
在app.component.ts中,添加以下代码片段:
import { Component, Inject } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
}) export class AppComponent { title = 'Learning Angular - Packt Way';
constructor ( @Inject('username') private username ) {} }
让我们详细分析前面的代码片段:
-
我们从
@angular/core中导入了component和Inject模块。 -
我们创建了我们的组件,并为组件的 HTML 和样式表定义了相应的 HTML 和 CSS 文件。
-
在
AppComponent类中,我们定义了一个title变量并为其赋值。 -
我们创建了一个类的构造函数,并传递了一个
@inject装饰器来传递我们在app.module.ts文件中定义的username名称。 -
现在我们已经在提供者数组配置中注册了
username变量,我们可以在组件模板中的任何地方使用该变量的值。
太棒了,现在让我们运行这个应用程序;我们应该看到以下截图中显示的输出:
在以下截图中需要注意的一点是,以绿色标记的变量值'Sridhar@gmail.com'在模板中被打印出来。
在本节中,您学会了使用@Inject装饰器定义和使用常量提供者。
您学会了如何为我们的服务类使用@Injectable;我们可以将其注册为提供者,并在其他服务或组件中使用它。
我们可以定义一些常量变量,也可以注入和在不同组件中使用该值。
现在您应该能够创建多个可重用的服务、提供者类,以及常量变量,这些可以用来创建我们的应用程序存储库。
总结
在本章中,我们讨论了现在我们所知道的 Angular DI。DI 允许我们将提供者类和可注入对象注入到组件中使用提供者。我们学习并实现了提供者类和分层依赖注入。我们还学会了在NgModule中注册提供者,或者直接在组件内部注册提供者。
我们重点关注如何创建和配置注入器,以及如何在组件装饰器中注册服务提供者。
本章解释了提供者类也可以有依赖项,这些依赖项可以在内部再次注入到服务或组件中。在下一章中,您将学习关于 Angular 动画。Angular 动画是一个核心库,通过将动作和过渡应用到应用程序中,提供更好的用户体验。
我们将学习各种过渡和动作,以及如何设计动画;最重要的是,我们将在学习过程中创建一些很酷的东西。