本文目录

本项目源码放在github

六、改造组件

从这里开始,我们要使用RxJS来改造组件和添加新功能了,让整个项目更加完善。

1.添加历史记录组件

  • 创建HistoryComponent组件
ng g component hostory

然后在app.component.html文件夹中添加组件:

<!-- app.component.html -->
<app-history></app-history>

2.添加增删改查功能

这里我们要开始做书本的增删改查功能,需要先创建一个HistoryService服务,方便我们实现这几个功能:

  • 创建HistoryService服务
ng g service history

然后在生成的ts文件中,增加addclear方法,add方法用来添加历史记录到history数组中,clear方法则是清空history数组:

// history.service.ts
export class HistoryService {
history: string[] = [];
add(history: string){
this.history.push(history);
}
clear(){
this.history = [];
}
}
  • 使用HistoryService服务

在将这个服务,注入到BooksService中,并改造getBooks方法:

// books.service.ts
import { HistoryService } from './history.service';
constructor(
private historyservice: HistoryService
) { }
getBooks(): void{
this.historyservice.add('请求书本数据')
this.booksservice.getBookList()
.subscribe(books => this.books = books);
}

也可以用相同方法,在IndexComponent中添加访问首页书本列表的记录。

// index.component.ts
import { HistoryService } from '../history.service';
constructor(
private booksservice: BooksService,
private historyservice: HistoryService
) { }
getBooks(): void{
this.historyservice.add('访问首页书本列表');
this.booksservice.getBookList()
.subscribe(books => this.books = books);
}

接下来,将我们的HistoryService注入到HistoryComponent中,然后才能将历史数据显示到页面上:

// history.component.ts
import { HistoryService } from '../history.service';
export class HistoryComponent implements OnInit {
constructor(private historyservice: HistoryService) { }
ngOnInit() {}
}
<!-- history.component.html -->
<div *ngIf="historyservice.history.length">
<h2>操作历史:</h2>
<div>
<button class="clear"
(click)="historyservice.clear()"
>清除</button>
<div *ngFor="let item of historyservice.history">{{item}}</div>
</div>
</div>

代码解释

*ngIf="historyservice.history.length",是为了防止还没有拿到历史数据,导致后面的报错。

(click)="historyservice.clear()", 绑定我们服务中的clear事件,实现清除缓存。

*ngFor="let item of historyservice.history",将我们的历史数据渲染到页面上。

到了这一步,就能看到历史数据了,每次也换到首页,都会增加一条。

接下来,我们要在书本详情页也加上历史记录的统计,导入文件,注入服务,然后改造getBooks方法,实现历史记录的统计:

// detail.component.ts
import { HistoryService } from '../history.service'; export class DetailComponent implements OnInit {
constructor(
private route: ActivatedRoute,
private location: Location,
private booksservice: BooksService,
private historyservice: HistoryService
) { }
//...
getBooks(id: number): void {
this.books = this.booksservice.getBook(id);
this.historyservice.add(`查看书本${this.books.title},id为${this.books.id}`);
console.log(this.books)
}
}

这时候就可以在历史记录中,看到这些操作的记录了,并且清除按钮也正常使用。

七、HTTP改造

原本我只想写到上一章,但是想到,我们实际开发中,哪有什么本地数据,基本上数据都是要从服务端去请求,所以这边也有必要引入这一张,模拟实际的HTTP请求。

1.引入HTTP

在这一章,我们使用Angular提供的 HttpClient 来添加一些数据持久化特性。

然后实现对书本数据进行获取,增加,修改,删除和查找功能。

HttpClient是Angular通过 HTTP 与远程服务器通讯的机制。

这里我们为了让HttpClient在整个应用全局使用,所以将HttpClient导入到根模块app.module.ts中,然后把它加入 @NgModule.imports 数组:

import { HttpClientModule } from '@angular/common/http';
@NgModule({
//...
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule
],
//...
})

这边我们使用 内存 Web API(In-memory Web API)模拟出的远程数据服务器通讯。

注意: 这个内存 Web API 模块与 Angular 中的 HTTP 模块无关。

通过下面命令来安装:

npm install angular-in-memory-web-api --save

然后在app.module.ts中导入 HttpClientInMemoryWebApiModuleInMemoryDataService 类(后面创建):

