本文目录

本项目源码放在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. 学习Java第一步:安装Intellij IDEA和JDK

    注:其实真正学习一门新语言的第一步并不是安装开发工具,我是C#转JAVA,有一点编程经验了,所以可以直接跳过前面几步,直接上IDE. 1.下载IntelliJ IDEA [官网] http://www ...

  2. linux文件时间

    Linux 查看文件修改时间(精确到秒)(简单) ls --full-time 查看文件时间戳命令:stat test.txt linux 下查看文件修改时间 等(详细) 查看文件时间戳命令:stat ...

  3. 防火墙和SELinux

    在/etc/sysconfig/selinux中修改SELINUX=disabled关闭SELinux 执行systemctl disable firewalld关闭防火墙 然后重启计算机

  4. 通过django 速成 blog

    1.            创建项目 33进入在python目录下的scripts文件后执行 django-admin.py   startproject  mysite 这样就生成了名为mysite ...

  5. python中的__call__方法

    在Python中,函数其实是一个对象: >>> f = abs >>> f.__name__ 'abs' >>> f(-) 由于 f 可以被调用, ...

  6. PHP创建对象的6种方式

    创建对象实例: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ...

  7. ASP.NET Core gRPC 使用 Consul 服务注册发现

    一. 前言 gRPC 在当前最常见的应用就是在微服务场景中,所以不可避免的会有服务注册与发现问题,我们使用gRPC实现的服务可以使用 Consul 或者 etcd 作为服务注册与发现中心,本文主要介绍 ...

  8. RxJS入门

    一.RxJS是什么? 官方文档使用了一句话总结RxJS: Think of RxJS as Lodash for events.那么Lodash主要解决了什么问题?Lodash主要集成了一系列关于数组 ...

  9. day 18 random模块 时间模块 sys模块 os模块

    import random 利用random模块可以进行从一个列表或者数字范围之间随机取出一个数字 # 取随机小数 : 数学计算 print(random.random()) # 取0-1之间的小数 ...

  10. Git使用和介绍-基础指令

    转载请标明出处:http://blog.csdn.net/shensky711/article/details/52210625 本文出自: [HansChen的博客] 查看已有配置 取消已有的配置 ...