0
点赞
收藏
分享

微信扫一扫

【Angular】163-Angular6入门项目(四)

本文目录

一、项目起步

二、编写路由组件

三、编写页面组件

  • 1.编写单一组件
  • 2.模拟数据
  • 3.编写主从组件

四、编写服务

  • 1.为什么需要服务
  • 2.编写服务

五、引入RxJS

  • 1.关于RxJS
  • 2.引入RxJS
  • 3.改造数据获取方式

六、改造组件

  • 1.添加历史记录组件
  • 2.添加和删除历史记录

七、HTTP改造

  • 1.引入HTTP
  • 2.通过HTTP请求数据
  • 3.通过HTTP修改数据
  • 4.通过HTTP增加数据
  • 5.通过HTTP删除数据
  • 6.通过HTTP查找数据

八、结语


这个入门项目是我学习完Angular 英雄指南教程(https://angular.cn/tutorial)后,自己手写的一个练习项目,一步一步来,最终的项目源码可以原文链接查看,大佬们请指点啦。


六、改造组件

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

1.添加历史记录组件

  • 创建 ​HistoryComponent​组件
  1. ng g component hostory

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

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

2.添加增删改查功能

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

  • 创建 ​HistoryService​服务
  1. ng g service history

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

  1. // history.service.ts
  2. export class HistoryService {
  3. history: string[] = [];
  4. add(history: string){
  5. this.history.push(history);
  6. }
  7. clear(){
  8. this.history = [];
  9. }
  10. }
  • 使用 ​HistoryService​服务

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

  1. // books.service.ts
  2. import { HistoryService } from './history.service';
  3. constructor(
  4. private historyservice: HistoryService
  5. ) { }
  6. getBooks(): void{
  7. this.historyservice.add('请求书本数据')
  8. this.booksservice.getBookList()
  9. .subscribe(books => this.books = books);
  10. }

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

  1. // index.component.ts
  2. import { HistoryService } from '../history.service';
  3. constructor(
  4. private booksservice: BooksService,
  5. private historyservice: HistoryService
  6. ) { }
  7. getBooks(): void{
  8. this.historyservice.add('访问首页书本列表');
  9. this.booksservice.getBookList()
  10. .subscribe(books => this.books = books);
  11. }

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

  1. // history.component.ts
  2. import { HistoryService } from '../history.service';
  3. export class HistoryComponent implements OnInit {
  4. constructor(private historyservice: HistoryService) { }
  5. ngOnInit() {}
  6. }
  7. <!-- history.component.html -->
  8. <div *ngIf="historyservice.history.length">
  9. <h2>操作历史:</h2>
  10. <div>
  11. <button class="clear"
  12. (click)="historyservice.clear()"
  13. >清除</button>
  14. <div *ngFor="let item of historyservice.history">{{item}}</div>
  15. </div>
  16. </div>

代码解释
​​*ngIf="historyservice.history.length"​​,是为了防止还没有拿到历史数据,导致后面的报错。
​​(click)="historyservice.clear()"​​, 绑定我们服务中的 ​clear​​事件,实现清除缓存。
​​*ngFor="let item of historyservice.history"​,将我们的历史数据渲染到页面上。

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

【Angular】163-Angular6入门项目(四)_历史记录

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

  1. // detail.component.ts
  2. import { HistoryService } from '../history.service';

  3. export class DetailComponent implements OnInit {
  4. constructor(
  5. private route: ActivatedRoute,
  6. private location: Location,
  7. private booksservice: BooksService,
  8. private historyservice: HistoryService
  9. ) { }
  10. //...
  11. getBooks(id: number): void {
  12. this.books = this.booksservice.getBook(id);
  13. this.historyservice.add(`查看书本${this.books.title},id${this.books.id}`);
  14. console.log(this.books)
  15. }
  16. }

【Angular】163-Angular6入门项目(四)_历史记录_02

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

七、HTTP改造

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

1.引入HTTP

在这一章,我们使用Angular提供的 ​HttpClient​​ 来添加一些数据持久化特性。
然后实现对书本数据进行获取,增加,修改,删除和查找功能。

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

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

  1. import { HttpClientModule } from '@angular/common/http';
  2. @NgModule({
  3. //...
  4. imports: [
  5. BrowserModule,
  6. AppRoutingModule,
  7. HttpClientModule
  8. ],
  9. //...
  10. })

这边我们使用 内存 Web API(In-memory Web API) 模拟出的远程数据服务器通讯。
注意: 这个内存 Web API 模块与 Angular 中的 HTTP 模块无关。

通过下面命令来安装:

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

然后在 ​app.module.ts​​中导入 ​HttpClientInMemoryWebApiModule​​ 和 ​InMemoryDataService​ 类(后面创建):

  1. // app.module.ts
  2. import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
  3. import { InMemoryDataService } from './in-memory-data.service';
  4. @NgModule({
  5. // ...
  6. imports: [
  7. // ...
  8. HttpClientInMemoryWebApiModule.forRoot(
  9. InMemoryDataService, {dataEncapsulation:false}
  10. )
  11. ],
  12. // ...
  13. })
  14. export class AppModule { }

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

然后我们要创建 ​InMemoryDataService​类:

  1. ng g service InMemoryData

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

  1. // in-memory-data.service.ts
  2. import { Injectable } from '@angular/core';
  3. import { InMemoryDbService } from 'angular-in-memory-web-api';
  4. import { Books } from './books';
  5. @Injectable({
  6. providedIn: 'root'
  7. })
  8. export class InMemoryDataService implements InMemoryDbService {
  9. createDb(){
  10. const books = [
  11. {
  12. id: 1,
  13. url: 'https://img3.doubanio.com/view/subject/m/public/s29988481.jpg',
  14. title: '像火焰像灰烬',
  15. author: '程姬',
  16. },
  17. // 省略其他9条数据
  18. ];
  19. return {books};
  20. }
  21. constructor() { }
  22. }

这里先总结 ​InMemoryDbService​​所提供的RESTful API,后面都要用到:
例如如果 ​​url​​是 ​api/books​,那么

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

2.通过HTTP请求数据

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

  1. // books.service.ts
  2. import { HttpClient, HttpHeaders} from '@angular/common/http';
  3. // ...
  4. export class BooksService {
  5. constructor(
  6. private historyservice: HistoryService,
  7. private http: HttpClient
  8. ) { }
  9. private log(histories: string){
  10. this.historyservice.add(`正在执行:${histories}`)
  11. }
  12. private booksUrl = 'api/books'; // 提供一个API供调用
  13. // ...
  14. }

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

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

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

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

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

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

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

知识点
​​.pipe()​​ 方法用来扩展 ​Observable​​ 的结果。
​​catchError()​​ 操作符会拦截失败的 Observable。并把错误对象传给错误处理器,错误处理器会处理这个错误。
​​handleError()​ 错误处理函数做了两件事,发出错误通知和返回空结果避免程序出错。

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

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

3.通过HTTP修改数据

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

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

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

  1. // app.module.ts
  2. // ...
  3. import { FormsModule } from '@angular/forms';
  4. @NgModule({
  5. // ...
  6. imports: [
  7. // ...
  8. FormsModule
  9. ],
  10. // ...
  11. })

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

  1. // detail.component.ts
  2. save(): void {
  3. this.historyservice.updateBooks(this.books)
  4. .subscribe(() => this.goBack());
  5. }
  6. goBack(): void {
  7. this.location.back();
  8. }

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

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

知识点
​​HttpClient.put()​​ 方法接受三个参数: ​URL地址​​、 ​要修改的数据​​和 ​其他选项​​。
​​httpOptions​​ 常量需要定义在 ​@Injectable​修饰器之前。

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

4.通过HTTP增加数据

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

  1. ng g component add

添加路由:

  1. // app-routing.module.ts
  2. // ...
  3. import { AddComponent } from './add/add.component';

  4. const routes: Routes = [
  5. { path: '', redirectTo:'/index', pathMatch:'full' },
  6. { path: 'index', component: IndexComponent},
  7. { path: 'detail/:id', component: DetailComponent},
  8. { path: 'add', component: AddComponent},
  9. ]

添加路由入口:

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

编辑添加书本的页面:

  1. <!-- add.component.html -->
  2. <div class="add">
  3. <h2>添加书本:</h2>
  4. <label>标题:
  5. <input [(ngModel)]="books.title" placeholder="请输入标题">
  6. </label>
  7. <label>作者:
  8. <input [(ngModel)]="books.author" placeholder="请输入作者">
  9. </label>
  10. <label>书本id:
  11. <input [(ngModel)]="books.id" placeholder="请输入书本id">
  12. </label>
  13. <label>封面地址:
  14. <input [(ngModel)]="books.url" placeholder="请输入封面地址">
  15. </label>
  16. <div><button (click)="add(books)">添加</button></div>
  17. </div>

初始化添加书本的数据:

  1. // add.component.ts
  2. // ...
  3. import { Books } from '../books';
  4. import { BooksService } from '../books.service';
  5. import { HistoryService } from '../history.service';
  6. import { Location } from '@angular/common';
  7. export class AddComponent implements OnInit {
  8. books: Books = {
  9. id: 0,
  10. url: '',
  11. title: '',
  12. author: ''
  13. }
  14. constructor(
  15. private location: Location,
  16. private booksservice: BooksService,
  17. private historyservice: HistoryService
  18. ) { }
  19. ngOnInit() {}
  20. add(books: Books): void{
  21. books.title = books.title.trim();
  22. books.author = books.author.trim();
  23. this.booksservice.addBooks(books)
  24. .subscribe( book => {
  25. this.historyservice.add(`新增书本${books.title},id${books.id}`);
  26. this.location.back();
  27. });
  28. }
  29. }

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

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

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

【Angular】163-Angular6入门项目(四)_html_03

5.通过HTTP删除数据

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

  1. <!-- books.component.html -->
  2. <!-- 省略一些代码 -->
  3. <span class="delete" (click)="delete(list)">X</span>
  4. // books.component.ts
  5. import { BooksService } from '../books.service';
  6. export class BooksComponent implements OnInit {
  7. @Input() list: Books;
  8. constructor(
  9. private booksservice: BooksService
  10. ) { }
  11. // ...
  12. delete(books: Books): void {
  13. this.booksservice.deleteBooks(books)
  14. .subscribe();
  15. }
  16. }

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

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

这里需要在删除书本结束后,通知 ​IndexComponent​​将数据列表中的这条数据删除,这里还需要再了解一下Angular 父子组件数据通信。
然后我们在父组件 ​​IndexComponent​​上添加 ​change​​事件监听,并传入本地的 ​funChange​:

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

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

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

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

  1. import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
  2. export class BooksComponent implements OnInit {
  3. // ...
  4. @Output()
  5. change = new EventEmitter()
  6. // ...
  7. delete(books: Books): void {
  8. this.booksservice.deleteBooks(books)
  9. .subscribe(()=>{
  10. this.change.emit(books);
  11. });
  12. }
  13. }

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

6.通过HTTP查找数据

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

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

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

八、结语

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

【Angular】163-Angular6入门项目(四)_数据_04

【Angular】163-Angular6入门项目(四)_历史记录_05


举报

相关推荐

0 条评论