// app.module.ts
import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryDataService } from './in-memory-data.service';
@NgModule({
// ...
imports: [
// ...
HttpClientInMemoryWebApiModule.forRoot(
InMemoryDataService, {dataEncapsulation:false}
)
],
// ...
})
export class AppModule { }

知识点:

forRoot() 配置方法接受一个 InMemoryDataService 类(初期的内存数据库)作为参数。

然后我们要创建InMemoryDataService类:

ng g service InMemoryData

并将生成的in-memory-data.service.ts修改为:

// in-memory-data.service.ts
import { Injectable } from '@angular/core';
import { InMemoryDbService } from 'angular-in-memory-web-api';
import { Books } from './books';
@Injectable({
providedIn: 'root'
})
export class InMemoryDataService implements InMemoryDbService {
createDb(){
const books = [
{
id: 1,
url: 'https://img3.doubanio.com/view/subject/m/public/s29988481.jpg',
title: '像火焰像灰烬',
author: '程姬',
},
// 省略其他9条数据
];
return {books};
}
constructor() { }
}

这里先总结InMemoryDbService所提供的RESTful API,后面都要用到:

例如如果urlapi/books,那么

  • 查询所有成员:以GET方法访问api/books
  • 查询某个成员:以GET方法访问api/books/id,比如id1,那么访问api/books/1
  • 更新某个成员:以PUT方法访问api/books/id
  • 删除某个成员:以DELETE方法访问api/books/id
  • 增加一个成员:以POST方法访问api/books

2.通过HTTP请求数据

现在要为接下来的网络请求做一些准备,先在books.service.ts中引入HTTP符号,然后注入HttpClient并改造:

// books.service.ts
import { HttpClient, HttpHeaders} from '@angular/common/http';
// ...
export class BooksService {
constructor(
private historyservice: HistoryService,
private http: HttpClient
) { }
private log(histories: string){
this.historyservice.add(`正在执行:${histories}`)
}
private booksUrl = 'api/books'; // 提供一个API供调用
// ...
}

这里我们还新增一个私有方法log和一个私有变量booksUrl

接下来我们要开始发起http请求数据,开始改造getBookList方法:

// books.service.ts
// ...
getBookList(): Observable<Books[]> {
this.historyservice.add('请求书本数据')
return this.http.get<Books[]>(this.booksUrl);
}
// ...

这里我们使用 http.get 替换了 of,其它没修改,但是应用仍然在正常工作,这是因为这两个函数都返回了 Observable<Hero[]>

实际开发中,我们还需要考虑到请求的错误处理,要捕获错误,我们就要使用 RxJS 的 catchError() 操作符来建立对 Observable 结果的处理管道(pipe)。

我们引入catchError并改造原本getBookList方法:

// books.service.ts
getBookList(): Observable<Books[]> {
this.historyservice.add('请求书本数据')
return this.http.get<Books[]>(this.booksUrl).pipe(
catchError(this.handleError<Books[]>('getHeroes', []))
);
}
private handleError<T> (operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
this.log(`${operation} 失败: ${error.message}`); // 发出错误通知
return of(result as T); // 返回空结果避免程序出错
};
}

知识点

.pipe() 方法用来扩展 Observable 的结果。

catchError() 操作符会拦截失败的 Observable。并把错误对象传给错误处理器,错误处理器会处理这个错误。

handleError() 错误处理函数做了两件事,发出错误通知和返回空结果避免程序出错。

这里还需要使用tap操作符改造getBookList方法,来窥探Observable数据流,它会查看Observable的值,然后我们使用log方法,记录一条历史记录。

tap 回调不会改变这些值本身。

// books.service.ts
getBookList(): Observable<Books[]> {
return this.http.get<Books[]>(this.booksUrl)
.pipe(
tap( _ => this.log('请求书本数据')),
catchError(this.handleError<Books[]>('getHeroes', []))
);
}

3.通过HTTP修改数据

这里我们需要在原来DetailComponent上面,添加一个输入框、保存按钮和返回按钮,就像这样:

<!-- detail.component.html -->
<!-- 前面代码省略 -->
<div>
<h2>修改信息:</h2>
<label>新标题:
<input [(ngModel)]="books.title" placeholder="请输入新标题">
</label>
<button (click)="save()">保存</button>
<button (click)="goBack()">返回</button>
</div>

