我们构建一个主从结构的页面,用于展现英雄列表

延续上一步教程

让应用代码保持转译和运行

我们要启动 TypeScript 编译器,它会监视文件变更,并启动开发服务器。我们只要敲:

npm start

这个命令会在我们构建《英雄指南》的时候让应用得以持续运行。

显示我们的英雄

创建英雄

我们先创建一个由十位英雄组成的数组。

/app/app.component.ts

const HEROES: Hero[] = [
  { id: 11, name: ‘Mr. Nice‘ },
  { id: 12, name: ‘Narco‘ },
  { id: 13, name: ‘Bombasto‘ },
  { id: 14, name: ‘Celeritas‘ },
  { id: 15, name: ‘Magneta‘ },
  { id: 16, name: ‘RubberMan‘ },
  { id: 17, name: ‘Dynama‘ },
  { id: 18, name: ‘Dr IQ‘ },
  { id: 19, name: ‘Magma‘ },
  { id: 20, name: ‘Tornado‘ }
];

HEROES是一个由Hero类的实例构成的数组,我们在第一部分定义过它。 我们当然希望从一个 Web 服务中获取这个英雄列表,但别急,我们得把步子迈得小一点,先用一组模拟出来的英雄。

暴露英雄

我们在AppComponent上创建一个公共属性,用来暴露这些英雄,以供绑定。

暴露英雄

我们在AppComponent上创建一个公共属性,用来暴露这些英雄,以供绑定。

/app/app.component.ts

export class AppComponent{  heros=HEROS; }

我们并不需要明确定义heros属性的数据类型,TypeScript能从HEROS数组中推断出来。

我们已经把英雄列表定义在了这个组件类中。 但显然,我们最终还是得从一个数据服务中获取这些英雄。 正因如此,一开始就应该把英雄数据隔离到一个类中来实现,以便于后期我们可以随时从一个服务中获取数据来替换。

在模板中显示英雄

我们的组件有了heroes属性,我们再到模板中创建一个无序列表来显示它们。 我们将在标题和英雄详情之间,插入下面这段 HTML 代码。

/app/app.component.ts

<h2>My Heroes</h2>
<ul class="heroes">
  <li>
    <!-- each hero goes here -->
  </li>
</ul>

现在,我们有了一个模板。接下来,就用英雄们的数据来填充它。

通过 ngFor 来显示英雄列表

我们想要把组件中的heroes数组绑定到模板中,迭代并逐个显示它们。 这下,我们就得借助 Angular 了。我们来一步步实现它!

首先,修改<li>标签,往上添加内置指令*ngFor

<li *ngFor="let hero of heros">

ngFor的前导星号(*)是此语法的重要组成部分

ngFor*前缀表示<li>及其子元素组成了一个主控模板。

ngFor指令在AppComponent.heroes属性返回的heroes数组上迭代,并输出此模板的实例。

引号中赋值给ngFor的那段文本表示“从heroes数组中取出每个英雄,存入一个局部的hero变量,并让它在相应的模板实例中可用”。

hero前的let关键字表示hero是一个模板输入变量。 在模板中,我们可以引用这个变量来访问一位英雄的属性。

要学习更多关于ngFor和模板输入变量的知识,见显示数据和 模板语法

现在,我们在<li>标签中插入一些内容,以便使用模板变量hero来显示英雄的属性。

/app/app.component.ts

<li *ngFor="let hero of heroes">
  <span class="badge">{{hero.id}}</span> {{hero.name}}
</li>

当浏览器刷新时,我们就看到了英雄列表。

给我们的英雄们“美容”

我们的英雄列表看起来实在是稀松平常。 但当用户的鼠标划过英雄或选中了一个英雄时,我们得让它/她看起来醒目一点。

要想给我们的组件添加一些样式,请把@Component装饰器的styles属性设置为下列 CSS 类:

/app/app.component.ts

