Angular 简介

1. 简介

构建内容

在此 Codelab 中,您将使用 Angular 构建一个外壳应用。完成后的应用将能够根据用户搜索来查看住宅详情以及查看住宅位置的详细信息。

您可以利用 Angular 强大的工具和出色的浏览器集成功能,通过 Angular 构建所有内容。

这是您今天将构建的应用

学习内容

  • 如何使用 Angular CLI 构建新项目。
  • 如何使用 Angular 组件构建界面。
  • 如何在应用的组件和其他部分中共享数据。
  • 如何在 Angular 中使用事件处理程序。
  • 如何使用 Angular CLI 将应用部署到 Firebase Hosting。

您需要满足的条件

  • 具备 HTML、CSS、TypeScript(或 JavaScript)、Git 和命令行方面的基础知识。

2. 环境设置

设置本地环境

为完成此 Codelab,您需要在本地机器上安装以下软件:

安装 Angular CLI

配置完所有依赖项后,您可以通过计算机上的命令行窗口安装 Angular CLI:

npm install -g @angular/cli

要确认您的配置是否正确,请在您的计算机命令行中运行以下命令:

ng –version

如果该命令运行成功,您将看到类似于以下屏幕截图的消息。

显示 Angular 版本的 Angular CLI 输出

获取代码

此 Codelab 的代码包含不同分支中的中间步骤和最终解决方案。首先,从 GitHub 下载代码

  1. 打开新的浏览器标签页,然后转到 https://github.com/angular/introduction-to-angular
  2. 从命令行窗口分支,将代码库和 cd introduction-to-angular/ 克隆到代码库中。
  3. 在起始代码分支中,输入 git checkout get-started
  4. 在您的首选代码编辑器中打开该代码,然后打开 introduction-to-angular 项目文件夹。
  5. 从命令行窗口中,运行 npm install 以安装运行服务器所需的依赖项。
  6. 要在后台运行 Angular 网络服务器,请打开一个单独的命令行窗口,然后运行 ng serve 来启动服务器。
  7. 打开一个浏览器标签页以http://localhost:4200

随着应用正常运行,您可以开始构建 Fairhouse 应用。

3.创建您的首个组件

组件是 Angular 应用的核心构建块。把组件看作用于建造的砖块。一开始,砖块的能量并不是很大,但与其他砖块结合在一起,就能打造出令人惊叹的建筑。

使用 Angular 构建的应用也是如此。

组件有 3 个主要方面:

  • 模板的 HTML 文件。
  • 样式的 CSSfile。
  • 与应用行为对应的 TypeScript 文件。

您要更新的第一个组件是 AppComponent

  1. 在代码编辑器中打开 app.component.html;这是 AppComponent 的模板文件。
  2. 删除此文件中的所有代码,并将其替换为以下代码:
<main>
  <header><img src="../assets/logo.svg" alt="fairhouse">Fairhouse</header>
  <section>
  </section>
</main>
  1. 保存代码并检查浏览器。在开发服务器运行时,这些更改在我们保存时反映在浏览器中。

恭喜!您已成功更新第一个 Angular 应用。敬请期待更多精彩应用。让我们继续。

接下来,您将为搜索添加一个文本字段,并向界面添加一个按钮。

组件有许多优势,其中一个是能够组织界面。您将要创建一个包含文本字段、搜索按钮并最终包含位置列表的组件。

如需创建此新组件,请使用 Angular CLI。Angular CLI 是一套命令行工具,可协助构建基架、部署等。

  1. 从命令行输入:
ng generate component housing-list

以下是此命令的部分内容:

  • ng 是 Angular CLI。
  • 该命令会生成要执行的操作类型。在这种情况下,为某些内容生成基架。
  • 该组件表示我们要创建的“内容”。
  • home-list 是组件的名称。
  1. 接下来,将新组件添加到 AppComponent 模板中。在 app.component.html 中,更新模板代码:
<main>
  <header><img src="../assets/logo.svg" alt="fairhouse">Fairhouse</header>
 <section>
   <app-housing-list></app-housing-list>
 </section>