这边切记一点,一定要在app.module.ts中引入 FormsModule模块,并在@NgModuleimports中引入,不然要报错了。

// app.module.ts
// ...
import { FormsModule } from '@angular/forms';
@NgModule({
// ...
imports: [
// ...
FormsModule
],
// ...
})

input框绑定书本的标题books.title,而保存按钮绑定一个save()方法,这里还要实现这个方法:

// detail.component.ts
save(): void {
this.historyservice.updateBooks(this.books)
.subscribe(() => this.goBack());
}
goBack(): void {
this.location.back();
}

这里通过调用BooksServiceupdateBooks方法,将当前修改后的书本信息修改到源数据中,这里我们需要去books.service.ts中添加updateBooks方法:

// books.service.ts
// ...
updateBooks(books: Books): Observable<any>{
return this.http.put(this.booksUrl, books, httpOptions).pipe(
tap(_ => this.log(`修改书本的id是${books.id}`)),
catchError(this.handleError<Books>(`getBooks请求是id为${books.id}`))
)
}
// ...

知识点

HttpClient.put() 方法接受三个参数:URL 地址要修改的数据其他选项

httpOptions 常量需要定义在@Injectable修饰器之前。

现在,我们点击首页,选择一本书进入详情,修改标题然后保存,会发现,首页上这本书的名称也会跟着改变呢。这算是好了。

4.通过HTTP增加数据

我们可以新增一个页面,并添加上路由和按钮:

ng g component add

添加路由:

// app-routing.module.ts
// ...
import { AddComponent } from './add/add.component'; const routes: Routes = [
{ path: '', redirectTo:'/index', pathMatch:'full' },
{ path: 'index', component: IndexComponent},
{ path: 'detail/:id', component: DetailComponent},
{ path: 'add', component: AddComponent},
]

添加路由入口:

<!-- app.component.html -->
<!-- 省略一些代码 -->
<a routerLink="/add">添加书本</a>

编辑添加书本的页面:

<!-- add.component.html -->
<div class="add">
<h2>添加书本:</h2>
<label>标题:
<input [(ngModel)]="books.title" placeholder="请输入标题">
</label>
<label>作者:
<input [(ngModel)]="books.author" placeholder="请输入作者">
</label>
<label>书本id:
<input [(ngModel)]="books.id" placeholder="请输入书本id">
</label>
<label>封面地址:
<input [(ngModel)]="books.url" placeholder="请输入封面地址">
</label>
<div><button (click)="add(books)">添加</button></div>
</div>

初始化添加书本的数据:

// add.component.ts
// ...
import { Books } from '../books';
import { BooksService } from '../books.service';
import { HistoryService } from '../history.service';
import { Location } from '@angular/common';
export class AddComponent implements OnInit {
books: Books = {
id: 0,
url: '',
title: '',
author: ''
}
constructor(
private location: Location,
private booksservice: BooksService,
private historyservice: HistoryService
) { }
ngOnInit() {}
add(books: Books): void{
books.title = books.title.trim();
books.author = books.author.trim();
this.booksservice.addBooks(books)
.subscribe( book => {
this.historyservice.add(`新增书本${books.title},id为${books.id}`);
this.location.back();
});
}
}

然后在books.service.ts中添加addBooks方法,来添加一本书本的数据:

// books.service.ts
addBooks(books: Books): Observable<Books>{
return this.http.post<Books>(this.booksUrl, books, httpOptions).pipe(
tap((newBook: Books) => this.log(`新增书本的id为${newBook.id}`)),
catchError(this.handleError<Books>('添加新书'))
);
}

现在就可以正常添加书本啦。

5.通过HTTP删除数据

这里我们先为每个书本后面添加一个删除按钮,并绑定删除事件delete

<!-- books.component.html -->
<!-- 省略一些代码 -->
<span class="delete" (click)="delete(list)">X</span>
// books.component.ts
import { BooksService } from '../books.service';
export class BooksComponent implements OnInit {
@Input() list: Books;
constructor(
private booksservice: BooksService
) { }
// ...
delete(books: Books): void {
this.booksservice.deleteBooks(books)
.subscribe();
}
}

然后还要再books.service.ts中添加deleteBooks方法来删除:

