angular8教程(5)-构建常见的列表页面

这篇教程我们来讲述一下如何构建一个常见的列表页面,这个页面通常会包含增删改查的功能。
第一步,我们先加载一个简单的subject的页面。

  1. 首先我们新建一个subject的组件,同样可以利用idea的快捷键来创建,在app文件夹上右键,依次选择New->Angular Schematic->component,输入subject,ok确认。
  2. 接着给这个subject组件添加一个路由,我们希望通过/home/subject这样的路径来访问这个页面,并且这个subject的页面是通过home组件的<router-outlet></router-outlet>的路由入口加载进来的,所以需要把subject的路由添加到home的子路由中。因此修改app-routing.module.ts如下
const routes: Routes = [
  { path: '', pathMatch: 'full', redirectTo: '/home' },
  { path: 'home', component: HomeComponent, canActivate: [LoginGuard], children: [
      {path: 'subject', component: SubjectComponent}] },
  { path: 'login', component: LoginComponent}
];
  1. 修改home.component.html中的左侧菜单栏部分
<ul nz-menu nzTheme="dark" nzMode="inline" [nzInlineCollapsed]="isCollapsed">
      <li nz-submenu nzOpen nzTitle="师生管理中心" nzIcon="user">
        <ul>
          <li nz-menu-item nzMatchRouter>
            <a routerLink="subject">科目</a>
          </li>
        </ul>
      </li>
    </ul>

这里只保留了一个一级菜单“师生管理中心”,下面包含一个“科目”的二级菜单,链接到subject页面,这里的subject是一个相对路径,是相对于当前路径/home而言的,如果要用完整路径的话就要在最前面加上"/"即"/home/subject"。
现在运行项目,登录成功后进入home页面,再点击科目就跳转到了"/home/subject"路径,我们可以看到右侧出现了"subject works!"字样,说明成功加载了subject页面。

第二步,和上一篇教程中的login功能类似,我们需要先创建一个处理http请求的service。在service路径下面新建一个subject.service.ts的文件。按照后台api的格式发送增删改查的请求。

import {Injectable} from '@angular/core';
import {Observable} from 'rxjs';
import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http';
import qs from 'qs';
const httpOptions = {
  headers: new HttpHeaders({ 'Content-Type': 'application/x-www-form-urlencoded' })
};
@Injectable({
  providedIn: 'root'
})
export class SubjectService {
  subjectUrl = '/studentmanage/subject/';

  getSubjects(
    pageIndex: number = 0,
    pageSize: number = 10,
    name: string
  ): Observable<any> {
    const params = new HttpParams()
      .append('index', `${pageIndex}`)
      .append('size', `${pageSize}`)
      .append('name', name);
    return this.http.get(`${this.subjectUrl}subjects`, {
      params
    });
  }

  deleteSubject(
    id: string
  ): Observable<any> {
    const params = new HttpParams().append('id', id);
    return this.http.post(`${this.subjectUrl}deleteSubject`, params, httpOptions);
  }

  editSubject(
    id: string,
    name: string
  ): Observable<any> {
    const options = { id, name };
    return this.http.post(`${this.subjectUrl}editSubject`, qs.stringify(options), httpOptions);
  }
  addSubject(
    name: string
  ): Observable<any> {
    const options = { name };
    return this.http.post(`${this.subjectUrl}addSubject`, qs.stringify(options), httpOptions);
  }
  constructor(private http: HttpClient) {}
}

这里关于请求参数,我用了两种写法,分别是上一篇教程中login service中用的qs库和angular自带的HttpParams,这两种用法效果是一致的,都是为了拼接url中的请求参数。

第三步,我们要改造模板视图subject.component.html和组件的逻辑代码subject.component.ts。这部分的代码主要参考ng-zorro的官方文档表格中的远程加载数据可编辑行两部分。

  1. 修改subject.component.html
<nz-table #ajaxTable
          nzShowSizeChanger
          [nzFrontPagination]="false"
          [nzData]="subjects"
          [nzLoading]="loading"
          [nzTotal]="total"
          [(nzPageIndex)]="pageIndex"
          [(nzPageSize)]="pageSize"
          (nzPageIndexChange)="searchData()"
          (nzPageSizeChange)="searchData(true)" class="my-table">
  <thead>
  <tr>
    <th nzWidth="5%">#</th>
    <th>科目名称</th>
    <th nzWidth="20%">操作</th>
  </tr>
  </thead>
  <tbody>
  <tr *ngFor="let data of ajaxTable.data;let i = index">
    <td>{{i+1}}</td>
    <td>
      <ng-container *ngIf="!editCache[data.id].edit; else nameInputTpl">
        {{ data.name }}
      </ng-container>
      <ng-template #nameInputTpl>
        <input type="text" nz-input [(ngModel)]="editCache[data.id].data.name" />
      </ng-template>
    </td>
    <td>
      <div class="editable-row-operations">
        <ng-container *ngIf="!editCache[data.id].edit; else saveTpl">
          <a (click)="startEdit(data.id)">编辑</a>
          <nz-divider nzType="vertical"></nz-divider>
          <a nz-popconfirm nzTitle="确定要删除吗?" (nzOnConfirm)="delete(data.id)">删除</a>
        </ng-container>
        <ng-template #saveTpl>
          <a nz-popconfirm nzTitle="确定要保存吗?" (nzOnConfirm)="saveEdit(data.id)">保存</a>
          <nz-divider nzType="vertical"></nz-divider>
          <a (click)="cancelEdit(data.id)">取消</a>
        </ng-template>
      </div>
    </td>
  </tr>
  </tbody>