</main>
  1. 保存所有文件并返回浏览器,以确认是否显示了消息 housing-list works
  2. 在代码编辑器中,转到 housing-list.component.html,移除现有代码,然后将其替换为:
<label for="location-search">Search for a new place</label>
<input id="location-search" placeholder="Ex: Chicago"><button>Search</button>
  1. housing-list.component.css 中,添加以下样式:
input, button {
    border: solid 1px #555B6E;
    border-radius: 2px;
    display: inline-block;
    padding: 0;
}
input {
    width: 400px;
    height: 40px;
    border-radius: 2px 0 0 2px;
    color: #888c9c;
    border: solid 1px #888c9c;
    padding: 0 5px 0 10px;
}

button {
    width: 70px;
    height: 42px;
    background-color: #4468e8;
    color: white;
    border: solid 1px #4468e8;
    border-radius: 0 2px 2px 0;
}

article {
    margin: 40px 0 10px 0;
    color: #202845;
}
article, article > p {
    color: #202845;
}

article> p:first-of-type {
    font-weight: bold;
    font-size: 22pt;
}

img {
    width: 490px;
    border-radius: 5pt;
}

label {
    display: block;
    color: #888c9c;
}

  1. 保存文件,然后返回浏览器。应用现在有搜索框和按钮

我们的 Angular 应用即将开始塑造。

4.事件处理

应用有输入字段和按钮,但没有互动。在网页上,您通常需要与控件互动并调用事件和事件处理程序的使用。您将使用此策略来构建您的应用。

您将在housing-list.component.html中进行这些更改。

要添加点击处理程序,您需要向该按钮添加事件监听器。在 Angular 中,语法是将事件名称括在圆括号中并为其分配一个值。在这里,您可以为点击按钮时调用的方法命名。我们将其命名为 searchHousingLocations,请务必将括号添加到此函数名称的末尾以调用该函数。

  1. 更新按钮代码以匹配以下代码:
<button (click)="searchHousingLocations()">Search</button>
  1. 保存此代码并检查浏览器。现在存在编译错误:

46a528b5ddbc7ef8.png

由于 searchHousingLocations 方法不存在,应用会抛出此错误,因此您需要进行更改。

  1. housing-list.component.ts 中,在 HousingListComponent 类的正文末尾添加一个新方法:
 searchHousingLocations() {}

您很快便会填写此方法的详细信息。

  1. 保存此代码即可更新浏览器并解决相应错误。

下一步是获取输入字段的值,并将其作为参数传递给 searchHousingLocations 方法。您将使用名为模板变量的 Angular 功能,该功能提供对模板中的元素的引用并与其交互。

  1. housing-list.component.html 中,添加一个名为 search 的属性,将 # 标签作为输入的前缀。
<label for="location-search">Search for a new place</label>
<input id="location-search" #search><button (click)="searchHousingLocations()">Search</button>

现在,我们有了对输入的引用。我们还可以访问输入的 .value 属性。

  1. 将输入的值传递给 searchHousingLocations 方法。
<input id="location-search" #search><button (click)="searchHousingLocations(search.value)">Search</button>

到目前为止,您一直在传递该参数作为参数,但让我们更新一下使用该参数的方法。目前,该参数会在 console.log 命令中使用,之后还会用作搜索参数。

  1. housing-list.component.ts 中,添加以下代码:
 searchHousingLocations(searchText: string) {
   console.log(searchText);
 }
  1. 保存代码,然后在浏览器中打开 Chrome 开发者工具,然后转到 Console(控制台)标签页。在输入中输入任意值。选择搜索,并验证该值是否显示在 Chrome 开发者工具的控制台标签页中。

chrome devtools 控制台输出与界面匹配的搜索文本

您已成功添加事件处理程序,应用可以接受用户的输入。

5. 搜索结果

下一步是根据用户输入显示结果。每个位置都具有以下属性:字符串、名称、城市、州/省、照片、availableUnits 的数字属性以及两个洗衣属性和 Wi-Fi 布尔值属性:

