四。Angular -- HttpClient

HttpClient 用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。

Posted by Azr on January 21, 2019

HttpClient对http协议支持非常好,使用起来很简单,版本更新快,功能也很强大,具有足够的灵活性和扩展性。

基本了解

概念

  1. HttpClient作用是发送Http请求。
  2. HttpClient在内部封装了浏览器提供的 XMLHttpRequest 接口。
  3. HttpClient使用基于可观察(Observable)对象的 API 。
  4. HttpClient提供了请求和响应拦截器。
  5. HttpClient流式错误处理机制。

使用

  1. 在跟模块app.module.ts导入HttpClient, 并在@NgModule导出HttpClientModule

    // 导入HttpClient
    import { HttpClientModule } from '@angular/common/http';
    @NgModule({
      // 导出
      imports: [
        ....
        HttpClientModule
      ]
    })
    
  2. assets文件夹下新建文件todos.json

    {
      "name": "TODOS",
      "desc": "TODOS desc"
    }
    
  3. 在组件app.component.ts中导入HttpClient,并且要在constructor中创建一个http的属性。

    // 导入HttpClient
    import { HttpClient } from '@angular/common/http'
    .... 
    export class AppComponent {
      // 告知组件要使用HttpClient
      constructor(private http: HttpClient) { }
         
      name: string
      getData() {
          // 发送get请求   第一个参数是要请求的文件  通过subscribe函数去订阅一个方法  将参数传递过来。
        this.http.get('../assets/todos.json').subscribe(res => {
          console.log(res)
        })
      }
    }
    

    app.component.html页面样式

    <div>
      <button (click)="getData()">获取数据</button>
      <h1>通过 HttpClient 获取到的数据为:</h1>
    </div>
    

    这样控制台可以打印出来,json文件中的数据。

    app.component.ts

    .... 
    export class AppComponent {
     .... 
      name: string
      getData() {
         // 要记得给res约束类型
        this.http.get('../assets/todos.json').subscribe((res: any) => {
          console.log(res)
          this.name = res.name
        })
      }
    }
    

添加类型检查

设置接口

interface Todo {
  name: string,
  desc: string
}
  1. 在对http添加泛型,res就不要添加类型提示了。而且在this.name = res.name后面的res会有只能提示,更加方便。
export class AppComponent {
....
  getData() {
      // 添加<Todo>的类型检查    res后面的any就可以去掉了。 
    this.http.get<Todo>('../assets/todos.json').subscribe((res) => {
      this.name = res.name
    })
  }
}
  1. 可以在res后面添加类型约束
export class AppComponent {
...
  getData() {
    this.http.get('../assets/todos.json').subscribe((res: TOdo) => {
      this.name = res.name
    })
  }
}
  1. 最好的办法就是两者同时使用
export class AppComponent {
    .....
  getData() {
    this.http.get<Todo>('../assets/todos.json').subscribe((res: TOdo) => {
      this.name = res.name
    })
  }
}

获取一个完整的响应

... 
export class AppComponent {
    .....
  getData() {
    this.http
      .get('../assets/todos.json',{ observe: 'response' })
      .subscribe((res)=> {
       console.log(res.headers.get('content-type'), res.body)
      })
  }
}  

就可以拿到响应头以及响应体,控制台会显示

application/json; charset=UTF-8

{name: "TODOS", desc: "TODOS desc"}
desc: "TODOS desc"
name: "TODOS"
__proto__: Object
.... 

添加类型检查 HttpResponse

  1. 添加泛型
... 
export class AppComponent {
    .....
  getData() {
    this.http
      // 添加泛型
      .get<Todo>('../assets/todos.json', { observe: 'response' })
      .subscribe(res => {
        console.log(res.headers.get('content-type'), res.body)
      })
  }
}
  1. 可以在res后面添加HttpResponse类型约束
import { HttpClient, HttpResponse } from '@angular/common/http';
... 
export class AppComponent {
    .....
  getData() {
    this.http
      // 引入HttpResponse 添加泛型
      .get('../assets/todos.json', { observe: 'response' })
      .subscribe((res: HttpResponse<Todo>) => {
        console.log(res.headers.get('content-type'), res.body)
      })
  }
}
  1. 最好的使用方式是将两者相结合。
import { HttpClient, HttpResponse } from '@angular/common/http';
... 
export class AppComponent {
    .....
   getData(){
    this.http
    .get<Todo>('../assets/todos.json', { observe: 'response' })
    .subscribe((res: HttpResponse<Todo>) => {
      console.log(res.headers.get('content-type'), res.body)
      this.name = res.body.name
    })
  }
}