styles: [`
  .selected {
    background-color: #CFD8DC !important;
    color: white;
  }
  .heroes {
    margin: 0 0 2em 0;
    list-style-type: none;
    padding: 0;
    width: 15em;
  }
  .heroes li {
    cursor: pointer;
    position: relative;
    left: 0;
    background-color: #EEE;
    margin: .5em;
    padding: .3em 0;
    height: 1.6em;
    border-radius: 4px;
  }
  .heroes li.selected:hover {
    background-color: #BBD8DC !important;
    color: white;
  }
  .heroes li:hover {
    color: #607D8B;
    background-color: #DDD;
    left: .1em;
  }
  .heroes .text {
    position: relative;
    top: -3px;
  }
  .heroes .badge {
    display: inline-block;
    font-size: small;
    color: white;
    padding: 0.8em 0.7em 0 0.7em;
    background-color: #607D8B;
    line-height: 1em;
    position: relative;
    left: -1px;
    top: -4px;
    height: 1.8em;
    margin-right: .8em;
    border-radius: 4px 0 0 4px;
  }
`]

注意,我们又使用了反引号语法来书写多行字符串。

这里有很多种样式!我们可以像上面那样把它们内联在组件中,或者把样式移到单独的文件中, 这样能让编写组件变得更容易。我们会后面的章节中使用独立样式文件,现在我们先不管它。

当我们为一个组件指定样式时,它们的作用域将仅限于该组件。 上面的例子中,这些样式只会作用于AppComponent组件,而不会“泄露”到外部 HTML 中。

用于显示英雄们的模板看起来像这样:

<h2>My Heroes</h2>
<ul class="heroes">
  <li *ngFor="let hero of heroes">
    <span class="badge">{{hero.id}}</span> {{hero.name}}
  </li>
</ul>

选择英雄

在我们的应用中,已经有了英雄列表以及一个单独的英雄。 但列表和单独的英雄之间还没有任何关联。 我们希望用户在列表中选中一个英雄,然后让这个被选中的英雄出现在详情视图中。 这种 UI 布局模式,通常被称为“主从结构”。 在这个例子中,主视图是英雄列表,从视图则是被选中的英雄。

我们通过组件中的一个selectedHero属性来连接主从视图,它被绑定到了点击事件上。

我们往<li>元素上插入一句 Angular 事件绑定代码,绑定到它的点击事件。

看起来是这样的。<li *ngFor=‘let hero of heroes‘  (click)="onSelect(hero)">

事件绑定详解

(click)="onSelect(hero)"
圆括号表示<li>元素上的click事件是绑定的目标。 等号右边的表达式调用AppComponentonSelect()方法,并把模板输入变量hero作为参数传进去。 它是我们前面在ngFor中定义的那个hero变量。

添加点击处理器

我们的事件绑定引用了onSelect方法,但它还不存在。 我们现在就把它添加到组件上。

这个方法该做什么?它应该把组件中被选中的英雄设置为用户刚刚点击的那个。

我们的组件还没有用来表示“当前选中的英雄”的变量,我们就从这一步开始。

暴露选中的英雄

我们不再需要AppComponenthero属性。 把它替换selectedHero属性。

selectedHero: Hero;

我们决定在用户选取之前,不会默认选择任何英雄,所以,不用像hero一样初始化selectedHero变量。

现在,添加一个onSelect方法,用于将用户点击的英雄赋给selectedHero属性。

onSelect(hero: Hero): void {
  this.selectedHero = hero;
}

我们将把所选英雄的详细信息显示在模板中。目前,它仍然引用之前的hero属性。 我们这就修改模板,让它绑定到新的selectedHero属性。

<h2>{{selectedHero.name}} details!</h2>
<div><label>id: </label>{{selectedHero.id}}</div>
<div>
    <label>name: </label>
    <input [(ngModel)]="selectedHero.name" placeholder="name"/>
</div>