name: "Location One",
city: "Chicago",
state: "IL",
photo: "/path/to/photo.jpg",
availableUnits: 4,
wifi: true,
laundry: true

您可以将这些数据表示为普通 JavaScript 对象,但最好在 Angular 中使用 TypeScript 支持。使用类型有助于避免构建时发生错误。

我们可以使用类型来定义数据的特征,也称为“塑造数据”。在 TypeScript 中,接口通常用于此目的。我们来创建一个用于表示住房位置数据的接口。在编辑器的终端中,使用 Angular CLI 创建 HousingLocation 类型。

  1. 为此,请输入:
ng generate interface housing-location
  1. housing-location.ts 中,添加接口的类型详细信息。根据我们的设计,为每个属性指定适当的类型:
export interface HousingLocation {
  name: string,
  city: string,
  state: string,
  photo: string,
  availableUnits: number,
  wifi: boolean,
  laundry: boolean,
}
  1. 保存文件并打开 app.component.ts
  2. 通过从 ./housing-location 导入住址位置接口来创建包含代表住房位置数据的数组。
import { HousingLocation } from './housing-location';
  1. 更新 AppComponent 类,使其包含名为 housingLocationList、类型为 HousingLocation[] 的属性。使用以下值填充数组:
housingLocationList: HousingLocation[] = [
  {
    name: "Acme Fresh Start Housing",
    city: "Chicago",
    state: "IL",
    photo: "../assets/housing-1.jpg",
    availableUnits: 4,
    wifi: true,
    laundry: true,
  },
  {
    name: "A113 Transitional Housing",
    city: "Santa Monica",
    state: "CA",
    photo: "../assets/housing-2.jpg",
    availableUnits: 0,
    wifi: false,
    laundry: true,
  },
  {
    name: "Warm Beds Housing Support",
    city: "Juneau",
    state: "AK",
    photo: "../assets/housing-3.jpg",
    availableUnits: 1,
    wifi: false,
    laundry: false,
  }
];

您无需通过实例化类的新实例来获取对象,我们便可利用该接口提供的类型信息。对象中的数据必须具有相同的“形状”,也就是说,它必须与接口上定义的属性匹配。

数据存储在 app.component.ts 中,但我们需要与其他组件共享这些数据。一种解决方案是使用 Angular 中的服务,但为了降低应用的复杂性,我们将使用 Angular 提供的输入修饰器。输入修饰器允许组件从模板接收值。您将使用它与 HousingListComponent 共享 housingLocationList 数组。

  1. housing-list.component.ts 中,从 @angular/core 导入 input,并从 ./housingLocation 导入 HousingLocation
import { Component, OnInit, Input } from '@angular/core';
import {HousingLocation } from '../housing-location';
  1. 在组件类的正文中创建一个名为 locationList 的属性。您将使用 Input 作为 locationList 的修饰器。
export class HousingListComponent implements OnInit {

  @Input() locationList: HousingLocation[] = [];
  ...
}

此属性的类型设置为 HousingLocation[]

  1. app.component.html 中,更新 app-housing-list 元素以包含名为 locationList 的属性,并将值设置为 housingLocationList
<main>
 ...
 <app-housing-list [locationList]="housingLocationList"></app-housing-list>
</main>

locationList 属性必须用方括号 ( [ ]) 括起来,以便 Angular 可以将 locationList 属性的值动态绑定到变量或表达式。否则,Angular 将等号右侧的值视为字符串。

如果您此时遇到任何错误,请检查:

  • 输入属性名称拼写与 TypeScript 类中属性的拼写一致。这种情况也很重要。
  • 等号右侧的属性名称准确无误。
  • 输入属性用方括号括起来。

数据共享配置已完成!下一步是在浏览器中显示结果。由于数据采用数组格式,我们需要使用 Angular 功能,让您可以循环遍历模板 *ngFor 中的数据和重复元素。

  1. housing-list.component.html 中,更新模板中的文章元素以使用 *ngFor,以便在浏览器中显示数组条目:
<article *ngFor="let location of locationList"></article>

分配给 ngFor 属性的值是 Angular 模板语法。它会在模板中创建一个局部变量。Angular 会在开始标记和结束标记之间的 article 元素范围内使用局部变量。

如需详细了解 ngFor模板语法,请参阅 Angular 文档

ngForlocationList 数组的每个条目重复一篇文章元素。接下来,将显示位置变量中的值。

  1. 更新此模板以添加段落元素 (<p>)。段落元素的子元素是 location 属性中的插值:
<input #search><button (click)="searchHousingLocations(search.value)">Search</button>
<article *ngFor="let location of locationList">
   <p>{{location.name}}</p>
</article>

在 Angular 模板中,您可以使用文本插值来显示带有双大括号 ({{ }}) 语法的值。

  1. 保存并返回浏览器。现在,应用会为 locationList 数组中的每个数组条目显示一个标签。

显示了 3 个住房位置的列表

数据从应用组件分享到住房列表组件,我们会逐一迭代上述各个值,以在浏览器中显示它们。

我们刚刚介绍了一些可在组件之间共享数据的方法,使用了某些新的模板语法和 ngFor 指令。

6. 过滤搜索结果

目前,该应用根据用户的搜索内容显示所有结果,而不是显示结果。如需更改此行为,您需要更新 HousingListComponent,以使应用按预期运行。

  1. housing-list.component.ts 中,更新 HousingListComponent 以创建一个名为 results 且类型为 HousingLocation[] 的新属性:
export class HousingListComponent implements OnInit {

 @Input() locationList: HousingLocation[] = [];
 results: HousingLocation[] = [];
 ...

results 数组表示与用户搜索匹配的住址。下一步是更新 searchHousingLocations 方法以过滤值。

  1. 移除 console.log 并更新代码,将结果属性分配给对 locationList 进行过滤的输出(按 searchText 过滤):
searchHousingLocations(searchText: string) {
  this.results = this.locationList.filter(
  (location: HousingLocation) => location.city
    .toLowerCase()
    .includes(
        searchText.toLowerCase()
  ));
}

在此代码中,我们使用数组过滤方法,只接受包含 searchText 的值。所有值均使用小写形式的字符串。

两点注意事项:

  • 在方法内引用某个类的属性时,必须使用 this 前缀。这就是我们使用 this.resultsthis.locationList 的原因。
  • 此处的搜索功能仅与营业地点的 city 属性匹配,但您可以更新代码以包含更多属性。

尽管此代码按原样运行,但您可以对其进行改进。

  1. 更新代码,以防止 searchText 为空时在数组中进行搜索:
searchHousingLocations(searchText: string) {
  if (!searchText) return;
  ...
}

方法已更新,您需要对模板做出修改,然后结果在浏览器中显示。

  1. housing-location.component.html 中,将 ngFor 中的 locationList 替换为 results
<article *ngFor="let location of results">...</article>
  1. 保存代码并返回浏览器。使用输入值,从示例数据中搜索位置(例如,芝加哥)。

该应用仅显示匹配的结果:

与在搜索字段中输入的文字相匹配的搜索结果

您已完成将用户输入与搜索结果完全链接所需的其他功能。应用即将完成。

接下来,系统将显示关于该应用的更多详细信息,以完成保存。

7. 显示详细信息

应用需要支持点击搜索结果以及在详细信息面板中显示信息。HousingListComponent 知道点击哪个结果,因为在该组件中显示数据。我们需要一种方法来将 HousingListComponent 中的数据共享给父级组件 AppComponent

在 Angular 中,@Input() 将数据从父项发送到子项,而 @Output() 允许组件将数据从子项发送到其父组件。输出修饰器使用 EventEmitter 将任何事件通知所有监听器。在这种情况下,您需要发出一个代表搜索结果点击的事件。您要随选择事件一起发送所选内容作为载荷的一部分。

