3、编写页面组件html
4、编写服务前端
5、引入RxJSgit
6、改造组件github
7、HTTP改造web
本项目源码放在github数据库
从这里开始,咱们要使用RxJS来改造组件和添加新功能了,让整个项目更加完善。npm
HistoryComponent
组件ng g component hostory
而后在app.component.html
文件夹中添加组件:api
<!-- app.component.html --> <app-history></app-history>
这里咱们要开始作书本的增删改查功能,须要先建立一个HistoryService
服务,方便咱们实现这几个功能:数组
HistoryService
服务ng g service history
而后在生成的ts文件中,增长add
和clear
方法,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请求。
在这一章,咱们使用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
中导入 HttpClientInMemoryWebApiModule
和 InMemoryDataService
类(后面建立):
// 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,后面都要用到:
例如若是url
是api/books
,那么
api/books
api/books/id
,好比id
是1
,那么访问api/books/1
api/books/id
api/books/id
api/books
如今要为接下来的网络请求作一些准备,先在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', [])) ); }
这里咱们须要在原来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
模块,并在@NgModule
的imports
中引入,否则要报错了。
// 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(); }
这里经过调用BooksService
的updateBooks
方法,将当前修改后的书本信息修改到源数据中,这里咱们须要去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
修饰器以前。
如今,咱们点击首页,选择一本书进入详情,修改标题而后保存,会发现,首页上这本书的名称也会跟着改变呢。这算是好了。
咱们能够新增一个页面,并添加上路由和按钮:
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>('添加新书')) ); }
如今就能够正常添加书本啦。
这里咱们先为每一个书本后面添加一个删除按钮,并绑定删除事件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
上多导入Output
和EventEmitter
,并添加@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); }); } }
这样就实现了咱们父子组件之间的事件传递啦,如今咱们的页面仍是正常运行,而且删除一条数据后,页面数据会更新。
仍是在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 | 王平安 |
---|---|
pingan8787@qq.com | |
博 客 | www.pingan8787.com |
微 信 | pingan8787 |
每日文章推荐 | https://github.com/pingan8787... |
JS小册 | js.pingan8787.com |
微信公众号 | 前端自习课 |