// books.service.ts
deleteBooks(books: Books): Observable<Books>{
const id = books.id;
const url = `${this.booksUrl}/${id}`;
return this.http.delete<Books>(url, httpOptions).pipe(
tap(_ => this.log(`删除书本${books.title},id为${books.id}`)),
catchError(this.handleError<Books>('删除书本'))
);
}

这里需要在删除书本结束后,通知IndexComponent将数据列表中的这条数据删除,这里还需要再了解一下Angular 父子组件数据通信

然后我们在父组件IndexComponent上添加change事件监听,并传入本地的funChange

<!-- index.component.html -->
<app-books *ngFor="let item of books" [list]="item"
(change) = "funChange(item, $event)"
></app-books>

在对应的index.component.ts中添加funChange方法:

// index.component.ts
funChange(books, $event){
this.books = this.books.filter(h => h.id !== books.id);
}

再来,我们在子组件BooksComponent上多导入OutputEventEmitter,并添加@Output()修饰器和调用emit

import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
export class BooksComponent implements OnInit {
// ...
@Output()
change = new EventEmitter()
// ...
delete(books: Books): void {
this.booksservice.deleteBooks(books)
.subscribe(()=>{
this.change.emit(books);
});
}
}

这样就实现了我们父子组件之间的事件传递啦,现在我们的页面还是正常运行,并且删除一条数据后,页面数据会更新。

6.通过HTTP查找数据

还是在books.service.ts,我们添加一个方法getBooks,来实现通过ID来查找指定书本,因为我们是通过ID查找,所以返回的是单个数据,这里就是Observable<Books>类型:

// books.service.ts
getBooks(id: number): Observable<Books>{
const url = `${this.booksUrl}/${id}`;
return this.http.get<Books>(url).pipe(
tap( _ => this.log(`请求书本的id为${id}`)),
catchError(this.handleError<Books>(`getBooks请求是id为${id}`))
)
}

注意,这里 getBooks 会返回 Observable<Books>,是一个可观察的单个对象,而不是一个可观察的对象数组。

八、结语

这个项目其实很简单,但是我还是一步一步的写下来,一方面让自己更熟悉Angular,另一方面也是希望能帮助到更多朋友哈~

最终效果:

本部分内容到这结束

Author 王平安
E-mail pingan8787@qq.com
博 客 www.pingan8787.com
微 信 pingan8787
每日文章推荐 https://github.com/pingan8787/Leo_Reading/issues
JS小册 js.pingan8787.com
微信公众号 前端自习课

【CuteJavaScript】Angular6入门项目(4.改造组件和添加HTTP服务)的更多相关文章

  1. 【CuteJavaScript】Angular6入门项目(2.构建项目页面和组件)

    本文目录 一.项目起步 二.编写路由组件 三.编写页面组件 1.编写单一组件 2.模拟数据 3.编写主从组件 四.编写服务 1.为什么需要服务 2.编写服务 五.引入RxJS 1.关于RxJS 2.引 ...

  2. 【CuteJavaScript】Angular6入门项目(1.构建项目和创建路由)

    本文目录 一.项目起步 二.编写路由组件 三.编写页面组件 1.编写单一组件 2.模拟数据 3.编写主从组件 四.编写服务 1.为什么需要服务 2.编写服务 五.引入RxJS 1.关于RxJS 2.引 ...

  3. 【CuteJavaScript】Angular6入门项目(3.编写服务和引入RxJS)

    本文目录 一.项目起步 二.编写路由组件 三.编写页面组件 1.编写单一组件 2.模拟数据 3.编写主从组件 四.编写服务 1.为什么需要服务 2.编写服务 五.引入RxJS 1.关于RxJS 2.引 ...

  4. 读书笔记---《Docker 技术入门与实践》---为镜像添加SSH服务

    之前说到可以通过attach和exec两个命令登陆容器,但是如果遇到需要远程通过ssh登陆容器的场景,就需要手动添加ssh服务. 下面介绍两种方法创建带有ssh服务的镜像,commit命令创建和通过D ...

  5. 微信小程序入门与实战 常用组件API开发技巧项目实战*全

    第1章 什么是微信小程序? 第2章 小程序环境搭建与开发工具介绍 第3章 从一个简单的“欢迎“页面开始小程序之旅 第4章 第二个页面:新闻阅读列表 第5章 小程序的模板化与模块化 第6章 构建新闻详情 ...

  6. day 64 crm项目(1) admin组件的初识别以及应用

    前情提要: 今天进入项目学习阶段,crm 一个又老又土又实用的入门项目 一:django回顾 二:事前准备 1:首先创建django项目 2:在model中创建数据 from django.db im ...

  7. k8s kubernetes 核心笔记 镜像仓库 项目k8s改造(含最新k8s v1.16.2版本)

    k8s kubernetes 核心笔记 镜像仓库 项目k8s改造 2019/10/24 Chenxin 一 基本资料 一 参考: https://kubernetes.io/ 官网 https://k ...

  8. K2 BPM项目 基于COM组件调用SAP RFC 问题

    K2 BPM项目 基于COM组件调用SAP RFC 问题 问题前景: 环境:Win 2008 R2 64bit 最近项目中有支流程需求中需要在会计入账环节回写SAP的会计凭证. SAP组给我们提供.N ...

  9. Bootstrap入门(十七)组件11:分页与标签

    Bootstrap入门(十七)组件11:分页与标签   1.默认样式的分页 2.分页的大小 3.禁用的分页 4.翻页的效果 5.两端对齐的分页 6. 标签的不同样式 7. 标签的大小   先引入本地的 ...