  1. housing-list.component.ts 中,更新 import 以包含 @angular/core 和来自其位置的 HousingLocation 中的 OutputEventEmitter
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { HousingLocation } from '../housing-location';
  1. HousingListComponent 的正文中,更新代码以添加一个名为 locationSelectedEvent 且类型为 EventEmitter<HousingLocation>(); 的新属性:
@Output() locationSelectedEvent = new EventEmitter<HousingLocation>();

locationSelectedEvent 属性使用 @Output() 进行修饰,这使它成为此组件的 API 的一部分。借助 EventEmitter,您可以通过为类提供 HousingLocation 类型来利用泛型 API。当 locationSelectedEvent 发出某个事件时,该事件的监听器可以预期任何附加的数据的类型为 HousingLocation。此类类型可以保障我们的开发并降低出现某些错误的可能性。

当用户点击列表中的营业地点时,我们需要触发 locationSelectedEvent

  1. 更新 HousingListComponent 以添加名为 selectLocation 的新方法,该方法接受 housingLocation 类型的值作为参数:
selectHousingLocation(location: HousingLocation) { }
  1. 在该方法的正文中,从 locationSelectedEvent 发出器发出新的事件。发出的值是用户选择的位置。
selectHousingLocation(location: HousingLocation) {
  this.locationSelectedEvent.emit(location);
}

让我们将其关联到模板。

  1. housing-list-component.html 中,更新文章元素以添加一个包含 click event 的新按钮子级。此事件调用 TypeScript 类中的 selectHousingLocation 方法,并传入对所点击 location 的引用作为参数。
<article *ngFor="let location of results" >
  <p>{{location.name}}</p>
  <button (click)="selectHousingLocation(location)">View</button>
</article>

住房位置现在有一个可点击的按钮,您可以将值传递回组件。

该流程的最后一步是更新 AppComponent 以监听事件,并相应地更新显示内容。

  1. app.component.html 中,更新 app-housing-list 元素以监听 locationSelectedEvent 并使用 updateSelectedLocation 方法处理事件:
<app-housing-list [locationList]="housingLocationList" (locationSelectedEvent)="updateSelectedLocation($event)"></app-housing-list>

在处理模板中的事件处理脚本时,Angular 提供了 $event。$event 参数是 HousingLocation 类型的对象,因为这是 EventEmitter 的类型参数。Angular 会为您处理所有这一切。您只需确认您的模板正确无误即可。

  1. app.component.ts 中,更新代码以包含名为 selectedLocation 且类型为 HousingLocation | undefined 的新属性。
selectedLocation: HousingLocation | undefined;

它使用称为联合类型的 TypeScript 功能。联合可让变量接受多种类型中的一种。在本例中,您希望 selectedLocation 的值为 HousingLocationundefined,因为您没有为 selectedLocation 指定默认值。

您需要实现 updateSelectedLocation

  1. 添加一个名为 updateSelection 的新方法,该方法使用一个名为 location 且类型为 HousingLocation 的参数。
updateSelectedLocation(location: HousingLocation) { } searchHousingLocations() {}
  1. 在该方法的正文中,将 selectedLocation 的值设置为 location 参数:
updateSelectedLocation(location: HousingLocation) {
  this.selectedLocation = location;
}

完成此部分后,最后一步是更新模板以显示所选位置。

  1. app.component.html 中,添加一个新的 <article> 元素,我们将使用它来显示所选位置的属性。使用以下代码更新模板:
<article>
  <img [src]="selectedLocation?.photo">
  <p>{{selectedLocation?.name}}</p>
  <p>{{selectedLocation?.availableUnits}}</p>
  <p>{{selectedLocation?.city}}, {{selectedLocation?.state}}</p>
  <p>{{selectedLocation?.laundry ? "Has laundry" : "Does Not have laundry"}}</p>
  <p>{{selectedLocation?.wifi ? "Has wifi" : "Does Not have wifi"}}</p>
 </article>

由于 selectedLocation 可以是 undefined,因此您可以使用可选链运算符从媒体资源中检索值。此外,您还对 wifilaundry 布尔值使用三元语法。这样就可以根据值显示自定义消息。