处理错误

使用subscribe中的第二个参数来进行错误处理

... 
export class AppComponent {
    .....
    getData(){
    this.http
    .get<Todo>('../assets/todos.json', { observe: 'response' })
    .subscribe(
      (res: HttpResponse<Todo>) => {
        console.log(res.headers.get('content-type'), res.body)
        this.name = res.body.name
      }),
      // 错误处理
      err => { 
        console.log(err)
      }
  }
}

json-server提供接口

  1. 安装

    $ npm i -g json-server 
    
  2. 创建一个json文件db.json

    {
      "todos": [
        {
          "id": 1,
          "name": "eat",
          "done": false
        },
        {
          "id": 2,
          "name": "sleep",
          "done": true
        },
        {
          "id": 3,
          "name": "learn",
          "done": false
        }
      ]
    }
    
  3. 运行json-server

    $ json-server db.json
    

    控制台显示

    ➜  json-server db.json
       
      \{^_^}/ hi!
       
      Loading db.json
      Done
       
      Resources
      http://localhost:3000/todos
       
      Home
      http://localhost:3000
       
      Type s + enter at any time to create a snapshot of the database
    
  4. 在浏览器打开http://localhost:3000/todos就可以得到一个json-server 的接口

    localhost:3000/todos数据

发送rest请求

app.component.html

<div>
  <h1>发送请求 GET/POST/DELETE/PATCH</h1>
  <button (click)="getData()">(查询)GET请求</button>
  <br>
  <button (click)="addData()">(添加)POST请求</button>
  <br>
  <button (click)="delData()">(删除)DELETE请求</button>
  <br>
  <button (click)="updateData()">(修改)PATCH请求</button>
</div>

app.component.ts

import { Component } from '@angular/core';
import { HttpClient, HttpResponse } from '@angular/common/http';

interface Todo {
  name: string,
  desc: string
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent {

  constructor(private http: HttpClient) {}

  // 发送请求 GET/POST/DELETE/PATCH
  // REST API

  // 接口地址
  url = 'http://localhost:3000/todos'
  getData() { 
    this.http
      .get(this.url)
      .subscribe(res=>{
        console.log('getData==>', res)
      })
  }

  addData() {
    this.http
      .post(this.url, {
        "name": "add",
        "done": "false"
      })
      .subscribe(res => {
        console.log('addData==>',res)
      })
  }

  delData() {
    this.http
        .delete(`${this.url}/2`)
        .subscribe(res => {
          console.log('delData==>', res)
        })
  }

  updateData() {
    this.http
        .patch(`${this.url}/3`, {
          name: '好好学习 天天向上'
        })
        .subscribe(res => {
          console.log('updateData==>', res)
        })
    } 
}

HttpClient升级todos案例

先要在app.module.ts中导入导出HttpClient

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { TodosModule } from './todos/todos.module';
// 导入 HttpClientModule
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    TodosModule,
    // 导出 HttpClientModule
    HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

1. 获取数据

先在todos.service.ts导入HttpClient

// 导入HttpClient
import { HttpClient } from '@angular/common/http';
.....
export class TodosService {
  // 告知组件  在组件中使用HttpClient
  constructor(private http: HttpClient) {}
  ...
}

启用todosjson-server

➜ json-server db.json

  \{^_^}/ hi!

  Loading db.json
  Done

  Resources
  http://localhost:3000/todos

  Home
  http://localhost:3000

