学习Angular的结构指令

126 阅读8分钟

当使用Angular构建用户界面时,我们需要一个有效的模板,它可以定义在内联,也可以定义在一个专门的html文件中。模板本身是用html创建的,为了赋予html特殊的能力,我们使用了Angular的数据绑定和指令。Angular使得建立这些用户界面变得更加容易,因为数据绑定提供了动态数据的显示,而事件允许Angular应用程序对用户的操作做出适当的反应。Angular指令提供了向html添加逻辑的能力,这是它默认不具备的。


内联模板的优势

到目前为止,我们已经开始在文件中创建一个内联模板 app.component.ts文件中创建内联模板。虽然不一定是最好的选择,但也有一些优势。

  • 模板被直接定义在相关的组件内
  • 将视图和该视图的代码保持在一个地方
  • 使得数据绑定与类属性的匹配更容易。

建立一个链接模板

即使有上面列出的优点,使用链接模板往往也是有意义的。内联模板在各种IDE开发工具(如Visual Studio Code、Jetbrains Webstorm等)中的效果并不理想。通常情况下,类似intellisense的功能和自动格式化都无法工作。因此,随着所需的html数量的增加,利用这种链接模板的方法变得更加容易。让我们建立一个可以列出视频游戏的组件。


构建一个游戏列表组件

按照惯例,创建专门的文件夹来存放每个组件是有意义的。因此,我们可以首先创建一个 games文件夹来存放游戏相关的组件。
new directory to hold angular component

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

在我们的模板文件中,可以使用下面的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
angular typescript file

下面的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组件作为指令。
angular custom directive


添加动态数据

现在,应用程序中只有静态数据。让我们通过使用数据绑定来改变这种情况。我们可以做的第一件事是访问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 one way interpolation


介绍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 ngif example


介绍一下Angular *ngFor

在Angular中,你需要有能力在项目的集合上循环并显示它们。在我们的例子中,一旦我们有了一些游戏,我们就应该能够在这些游戏上进行循环并在我们的表格中显示它们。要做到这一点,我们可以使用 *ngFor.这个结构指令为一个可迭代列表(如数组)中的每个项目重复一次dom树的一部分。我们要做的是在 gamesgame-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>

在浏览器中的结果是,这两款游戏被迭代了,我们在页面上看到了它们的数据。
angular ngfor example


关于for offor in的说明

在上面的例子中,我们看到*ngFor使用了for的循环结构。这是因为 **for of**循环能够迭代任何可迭代的类型,如数组。它产生存储在每个索引中的值,而不是索引本身。 for of另一方面,它产生的是索引值,如0、1、2等......你可以把for 看成 是一个特定于索引的迭代器。


总结

  • 模板

    • 内联
      • 最好使用较短的标记长度
      • 使用了 **template**属性
      • 对于单行模板可以使用双引号或单引号
      • 对多行模板使用反斜线
      • 没有智能感应或自动代码格式化
    • 链接模板
      • 最好在html标记变长时使用
      • 使用了 **templateUrl**属性
      • 定义html模板文件的路径
  • 作为指令的组件

    • 在组件中使用该元素作为指令,如。
    • 在关联模块中声明组件,使用 declarations数组
  • 插值

    • 是一种单向的数据绑定
    • 数据从类属性传到元素属性
    • 使用{{ }}大括号来定义
  • 结构指令

    • 前缀为星号的 *字符
    • 分配给一个带引号的字符串表达式
    • *ngIf用于从Dom中添加或删除一个元素和它的孩子。
    • 基于表达式,其评估结果为真或假
    • *ngFor用于在Dom中重复一个元素和它的子元素。
    • 在循环中使用let作为局部变量的类型
    • 在创建表达式时使用 "of "而不是 "in"。