</nz-table>
  1. 修改subject.component.ts
import { Component, OnInit } from '@angular/core';
import {SubjectService} from '../service/subject.service';

@Component({
  selector: 'app-subject',
  templateUrl: './subject.component.html',
  styleUrls: ['./subject.component.scss']
})
export class SubjectComponent implements OnInit {
  title = '科目';
  subjects = [];
  pageIndex = 1;
  pageSize = 5;
  total = 1;
  loading = true;
  editCache: { [key: string]: any } = {};
  searchValue = '';
  constructor(private subjectService: SubjectService) {}
  ngOnInit(): void {
    this.searchData();
  }
  searchData(reset: boolean = false): void {
    if (reset) {
      this.pageIndex = 1;
    }
    this.loading = true;
    this.subjectService.getSubjects(this.pageIndex - 1, this.pageSize, this.searchValue)
      .subscribe(result => this.onSuccess(result));
  }
  onSuccess(result: any) {
    this.loading = false;
    console.log('result: ' + JSON.stringify(result));
    const data = result.data;
    this.subjects = data.subjects;
    this.total = data.total;
    this.updateEditCache();
  }
  startEdit(id: string): void {
    this.editCache[id].edit = true;
  }

  cancelEdit(id: string): void {
    const index = this.subjects.findIndex(item => item.id === id);
    this.editCache[id] = {
      data: { ...this.subjects[index] },
      edit: false
    };
  }

  saveEdit(id: string): void {
    this.subjectService.editSubject(id, this.editCache[id].data.name).subscribe(result => {
      const index = this.subjects.findIndex(item => item.id === id);
      Object.assign(this.subjects[index], this.editCache[id].data);
      this.editCache[id].edit = false;
    });
  }

  updateEditCache(): void {
    this.subjects.forEach(item => {
      this.editCache[item.id] = {
        edit: false,
        data: { ...item }
      };
    });
  }

  delete(id: string): void {
    this.subjectService.deleteSubject(id).subscribe(result => this.searchData());
  }
}

这里我们就讲一下用到的一些angular的模板语法
(1)angular模板支持几乎所有的html元素和语法,除了<script>,还有<html>、<body> 和 <base>这些元素是无用的,其他都是和原生html一样使用。例如我们这里使用了表格元素<tr><td>等。在这个基础上,angular模板还有扩展的元素和语法,如<nz-table>这个就是扩展的元素,是由ng-zorro这个UI框架定义的。
(2)以“#”开头的“#ajaxTable”表示模板引用变量,就是指这个nz-table本身。到下面表格正文就引用了这个变量<tr *ngFor="let data of ajaxTable.data;let i = index">
(3)未加任何括号的属性代表一个无绑定的常量属性如nzShowSizeChanger,它是一个布尔类型的属性常量,值为true。同样还有<nz-divider nzType="vertical"></nz-divider>种的nzType等。
(4)加了[]的属性代表了该属性被绑定到了一个""中的变量或者常量上,比如[nzData]="subjects"这个属性,这里的subjects实际上是一个变量,在subject.component.ts中我们将它声明为一个数组,把从后台获取的subjects数据赋值给它,这样模板视图就可以根据subjects的值更新界面。
(5)加了()的代表的是绑定的事件,""里面一般都是写该事件发生时调用的方法。例如<a (click)="startEdit(data.id)">编辑</a>这里是一个最典型点击事件,点击这个链接后调用startEdit方法。
(6)像[(nzPageIndex)]这种加了[()]的则代表了双向绑定的属性,从符号上可以看出来,代表了属性绑定和事件绑定的结合,既绑定了这个属性,又可以随时监听这个属性的变化。
(7)<tr *ngFor="let data of ajaxTable.data;let i = index">
<td>{{i+1}}</td>
<td>
<ng-container *ngIf="!editCache[data.id].edit; else nameInputTpl">
{{ data.name }}
</ng-container>
因为这几句有上下文关系,所以放到一起看,里面包含了几个语法点:
a. *ngFor:这是一个结构指令,类似于js代码中的for循环语句,引号中的这一小段看起来像js代码的官方解释为微语法(microsyntax)—— 由 Angular 解释的一种小型语言。
b. *ngIf:同样是一个结构指令,从名称看类似于js中的if选择语句,当然引号中的也同样可以看作是微语法。
c. 插值与模板表达式:就是把变量或者表达式插入到文本标记中,用{{···}}表示,如{{i+1}}和{{ data.name }}。
d. 表达式上下文:这里我们可以看到在微语法语句中可以引用上面用“#”标记的模板引用变量,而微语法中可以声明变量data和i,在下文的插值和模板表达式中又可以引用这两个变量,这里都是有上下文关系的。表达式中的上下文变量是由模板变量(ajaxTable)、指令的上下文变量(data)和组件的成员(subject)叠加而成的。 这三者的优先级是依次递减的。
到这里我们这个subject列表的删改查功能其实已经完成了,目前还缺少一个增的功能。