  1. 保存代码并检查浏览器。搜索地点,然后点击某个位置以显示详细信息:

两列布局;左侧显示的是搜索结果,右侧显示所选的位置详情

看起来不错,但仍有一个问题需要解决。网页最初加载时,详细信息面板中有一些文字工件不应显示。Angular 可通过一些方式有条件地显示您将在下一步中使用的内容。

默认界面,屏幕上显示的工件不正确

目前,我们对该应用的进展充满期待。以下是您目前为止已经实现的内容:

  • 您可以使用输出修饰器和 EventEmitter 将数据从子组件共享给父组件。
  • 您还已成功允许用户输入该值,然后使用该值进行搜索。
  • 应用可以显示搜索结果,用户可以点击查看更多详情。

到目前为止,您做得非常好。我们来更新模板并完成应用。

8. 美化模板

界面目前包含应有条件显示的详细信息面板中的文本工件。我们将使用两个 Angular 功能,即 ng-container*ngIf

如果您将 ngIf 指令直接应用于 article 元素,它会在用户做出第一次选择时导致布局偏移。为了改善这种体验,您可以将位置详情封装在作为 article 子级的另一个元素中。该元素没有任何样式或函数,只是向 DOM 添加了权重。为避免出现这种情况,您可以使用 ng-container。您可对其应用指令,但这些指令不会显示在最终 DOM 中。

  1. app.component.html 中,更新 article 元素以匹配以下代码:
<article>
  <ng-container>
  <img [src]="selectedLocation?.photo">
  <p>{{selectedLocation?.name}}</p>
  <p>{{selectedLocation?.city}}, {{selectedLocation?.state}}</p>
  <p>Available Units: {{selectedLocation?.availableUnits}}</p>
  <p>{{selectedLocation?.laundry ? "Has laundry" : "Does Not have laundry"}}</p>
  <p>{{selectedLocation?.wifi ? "Has wifi" : "Does Not have wifi"}}</p>
  </ng-container>
</article>
  1. 接下来,将 *ngIf 属性添加到 ng-container 元素中。值应当为 selectedLocation
<article>
  <ng-container *ngIf="selectedLocation">
  ...
  </ng-container>
</article>

现在,仅当 selectedLocationTruthy 时,应用才会显示 ng-container 元素的内容。

  1. 保存此代码,并确认浏览器在网页加载时不再显示文字伪影。

最后一项更新就是,我们可以对应用进行更新。在 housing-location.component.html 的搜索结果中显示更多详细信息。

  1. housing-location.component.html 中,将代码更新为:
<label for="location-search">Search for a new place</label>
<input id="location-search" #search placeholder="Ex: Chicago"><button
    (click)="searchHousingLocations(search.value)">Search</button>
<article *ngFor="let location of results" (click)="selectHousingLocation(location)">
  <img [src]="location.photo" [alt]="location.name">
  <p>{{location.name}}</p>
  <p>{{location.city}}, {{location.state}}</p>
  <button (click)="selectHousingLocation(location)">View</button>
</article>
  1. 保存代码,并返回浏览器以显示已完成的应用。

两列应用:左侧显示搜索结果,右侧显示所选搜索结果的详细信息

现在,该应用看起来很棒,并且完全正常运行。非常棒。

9. 恭喜

感谢您走上这段旅程,使用 Angular 构建 Fairhouse。

您使用 Angular 创建了一个界面。您已使用 Angular CLI 创建了组件和接口。然后,您使用 Angular 中强大的模板功能构建了一个功能性应用,用于显示图像、处理事件等。

后续步骤

如果您想继续构建功能,请参考以下建议:

  • 数据是在应用中硬编码的。进行的重要重构是添加一项服务来包含这些数据。
  • 详情页面目前显示在同一个页面上,但将详细信息移到他们自己的页面上并利用 Angular 路由会很棒。
  • 另一个更新是将数据托管在静态端点,并使用 Angular 中的 HTTP 软件包在运行时加载数据。

大量的娱乐机会。