当使用Angular构建用户界面时,我们需要一个有效的模板,它可以定义在内联,也可以定义在一个专门的html文件中。模板本身是用html创建的,为了赋予html特殊的能力,我们使用了Angular的数据绑定和指令。Angular使得建立这些用户界面变得更加容易,因为数据绑定提供了动态数据的显示,而事件允许Angular应用程序对用户的操作做出适当的反应。Angular指令提供了向html添加逻辑的能力,这是它默认不具备的。
内联模板的优势
到目前为止,我们已经开始在文件中创建一个内联模板 app.component.ts文件中创建内联模板。虽然不一定是最好的选择,但也有一些优势。
- 模板被直接定义在相关的组件内
- 将视图和该视图的代码保持在一个地方
- 使得数据绑定与类属性的匹配更容易。
建立一个链接模板
即使有上面列出的优点,使用链接模板往往也是有意义的。内联模板在各种IDE开发工具(如Visual Studio Code、Jetbrains Webstorm等)中的效果并不理想。通常情况下,类似intellisense的功能和自动格式化都无法工作。因此,随着所需的html数量的增加,利用这种链接模板的方法变得更加容易。让我们建立一个可以列出视频游戏的组件。
构建一个游戏列表组件
按照惯例,创建专门的文件夹来存放每个组件是有意义的。因此,我们可以首先创建一个 games文件夹来存放游戏相关的组件。

现在,让我们在这个新的文件夹中添加游戏列表组件的模板。它将被命名为game-list.component.html。

在我们的模板文件中,可以使用下面的html来开始建立一个显示一些游戏的布局。
<div class='card'>
<div class='card-header'>
Games List
</div>
<div class='card-body'>
<div class="row">
<div class="col-3">
<input type="text" class="form-control" id="filterInput" placeholder="Type to filter">
</div>
<div class="col">
<div class="form-text text-muted">Filtered by:</div>
</div>
</div>
<div class='table-responsive'>
<table class='table'>
<thead>
<tr>
<th>
<button class='btn btn-primary'>
Hide Image
</button>
</th>
<th>Game</th>
<th>Part#</th>
<th>Release Date</th>
<th>Cost</th>
<th>5 Thumb Rating</th>
</tr>
</thead>
</table>
</div>
</div>
<div class="card-footer text-muted text-right">
<small>Powered by Angular</small>
</div>
</div>
模板文件现在已经到位了。接下来要做的事情是创建组件本身。我们将其命名为game-list.component.ts。

下面的Typescript代码将从Angular核心中导入Component装饰器,定义选择器,使用属性链接到我们刚刚创建的模板 **templateUrl**属性链接到我们刚刚创建的模板,最后还设置了一个简单的 **pageTitle**属性,所以我们可以在视图中使用这些数据。
import {Component} from '@angular/core';
@Component({
selector: 'game-list',
templateUrl: './game-list.component.html'
})
export class GameListComponent {
pageTitle = 'Game List';
}
如果你的IDE给你一个信息,如*ng.*component'GameListComponent',你就会发现它的名字是 "GameList"。组件'GameListComponent'没有包含在一个模块中,在模板中也不能使用。请 考虑将其添加到NgModule声明中,别担心,我们很快会解决这个问题。
使用组件作为指令
当一个组件有一个选择器被定义了,就像我们上面用games-list做的那样,我们可以把这个组件作为一个指令来使用。这是什么意思呢?这意味着我们可以在任何其他组件的模板中把game-list选择器作为一个html元素使用。所以要把game-list.component.html模板放在app.component.ts里面,我们可以这样做。
import {Component} from '@angular/core';
@Component({
selector: 'game-root',
template: `
<div><h1>{{pageTitle}}</h1>
<game-list></game-list>
</div>`
})
export class AppComponent {
pageTitle = 'Angular Games Viewer';
}
配置Angular模块
为了使用一个组件作为指令,Angular需要知道如何渲染这个自定义的html元素。为了实现这一点,需要配置与该组件相关的Angular模块。每个angular应用程序都需要至少有一个angular模块,这通常是AppModule。一个组件需要只属于一个Angular Module。因此,当一个组件包含一个指令时,Angular会查看该组件的模块,看看该组件可以看到哪些指令。这意味着我们需要在AppModule中声明或导入该组件。
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {AppComponent} from './app.component';
import {GameListComponent} from './games/game-list.component';
@NgModule({
declarations: [
AppComponent,
GameListComponent
],
imports: [BrowserModule],
bootstrap: [AppComponent]
})
export class AppModule {
}
测试应用程序
好了!这一切都结束了。在所有这些工作结束后,我们可以启动应用程序,看看我们得到了什么。导航到你项目的根目录,运行 ng serve命令。当我们在浏览器中访问该应用程序时,看起来我们已经能够让我们的自定义指令工作了!我们使用了游戏列表组件。我们使用了game-list组件作为指令。

添加动态数据
现在,应用程序中只有静态数据。让我们通过使用数据绑定来改变这种情况。我们可以做的第一件事是访问game-list.component.html文件,删除硬编码的页面标题,用一个插值来代替,如 {{pageTitle}}.
<div class='card'>
<div class='card-header'>
{{pageTitle}}
</div>
...
现在在game-list.component.ts文件中,我们可以设置这个属性。我们将它设置为Dynamic!游戏列表,这样它就和我们之前的硬编码值不同了。
import {Component} from '@angular/core';
@Component({
selector: 'game-list',
templateUrl: './game-list.component.html'
})
export class GameListComponent {
pageTitle = 'Dynamic! Game List';
}
如果我们重新访问该应用程序,它现在应该显示该变量的数据。 pageTitle变量的数据,而且它确实显示了!