使用 ngIf 隐藏空的详情

当应用加载时,我们会看到一个英雄列表,但还没有任何英雄被选中。 selectedHero属性是undefined。 因此,我们会看到浏览器控制台中出现下列错误:

EXCEPTION: TypeError: Cannot read property ‘name‘ of undefined in [null]

别忘了我们要在模板中显示的是selectedHero.name。 显然,这个 name 属性是不存在的,因为selectedHero本身还是undefined呢。

要处理这个问题,我们可以先让英雄详情不要出现在 DOM 中,直到有英雄被选中。

我们把模板中的英雄详情内容区用放在一个<div>中。 然后,添加一个ngIf内置指令,把ngIf的值设置为组件的selectedHero属性。

<div *ngIf="selectedHero">
  <h2>{{selectedHero.name}} details!</h2>
  <div><label>id: </label>{{selectedHero.id}}</div>
  <div>
    <label>name: </label>
    <input [(ngModel)]="selectedHero.name" placeholder="name"/>
  </div>
</div>

当没有selectedHero时,ngIf指令从 DOM 中移除表示英雄详情的这段 HTML 。 没有了表示英雄详情的元素,也就不用担心绑定问题。

当用户选取了一个英雄,selectedHero变成了“真”值,于是ngIf把英雄详情加回 DOM 中,并计算它所嵌套的各种绑定。

注:ngIfngFor被称为“结构型指令”,因为它们可以修改部分 DOM 的结构。 换句话说,它们让 Angular 在 DOM 中显示内容的方式结构化了。

浏览器刷新了,我们看到了一个英雄列表,但没有显示选中的英雄详情。 当selectedHeroundefined时,ngIf会保证英雄详情不出现在 DOM 中。 当我们从列表中点击一个英雄时,选中的英雄被显示在英雄详情里。 正如我们所预期的那样。

给所选英雄添加样式

我们在下面的详情区看到了选中的英雄,但是我们还是没法在上面的列表区快速地定位这位英雄。 可以通过在主列表的相应<li>元素上添加 CSS 类selected来解决这个问题。 例如,当我们在列表区选中了 Magneta 时,我们可以通过设置一个轻微的背景色来让它略显突出。

在模块中,我们在class上为selected类添加一个属性绑定。我们把绑定表达式设置为selectedHerohero的比较结果。

键是 CSS 类的名字 (selected)。当两位英雄一致时,值为true,否则为false。 也就是说:“当两位英雄匹配时,应用上selected类,否则不应用”。

[class.selected]="hero === selectedHero"

注意,模板中的class.selected包裹在方括号中。 这就是属性绑定的语法,实现从数据源(hero === selectedHero表达式)到class属性的单向数据流动。