  Type s + enter at any time to create a snapshot of the database

添加Todos的接口地址,将之前的假数据去掉;在服务中不做任何的处理,直接返回。

export class TodosService {  
  ...
  todosUrl = 'http://localhost:3000/todos';
  // 提供数据
  getTodos() {
    // const todos: Todo[] = [
    //   {id: 1, name: '1', done: true},
    //   {id: 2, name: '2', done: false}
    // ]
    // // 添加一个初终数据
    // this.todos = todos
    // return todos
    return this.http.get(this.todosUrl)
  }
  ...
}

在组件todo.component.ts中使用到数据,在进行处理。在ngOnInit这个里面调用subscribe

.....
export class TodoComponent implements OnInit {
  ....
  ngOnInit() {
    this.TodosService
        .getTodos()
        .subscribe((todos: Todo[]) => {
          this.todos = [...todos]
          console.log("Todos组件中获取到的数据-->",todos)
        })
  }
}

添加一下类型检查

todos.service.ts

export class TodosService {  
  ...
  todosUrl = 'http://localhost:3000/todos';
  // 提供数据
  getTodos() {
    // 泛型 <Todo[]>   
    return this.http.get<Todo[]>(this.todosUrl)
  }
  ...
}

todo.component.ts

.....
export class TodoComponent implements OnInit {
  ....
  ngOnInit() {
    this.TodosService
        .getTodos()
        // 约束 Todo[]
        .subscribe((todos: Todo[]) => {
          this.todos = todos
          console.log("Todos组件中获取到的数据-->",todos)
        })
  }
}

2. 添加数据

todos.service.ts

export class TodosService {  
  ...
  todosUrl = 'http://localhost:3000/todos';
  // 添加任务
  addTodo(todoName: string) {
    // let id: number
    // if (this.todos.length === 0) {
    //   return 1
    // } else {
    //   id = this.todos[this.todos.length - 1 ].id - 1
    // }

    // this.todos.push({
    //   id,
    //   name: todoName,
    //   done: false,
    // })
      
    //  post<Todo>   泛型
    return this.http.post<Todo>(this.todosUrl, {
      "name": todoName,
      "done": true
    })
 }
  ...
}

todo.component.ts

.....
export class TodoComponent implements OnInit {
    ....
    addTodo(todoName: string) {
    // this.TodosService.addTodo(todoName)
    this.TodosService
        .addTodo(todoName)
        .subscribe(todo => {
          console.log('Todos组件中获取到的数据-->',todo)
        })
    }
 
}

这个,添加数据,就会打印出来数据

Todos组件中获取到的数据--> 
Object
        done: true
        id: 7
        name: "123"
        __proto__:

todo.component.ts进行页面显示添加数据,this.todos.push(todo)

todo.component.ts

.....
export class TodoComponent implements OnInit {
    ....
    addTodo(todoName: string) {
        this.TodosService
            .addTodo(todoName)
        	// (todo: Todo)  约束
            .subscribe((todo: Todo)=> {
              console.log('Todos组件中获取到的数据-->',todo)
              this.todos.push(todo)
            }) 
    }
 
}

3. 切换任务状态

todos.service.ts

export class TodosService {  
  ...
  todosUrl = 'http://localhost:3000/todos';
  // 切换任务
  changeTodo(id: number,done: boolean) {
    // const curTodo = this.todos.find(todo => todo.id === id)
    // curTodo.done = !curTodo.done
    return this.http.patch<Todo>(`${this.todosUrl}/${id}`,{ done })
  }
  ...
}

todo.component.ts

.....
export class TodoComponent implements OnInit {
  ....
  changeTodo(id: number) {
    // this.TodosService.changeTodo(id)
    const curTodo: Todo = this.todos.find(todo => todo.id === id)
    this.TodosService
        .changeTodo(id,!curTodo.done)
        .subscribe((todo: Todo) => {
          console.log('Todos组件中切换-->',todo)
          curTodo.done = todo.done
        })
  }
}

4. 删除任务

todos.service.ts

export class TodosService {  
  ...
  todosUrl = 'http://localhost:3000/todos';
  // 删除任务
  delTodo(id: number) {
    // const curIndex = this.todos.findIndex(todo => todo.id === id)
    // this.todos.splice(curIndex,1)
    return this.http.delete<Object>(`${this.todosUrl}/${id}`)
  }
  ...
}

todo.component.ts

.....
export class TodoComponent implements OnInit {
  ....
  delTodo(id: number) {
    // this.TodosService.delTodo(id)
    this.TodosService
        .delTodo(id)
        .subscribe(() => {
          console.log('success')
          this.todos = this.todos.filter(todo => todo.id !== id)
        })
  }
}

5. 总结

  1. 获取数据
    1. 在跟模块中导入HttpClientModule的模块(提供了http的能力)。做为主模块的依赖项才可以进行使用。
    2. 在服务中完成了增删改查的功能
      1. 在服务中导入HttpClient
      2. 注册到服务中
      3. 完成增删改查
      4. getTodo使用http方法,并将其返回
    3. 请求成功的话,在组件中的subscribe就会被执行,并且数据会做为参数传递。
  2. 添加数据
    1. addTodo中使用http请求的post方法,第一个参数是接口地址,第二个参数是要添加的数据,将其返回。
    2. 当添加成功之后就会执行subscribe中的回调函数将数据添加到页面的todos数据中就可以。
  3. 切换/删除任务其实都是一样的,其两者都需要将ID传递给接口。

本文首次发布于 Azr的博客, 作者 @azrrrrr ,转载请保留原文链接.

原文链接: http://amor9.cn/2019/01/21/ng4/