介绍Angular *ngIf
我们了解到,指令是一个自定义的html元素或属性,它具有超出正常html的特殊能力。指令有自定义和内置两种。*ngIf指令就是这样一个内置的angular指令。它被用来给html添加逻辑,也被称为结构指令。这是因为它有能力通过添加、删除或以其他方式操作元素及其子元素来修改模板的布局或结构。如果一个angular指令以星号**(***)开头,那么你就知道它是一个结构指令。让我们看看这个动作。
我们只想在有游戏的时候显示显示游戏的表格。如果没有游戏,那么显示这个表格就没有意义了。因此,为了模拟没有游戏需要显示,我们将在game-list.component中创建一个 games属性,并将其设置为一个空数组。
import {Component} from '@angular/core';
@Component({
selector: 'game-list',
templateUrl: './game-list.component.html'
})
export class GameListComponent {
pageTitle = 'Dynamic! Game List';
games = [];
}
现在,在实际的模板文件中,我们可以使用该属性 *ngIf来检查是否有任何游戏可供显示。这将做的是让
<div class='card'>
<div class='card-header'>
{{pageTitle}}
</div>
<div class='card-body'>
<div class="row">
<div class="col-3">
<input type="text" class="form-control" id="filterInput" placeholder="Type to filter">
</div>
<div class="col">
<div class="form-text text-muted">Filtered by:</div>
</div>
</div>
<div class='table-responsive'>
<table class='table' *ngIf='games && games.length'>
<thead>
<tr>
<th>
<button class='btn btn-primary'>
Hide Image
</button>
</th>
<th>Game</th>
<th>Part#</th>
<th>Release Date</th>
<th>Cost</th>
<th>5 Thumb Rating</th>
</tr>
</thead>
</table>
</div>
</div>
<div class="card-footer text-muted text-right">
<small>Powered by Angular</small>
</div>
</div>
由于没有游戏要显示,那么表格就不应该再出现。看起来很好!

介绍一下Angular *ngFor
在Angular中,你需要有能力在项目的集合上循环并显示它们。在我们的例子中,一旦我们有了一些游戏,我们就应该能够在这些游戏上进行循环并在我们的表格中显示它们。要做到这一点,我们可以使用 *ngFor.这个结构指令为一个可迭代列表(如数组)中的每个项目重复一次dom树的一部分。我们要做的是在 games在game-list.component.ts中用一些JSON数据来表示几个游戏的变量。在未来,我们将把它设置为一个服务,从服务器上获取数据。
import {Component} from '@angular/core';
@Component({
selector: 'game-list',
templateUrl: './game-list.component.html'
})
export class GameListComponent {
pageTitle = 'Dynamic! Game List';
games = [
{
"gameId": 1,
"gameName": "Castlevania",
"gameCode": "AAA-0101",
"releaseDate": "September 26, 1986",
"description": "Action-adventure game series created and developed by Konami.",
"price": 19.99,
"thumbRating": 5.0,
"imageUrl": "./assets/images/castlevania.png"
},
{
"gameId": 2,
"gameName": "Dr Mario",
"gameCode": "AAA-1100",
"releaseDate": "July 27, 1990",
"description": "Action puzzle game produced by Gunpei Yokoi and published by Nintendo.",
"price": 15.99,
"thumbRating": 3,
"imageUrl": "./assets/images/drmario.png"
}
];
}
现在,在game-list.component.html中,我们可以利用*ngFor结构指令来循环显示两个游戏。
<div class='card'>
<div class='card-header'>
{{pageTitle}}
</div>
<div class='card-body'>
<div class="row">
<div class="col-3">
<input type="text" class="form-control" id="filterInput" placeholder="Type to filter">
</div>
<div class="col">
<div class="form-text text-muted">Filtered by:</div>
</div>
</div>
<div class='table-responsive'>
<table class='table' *ngIf='games && games.length'>
<thead>
<tr>
<th>
<button class='btn btn-primary'>
Hide Image
</button>
</th>
<th>Game</th>
<th>Part#</th>
<th>Release Date</th>
<th>Cost</th>
<th>5 Thumb Rating</th>
</tr>
</thead>
<tbody>
<tr *ngFor='let game of games'>
<td></td>
<td>{{ game.gameName }}</td>
<td>{{ game.gameCode }}</td>
<td>{{ game.releaseDate }}</td>
<td>{{ game.price }}</td>
<td>{{ game.thumbRating }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="card-footer text-muted text-right">
<small>Powered by Angular</small>
</div>
</div>
在浏览器中的结果是,这两款游戏被迭代了,我们在页面上看到了它们的数据。

关于for of与for in的说明
在上面的例子中,我们看到*ngFor使用了for的循环结构。这是因为 **for of**循环能够迭代任何可迭代的类型,如数组。它产生存储在每个索引中的值,而不是索引本身。 for of另一方面,它产生的是索引值,如0、1、2等......你可以把for 看成 是一个特定于索引的迭代器。
总结
-
模板
- 内联
- 最好使用较短的标记长度
- 使用了 **
template**属性 - 对于单行模板可以使用双引号或单引号
- 对多行模板使用反斜线
- 没有智能感应或自动代码格式化
- 链接模板
- 最好在html标记变长时使用
- 使用了 **
templateUrl**属性 - 定义html模板文件的路径
- 内联
-
作为指令的组件
- 在组件中使用该元素作为指令,如。
- 在关联模块中声明组件,使用
declarations数组
-
插值
- 是一种单向的数据绑定
- 数据从类属性传到元素属性
- 使用{{ }}大括号来定义
-
结构指令