<li *ngFor="let hero of heroes"
[class.selected]="hero === selectedHero"
(click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>

浏览器重新加载了我们的应用。 我们选中英雄 Magneta,通过背景色的变化,它被清晰的标记出来。

我们选择另一个英雄,色标也随着切换。

完整的app.component.ts文件如下:

import { Component } from ‘@angular/core‘;
export class Hero {
  id: number;
  name: string;
}
const HEROES: Hero[] = [
  { id: 11, name: ‘Mr. Nice‘ },
  { id: 12, name: ‘Narco‘ },
  { id: 13, name: ‘Bombasto‘ },
  { id: 14, name: ‘Celeritas‘ },
  { id: 15, name: ‘Magneta‘ },
  { id: 16, name: ‘RubberMan‘ },
  { id: 17, name: ‘Dynama‘ },
  { id: 18, name: ‘Dr IQ‘ },
  { id: 19, name: ‘Magma‘ },
  { id: 20, name: ‘Tornado‘ }
];
@Component({
  selector: ‘my-app‘,
  template: `
    <h1>{{title}}</h1>
    <h2>My Heroes</h2>
    <ul class="heroes">
      <li *ngFor="let hero of heroes"
        [class.selected]="hero === selectedHero"
        (click)="onSelect(hero)">
        <span class="badge">{{hero.id}}</span> {{hero.name}}
      </li>
    </ul>
    <div *ngIf="selectedHero">
      <h2>{{selectedHero.name}} details!</h2>
      <div><label>id: </label>{{selectedHero.id}}</div>
      <div>
        <label>name: </label>
        <input [(ngModel)]="selectedHero.name" placeholder="name"/>
      </div>
    </div>
  `,
  styles: [`
    .selected {
      background-color: #CFD8DC !important;
      color: white;
    }
    .heroes {
      margin: 0 0 2em 0;
      list-style-type: none;
      padding: 0;
      width: 15em;
    }
    .heroes li {
      cursor: pointer;
      position: relative;
      left: 0;
      background-color: #EEE;
      margin: .5em;
      padding: .3em 0;
      height: 1.6em;
      border-radius: 4px;
    }
    .heroes li.selected:hover {
      background-color: #BBD8DC !important;
      color: white;
    }
    .heroes li:hover {
      color: #607D8B;
      background-color: #DDD;
      left: .1em;
    }
    .heroes .text {
      position: relative;
      top: -3px;
    }
    .heroes .badge {
      display: inline-block;
      font-size: small;
      color: white;
      padding: 0.8em 0.7em 0 0.7em;
      background-color: #607D8B;
      line-height: 1em;
      position: relative;
      left: -1px;
      top: -4px;
      height: 1.8em;
      margin-right: .8em;
      border-radius: 4px 0 0 4px;
    }
  `]
})
export class AppComponent {
  title = ‘Tour of Heroes‘;
  heroes = HEROES;
  selectedHero: Hero;
  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }
}

已走的路

在本章中,我们完成了以下内容:

  • 我们的《英雄指南》现在显示一个可选英雄的列表
  • 我们可以选择英雄,并显示这个英雄的详情
  • 我们学会了如何在组件模板中使用内置的ngIfngFor指令,以及属性指令的写法[class.selected]="hero===selectedHero".
时间: 2024-10-17 17:42:43

我们构建一个主从结构的页面,用于展现英雄列表的相关文章

我们把主从结构的页面重构成多个组件

多个组件 我们的应用正在成长中.现在又有新的用例:重复使用组件,传递数据给组件并创建更多可复用. 我们来把英雄详情从英雄列表中分离出来,让这个英雄详情组件可以被复用. 首先老规矩,我们得让我们的代码运行起来: 让应用代码保持转译和运行 我们要启动 TypeScript 编译器,它会监视文件变更,并启动开发服务器.只要敲: npm start 这个命令会在我们构建<英雄指南>的时候让应用得以持续运行. 制作英雄详情组件 目前,英雄列表和英雄详情位于同一个文件的同一个组件中. 它们现在还很小,但很

基于 vue2 + vuex 构建一个具有 45 个页面的大型单页面应用

初学vue时曾在网上搜索vue的实战项目源码,无奈大部分都是简单的demo,对于深究vue没有太大的帮助,剩下的一些大部分都是像音乐播放器之类的展示型项目,交互没有预期那么复杂.但我们实际在工作中,经常会遇到有购物车的项目,这类项目因为涉及到money,所以对逻辑严谨度要求高,页面之间交互复杂,又会伴随着登陆.注册.用户信息等等,常常会让我们很头疼.既然还没人用vue写过这样的项目,那不如我来写,开源出来对能看到的人也会有帮助. 这种功能性的项目很实用但是往往也很枯燥,没有音乐播放器那么看起来绚

构建一个用于产品介绍的WEB应用