随机推荐

  1. geetest拼图破解

    geetest拼图破解: 1.考虑到每个网站的拼图数量很少,可以先把他们下下来,后面在通过RGB来判断差异,来找出需要移动到的位置 2.通过python + selenium来控制页面的操控页面的元素 ...

  2. lqb 基础练习 杨辉三角形

    基础练习 杨辉三角形 时间限制:1.0s   内存限制:256.0MB     问题描述 杨辉三角形又称Pascal三角形,它的第i+1行是(a+b)i的展开式的系数. 它的一个重要性质是:三角形中的 ...

  3. Python3.7.1学习(二)使用schedule模块定时执行任务

    python中有一个轻量级的定时任务调度的库:schedule.他可以完成每分钟,每小时,每天,周几,特定日期的定时任务.因此十分方便我们执行一些轻量级的定时任务. 1 安装  1.1在cmd中输入p ...

  4. DNS简单配置

    ——主要执行的程序:/usr/sbin/named ——系统服务:named ——默认端口:53 ——运行时的虚拟根环境:/var/named/chroot ——主配置文件:/etc/named.co ...

  5. Nginx 本地建立负载均衡(Windows环境)

    需求: 现在有个需求:两台服务器,建立负载均衡. A服务器:IP:localhost:负载均衡主服务器:代理本地文件夹D:\\SampleData B服务器:IP:10.10.10.10:代理本地文件 ...

  6. 【NHOI2018】扫雷完成图

    [题目描述] 扫雷游戏完成后会显示一幅图,图中标示了每个格子的地雷情况.现在,一个 n * n 方阵中有 k 个地雷,请你输出它的扫雷完成图. [输入数据] 输入共 k+1 行: 第 1 行为 2 个 ...

  7. ubuntu windows mutual remote control

    Win10 remote control Ubuntu18 Part1.ubuntu settings 1.安装所需组件 sudo apt-get update //若没有desktop sharin ...

  8. linux磁盘分区三步走

    为了便于理解硬盘的物理结构 ,可将硬盘看作一个圆,它是坚硬金属材料制成的涂以磁性介质的盘片,不同容量硬盘的盘片数不等.每个盘有两面,都可记录信息.要了解硬盘的物理结构,需要弄懂磁道.扇区.柱面.簇等几 ...

  9. python遍历所有盘符下的图片并拷贝下来

    最近在学习python,闲着无聊就试着写啦这个小的脚本,虽然有很多不足,但是还是收获不少. 该脚本的功能: ①遍历本地计算机中的所有盘符,并将名称记录下来: ②循环遍历盘符下的所有图片(当然这里可以根 ...

  10. Gzip,BZip2,Lzo,Snappy总结

    gzip,bzip2,lzo,snappy是hadoop中比较常见的文件压缩格式,可以节省很多硬盘存储,以下是Gzip , BZip2 , Lzo Snappy 四种方式的优缺点 和使用场景 Gzip ...