第三步,添加一个subjectForm的组件。

  1. 在subject路径下新建一个subjectForm的component,修改subject-form.component.html
<nz-page-header nzBackIcon [nzTitle]="title">
</nz-page-header>
<form nz-form [formGroup]="subjectForm" (ngSubmit)="submitForm()">
  <nz-form-item>
    <nz-form-label [nzSpan]="3" nzRequired nzFor="name">科目名称</nz-form-label>
    <nz-form-control [nzSpan]="8" nzErrorTip="请输入科目名称">
      <input type="text" nz-input formControlName="name" />
    </nz-form-control>
  </nz-form-item>
  <nz-form-item>
    <nz-form-control [nzSpan]="8" [nzOffset]="4">
      <button nz-button nzType="primary">提交</button>
    </nz-form-control>
  </nz-form-item>
</form>

这里的写法我们基本也是参考ngzorro的官方文档的表单Form部分。

  1. 修改subject-form.component.ts
import { Component, OnInit } from '@angular/core';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import {Router} from '@angular/router';
import {SubjectService} from '../../service/subject.service';
import { Location } from '@angular/common';

@Component({
  selector: 'app-subject-form',
  templateUrl: './subject-form.component.html',
  styleUrls: ['./subject-form.component.scss']
})
export class SubjectFormComponent implements OnInit {
  title = '新增科目';
  subjectForm: FormGroup;

  submitForm(): void {
    for (const i in this.subjectForm.controls) {
      this.subjectForm.controls[i].markAsDirty();
      this.subjectForm.controls[i].updateValueAndValidity();
      if (this.subjectForm.controls[i].invalid) {
        return;
      }
    }
    this.subjectService.addSubject(this.subjectForm.value.name)
      .subscribe(result => this.location.back());
  }

  constructor(private fb: FormBuilder, private subjectService: SubjectService,
              private location: Location) {}

  ngOnInit(): void {
    this.subjectForm = this.fb.group({
      name: [null, [Validators.required]]
    });
  }
}

在上一篇教程讲述登录功能的时候我们就已经用到了表单提交的功能,这边也是类似的。

  1. 在app-routing.module.ts中添加路由,
const routes: Routes = [
  { path: '', pathMatch: 'full', redirectTo: '/home' },
  { path: 'home', component: HomeComponent, canActivate: [LoginGuard], children: [
      {path: 'subject', component: SubjectComponent},
  {path: 'subject/addSubject', component: SubjectFormComponent}] },
  { path: 'login', component: LoginComponent}
];

并且在subject.component.html的最上方添加一个新增的按钮,点击可以跳转到新增的页面

<button nz-button nzType="primary" class="add-button" routerLink="addSubject">新增</button>

完成这些之后,我们可以重启项目,登录之后选中科目,尝试一下增删改的功能。
代码依然可以参考https://github.com/ahuadoreen/studentmanager-cli

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,200评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,526评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,321评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,601评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,446评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,345评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,753评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,405评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,712评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,743评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,529评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,369评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,770评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,026评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,301评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,732评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,927评论 2 336

推荐阅读更多精彩内容

  • core package 概要:Core是所有其他包的基础包.它提供了大部分功能包括metadata,templa...
    LOVE小狼阅读 2,545评论 0 3
  • 一、SPA的概念 首先明确一个概念,SPA,全称为Single Page Application单页应用,一个单页...
    耦耦阅读 5,918评论 0 3
  • 一.课程简介 (注意:这里的AngularJS指的是2.0以下的版本) AngularJS的优点: 模板功能强大丰...
    壹点微尘阅读 898评论 0 0
  • 声明 本系列文章内容梳理自以下来源: Angular 官方中文版教程 官方的教程,其实已经很详细且易懂,这里再次梳...
    请叫我大苏阅读 1,052评论 0 6
  • 2月27日 早上7点8分出门,本来准备慢悠悠走去。想起在老家,哥哥陪着我跑步,连贯地运动下去,气也不歇,倒也还可以...
    笑笑生风阅读 199评论 0 0