为了让用户更好地了解您的产品功能,您在发布新产品或者升级产品功能的时候,不妨使用一个产品介绍的向导,引导用户熟悉产品功能和流程.本文将给您介绍一款优秀的用于产品介绍的WEB应用. 就像微博或邮箱这类WEB产品升级一样,使用Guiders.js构建的应用,用户将会看到一些列的弹出层,这些弹出层可以定位到页面上的任意位置,引导用户一步步浏览,最后退出向导.Guiders.js是一款基于jquery的web应用插件,作者是jeff-optimizely.下面我们来介绍如何在实际项目中应用. 准备 加入

为您的Web项目构建一个简单的JSON控制器

摘要:无论您的项目使用的是哪种数据库后端,JavaScript Object Notation (JSON) 控制器都能简化您的开发工作.本文将带领您建立一个能够增强您的下一个开发项目的非常基础的 JSON 控制器. 您的下一个 PHP/MySQL 项目可能与您最近完成的十几个项目类似:建立一个 MySQL 数据库,创建包含 HTML 的 PHP 视图,根据需要添加 JavaScript 代码和 CSS 文件,连接到数据库,从数据库提取内容来填充视图,等等.如果您熟悉 web 开发,您一定知道分

基于Grunt构建一个JavaScript库

现在公认的JavaScript典型项目需要运行单元测试,合并压缩.有些还会使用代码生成器,代码样式检查或其他构建工具. Grunt.js是一个开源工具,可以帮助你完成上面的所有步骤.它非常容易扩展,并使用JavaScript书写,所以任何为JavaScript库或项目工作的人都可以按自己的需要扩展它. 本文解释如何使用Grunt.js构建JavaScript库.Grunt.js依赖Node.js和npm,所以第一节解释其是什么,如何安装和使用.如果你对npm有了解,那你可以跳过这一节.第四和第五

为 Drupal 7 构建一个新主题

主题解释了 Drupal 网站的用户界面 (UI).虽然主题结构并没有明显的变化,但 Drupal 版本 7 配备了一个新的主题实现方法.本文演示了如何创建一个新的 Drupal 7 主题. Drupal 主题的目标是将框架的处理逻辑和设计元素分开.为了做到这一点,Drupal 采用了一个复杂的主题系统,其中包括主题.主题引擎和挂钩.主题组件与 Drupal 核心系统和模块设计元素配合,创建具有独特外观的用户界面(单独 Drupal 页面和表单).由于将 Drupal 的业务逻辑从它的表示逻辑中

用Spring构建一个RESTful Web Service

本教程将手把手教你用Spring构建一个"hello world" RESTful Web Service. 你将构建什么 你将构建一个Web Service,用于接收HTTP GET请求,请求地址: http://localhost:8080/greeting 请求响应返回一段JSON格式的问候语: {"id":1,"content":"Hello, World!"} 你可以在请求中添加一个可选参数:name,定制问候语:

Django入门第一步:构建一个简单的Django项目

Django入门第一步:构建一个简单的Django项目 1.简介 Django是一个功能完备的Python Web框架,可用于构建复杂的Web应用程序.在本文中,将通过示例跳入并学习Django.您将按照以下步骤创建功能完备的Web应用程序,并一路学习框架的一些最重要的功能以及它们如何协同工作. 学习目标: 了解Django是什么以及为什么他是一个伟大的web框架 了解Django的体系结构以及与其他框架的对比 独立搭建一个简单的Django项目和应用程序(app) 2.为什么要学习Django

JavaScript网站设计实践(一)网站结构以及页面效果设计

原文:JavaScript网站设计实践(一)网站结构以及页面效果设计 这是JavaScript DOM编程艺术里的构建JavaScript网站设计的例子,这本书给我学习JavaScript带来许多启发,在这个乐队宣传网站中,把前面学到的知识点整合在这个项目了.在这里记录下实现这个乐队的宣传网站的具体过程,加深理解.好,从现在开始来实现这个JavaScript网站实战. 一.网页的结构 由三个部分组成:头部.导航.内容 效果图是这样的(勉强看,有点不美观.哈哈): 二.网站的的结构 这是我的项目目