Get data from a server
- 在本教程中,您将在Angular的HttpClient的帮助下添加以下数据持久性特性。
- HeroService通过HTTP请求获取英雄数据。用户可以通过HTTP添加、编辑和删除英雄并保存这些更改。
- 用户可以通过名字搜索英雄。
一、 启用HTTP服务
-
HttpClient是Angular用来通过HTTP与远程服务器通信的机制。
-
通过两个步骤使应用程序中的所有地方都可以使用HttpClient。首先,通过导入将其添加到根AppModule:
```
<-src/app/app.module.ts (HttpClientModule import)->
import { HttpClientModule } from '@angular/common/http';
```
-
*:*接下来,仍然在AppModule中,将HttpClient添加到导入数组中:
<-src/app/app.module.ts (imports array excerpt)-> @NgModule({ imports: [ HttpClientModule, ], })
二、模拟数据服务器
-
本教程示例使用内存中的Web API模块模拟与远程数据服务器的通信。
-
安装模块后,应用程序将向HttpClient发出请求并接收响应,而不知道内存中的Web API正在拦截这些请求,将它们应用到内存中的数据存储中,并返回模拟的响应。
-
通过使用内存中的Web API,您不必设置服务器来了解HttpClient。
重要提示:在Angular中,内存中的Web API模块与HTTP无关。 如果您只是阅读本教程以了解HttpClient,那么您可以跳过这一步。如果您正在编写本教程中的代码,请留在这里并立即添加内存中的Web API。
-
使用以下命令从npm安装内存中的Web API包:
npm install angular-in-memory-web-api --save
-
在AppModule中,导入HttpClientInMemoryWebApiModule和稍后将创建的InMemoryDataService类。
<-src/app/app.module.ts (In-memory Web API imports)-> import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api'; import { InMemoryDataService } from './in-memory-data.service';
-
在HttpClientModule之后,将HttpClientInMemoryWebApiModule添加到AppModule import数组中,并使用InMemoryDataService配置它。
<-src/app/app.module.ts (imports array excerpt)-> HttpClientModule, // HttpClientInMemoryWebApiModule模块拦截HTTP请求 // 返回模拟的响应数据 // 在真正的服务器准备接收请求时删除它 HttpClientInMemoryWebApiModule.forRoot( InMemoryDataService, { dataEncapsulation: false } )
-
forRoot()配置方法接受一个InMemoryDataService类,该类启动内存中的数据库。
-
生成类src/app/in-memory-data.service。ts使用以下命令:
```
ng generate service InMemoryData
```
-
用in-memory-data.service.ts的内容代替默认内容:
<-src/app/in-memory-data.service.ts-> import { InMemoryDbService } from 'angular-in-memory-web-api'; import { Hero } from './hero'; import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root', }) export class InMemoryDataService implements InMemoryDbService { createDb() { const heroes = [ { id: 11, name: 'Dr Nice' }, { id: 12, name: 'Narco' }, { id: 13, name: 'Bombasto' }, { id: 14, name: 'Celeritas' }, { id: 15, name: 'Magneta' }, { id: 16, name: 'RubberMan' }, { id: 17, name: 'Dynama' }, { id: 18, name: 'Dr IQ' }, { id: 19, name: 'Magma' }, { id: 20, name: 'Tornado' } ]; return {heroes}; } // 重写genId方法以确保英雄始终具有id。 // 如果heroes数组是空的, // 下面的方法返回初始数字(11)。 // 如果heroes数组不是空的,则下面的方法返回最高的值 // 英雄id + 1。 genId(heroes: Hero[]): number { return heroes.length > 0 ? Math.max(...heroes.map(hero => hero.id)) + 1 : 11; } }
in-memory-data.service.ts文件代替了
mock-heroes.ts
,现在可以安全删除了。当服务器准备好时,您将分离内存中的Web API,应用程序的请求将经过服务器。
三、Heroes and HTTP
-
在HeroService中,导入HttpClient和HttpHeaders:
<-src/app/hero.service.ts -> import { HttpClient, HttpHeaders } from '@angular/common/http';
-
仍然在HeroService中,将HttpClient注入到名为http的私有属性的构造函数中。
<-src/app/hero.service.ts-> constructor( private http: HttpClient, private messageService: MessageService) { }
-
注意,您一直在注入MessageService,但是由于您会频繁地调用它,所以将它封装在一个私有log()方法中:
<-src/app/hero.service.ts-> /** Log a HeroService message with the MessageService */ private log(message: string) { this.messageService.add(`HeroService: ${message}`); }
-
使用服务器上英雄资源的地址来定义表单的heroesUrl:base/:collectionName。这里base是发出请求的资源,而collectionName是 in-memory-data-service.ts 中的heroes数据对象。
<-src/app/hero.service.ts-> private heroesUrl = 'api/heroes'; // URL to web api
四、Get heroes with HttpClient
-
当前的HeroService.getHeroes()使用()函数的RxJS来返回一个模拟英雄数组,作为一个可观察到的<Hero[]>。
<-src/app/hero.service.ts (getHeroes with RxJs 'of()')-> getHeroes(): Observable<Hero[]> { return of(HEROES); }
-
转换该方法使用HttpClient如下:
<-src/app/hero.service.ts-> /** GET heroes from the server */ getHeroes (): Observable<Hero[]> { return this.http.get<Hero[]>(this.heroesUrl) }
- 刷新浏览器。应该可以成功地从模拟服务器加载英雄数据。
- 您已经将of()交换为http.get(),应用程序在没有任何其他更改的情况下继续工作,因为两个函数都返回一个可观察到的<Hero[]>。
HttpClient
methods return one value
所有HttpClient方法都返回一个可观察到的RxJS。
HTTP是一个请求/响应协议。你发出一个请求,它会返回一个响应。
一般来说,一个被观察对象可以在一段时间内返回多个值。来自HttpClient的一个可观察对象总是发出一个单独的值,然后完成,不再发出。
这个特定的HttpClient.get()调用返回一个可观察的<Hero[]>;也就是说,“一个可见的英雄数组”。实际上,它只会返回一个英雄数组。
HttpClient.get()
returns response data
-
默认情况下,HttpClient.get()以无类型JSON对象的形式返回响应主体。应用可选的类型说明符<Hero[]>,增加了TypeScript功能,减少了编译时的错误。
-
服务器的数据API决定JSON数据的形状。英雄指南数据API以数组的形式返回英雄数据。
```
其他api可能会将您想要的数据隐藏在对象中。您可能需要使用RxJS map()操作符来处理可观察结果,从而挖掘出数据。
虽然这里没有讨论,但是示例源代码中包含了getHeroNo404()方法中的map()示例。
```
Error handling
-
事情会出错,特别是当您从远程服务器获取数据时。getheroes()方法应该能捕获错误并执行适当的操作。
-
为了捕获错误,您可以通过RxJS的catchError()操作符从http.get()“管道”传输观察到的结果。
-
从rxjs/操作符以及稍后需要的其他操作符中导入catch错误符号。
```
<-src/app/hero.service.ts->
import { catchError, map, tap } from 'rxjs/operators';
```
-
现在使用pipe()方法扩展observable结果,并给它一个catchError()操作符。
<-src/app/hero.service.ts-> getHeroes (): Observable<Hero[]> { return this.http.get<Hero[]>(this.heroesUrl) .pipe( catchError(this.handleError<Hero[]>('getHeroes', []))//第一个参数是被监察的函数,第二个是返回的对象 ); }
-
操作符catchError()截获一个失败的观察目标。它向错误传递一个错误处理程序,该处理程序可以对错误做它想做的事情。
- 下面的handleError()方法报告错误,然后返回一个无害的结果,以便应用程序继续工作。
handleError
-
下面的handleError()将被许多HeroService方法共享,因此它被一般化以满足不同的需求。
-
它不是直接处理错误,而是返回一个错误处理函数来捕获它配置的错误,该函数使用失败操作的名称和一个安全的返回值。
```
<-src/app/hero.service.ts->
/**
* 处理失败的Http操作。
* 让应用程序继续。
* @参数操作 - 失败操作的名称
* @参数结果 - 可选值作为可观察结果返回
*/
private handleError<T> (operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
// TODO:将错误发送到远程日志记录基础设施
console.error(error); // 改为登录控制台
// TODO:更好地转换用户使用的错误
this.log(`${operation} failed: ${error.message}`);
// 通过返回一个空结果让应用程序继续运行。
return of(result as T);
};
}
```
-
在向控制台报告错误之后,处理程序构造一个用户友好的消息并向应用程序返回一个安全值,以便应用程序可以继续工作。
- 因为每个服务方法都返回一种不同类型的可观察结果,所以handleError()接受一个类型参数,这样它就可以返回应用程序所期望的类型的安全值。
Tap into the Observable
-
HeroService方法将进入可观察值流,并通过log()方法将消息发送到页面底部的消息区域。
-
他们将使用RxJS tap()操作符来完成这些操作,该操作符查看可观察值,对这些值执行一些操作,并将它们传递下去。tap()回调不会触及值本身。
-
下面是getHeroes()的最终版本,带有记录操作的tap()。
```
<-src/app/hero.service.ts->
/** GET heroes from the server */
getHeroes (): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl)
.pipe(
tap(_ => this.log('fetched heroes')),
catchError(this.handleError<Hero[]>('getHeroes', []))
);
}
```
Get hero by id
-
大多数web api都支持以下形式的get by id请求:baseURL/:id。
-
这里,基本URL是在Heroes和HTTP部分(api/ Heroes)中定义的heroesURL, id是您想要检索的英雄的编号。例如,api /英雄/ 11。
-
使用以下方法更新HeroService getHero()方法以发出请求:
```
<-src/app/hero.service.ts->
/** GET hero by id. Will 404 if id not found */
getHero(id: number): Observable<Hero> {
const url = `${this.heroesUrl}/${id}`;
return this.http.get<Hero>(url).pipe(
tap(_ => this.log(`fetched hero id=${id}`)),
catchError(this.handleError<Hero>(`getHero id=${id}`))
);
}
```
-
与getHeroes()有三个显著的区别:
getHero()使用所需的英雄id构造一个请求URL。 服务器应该使用单个英雄响应,而不是一组英雄。 getHero()返回一个可观察到的<Hero>(“一个可观察到的Hero对象”),而不是一个可观察到的Hero数组。
Update heroes
-
在英雄详情视图中编辑一个英雄的名字。当您键入时,英雄名称将更新页面顶部的标题。但是当你点击“返回”按钮时,改变就会丢失。
-
如果希望更改保持不变,则必须将它们写回服务器 。
-
在hero detail模板的末尾,添加一个带有click事件绑定的save按钮,该按钮调用一个名为save()的新组件方法。
```
<-src/app/hero-detail/hero-detail.component.html (save)->
<button (click)="save()">save</button>
```
-
在HeroDetail组件类中,添加以下save()方法,该方法使用hero服务updateHero()方法持久保存英雄的名字更改,然后导航回之前的视图。
<-src/app/hero-detail/hero-detail.component.ts (save)-> save(): void { this.heroService.updateHero(this.hero) .subscribe(() => this.goBack()); }
Add HeroService.updateHero()
-
updateHero()方法的整体结构与getHeroes()类似,但它使用http.put()将更改后的英雄持久化到服务器上。将以下内容添加到HeroService。
<-src/app/hero.service.ts (update)-> /** PUT: update the hero on the server */ updateHero (hero: Hero): Observable<any> { return this.http.put(this.heroesUrl, hero, this.httpOptions).pipe( tap(_ => this.log(`updated hero id=${hero.id}`)), catchError(this.handleError<any>('updateHero')) ); }
- put()方法接受三个参数:
- the URL
- the data to update
- options
- put()方法接受三个参数:
-
URL没有改变。heroes web API通过查看英雄的id知道要更新哪个英雄。
-
heroes web API期望在HTTP保存请求中有一个特殊的头。该头位于HeroService中定义的httpOptions常量中。将以下内容添加到HeroService类。
```
<-src/app/hero.service.ts->
httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
```
- 刷新浏览器,更改英雄名并保存更改。HeroDetailComponent中的save()方法会导航到前一个视图。英雄现在以更改后的名字出现在列表中。
Add a new hero
-
要添加一个英雄,这个应用程序只需要英雄的名字。您可以使用一个元素与一个add按钮相匹配。
-
在HeroesComponent模板中,在标题后面插入以下内容:
```
<-src/app/heroes/heroes.component.html (add)->
<div>
<label>Hero name:
<input #heroName />
</label>
<!-- (click) passes input value to add() and then clears the input -->
<button (click)="add(heroName.value); heroName.value=''">
add
</button>
</div>
```
-
在响应单击事件时,调用组件的单击处理程序add(),然后清除输入字段,以便为另一个名称做好准备。将以下内容添加到HeroesComponent类:
<-src/app/heroes/heroes.component.ts (add)-> add(name: string): void { name = name.trim(); if (!name) { return; } this.heroService.addHero({ name } as Hero) .subscribe(hero => { this.heroes.push(hero); }); }
-
当给定的名称是非空时,处理程序将从名称中创建一个类似hero的对象(只是缺少id),并将其传递给服务addHero()方法。
-
当addHero()保存成功时,subscribe()回调将接收新英雄并将其推入heroes列表中进行显示。
-
将以下addHero()方法添加到HeroService类中。
```
<-src/app/hero.service.ts (addHero)->
/** POST: add a new hero to the server */
addHero (hero: Hero): Observable<Hero> {
return this.http.post<Hero>(this.heroesUrl, hero, this.httpOptions).pipe(
tap((newHero: Hero) => this.log(`added hero w/ id=${newHero.id}`)),
catchError(this.handleError<Hero>('addHero'))
);
}
```
-
addHero()与updateHero()有两个不同之处:
它调用HttpClient.post()而不是put()。 它期望服务器为新英雄生成一个id,并在Observable< hero >中返回给调用者。
-
刷新浏览器并添加一些英雄。
Delete a hero
-
英雄列表中的每个英雄都应该有一个删除按钮。
-
元素中的英雄名称之后,将以下按钮元素添加到HeroesComponent模板中。
```
<-src/app/heroes/heroes.component.html->
<button class="delete" title="delete hero"
(click)="delete(hero)">x</button>
```
-
英雄列表的HTML应该是这样的:
<-src/app/heroes/heroes.component.html (list of heroes)-> <ul class="heroes"> <li *ngFor="let hero of heroes"> <a routerLink="/detail/{{hero.id}}"> <span class="badge">{{hero.id}}</span> {{hero.name}} </a> <button class="delete" title="delete hero" (click)="delete(hero)">x</button> </li> </ul>
-
要将删除按钮放置在hero条目的最右侧,请向hero.com ponent. CSS添加一些CSS。您将在下面的最终评审代码中找到该CSS。
-
将delete()处理程序添加到组件类。
```
delete(hero: Hero): void {
this.heroes = this.heroes.filter(h => h !== hero);
this.heroService.deleteHero(hero).subscribe();
}
```
-
尽管组件将hero删除委托给HeroService,但它仍然负责更新自己的英雄列表。组件的delete()方法将立即从该列表中删除hero-to-delete,并预测HeroService将在服务器上成功。
-
该组件实际上与heroService.delete()返回的Observable没有任何关系,但是它必须订阅。
```
如果忽略了subscribe(),服务将不会向服务器发送删除请求。通常,一个被观察对象在订阅之前什么也不做。
通过暂时删除subscribe(),单击“Dashboard”,然后单击“Heroes”,您自己确认一下。您将再次看到完整的英雄列表。
```
-
接下来,像这样向HeroService添加一个deleteHero()方法。
<-src/app/hero.service.ts (delete)-> /** DELETE: delete the hero from the server */ deleteHero (hero: Hero | number): Observable<Hero> { const id = typeof hero === 'number' ? hero : hero.id; const url = `${this.heroesUrl}/${id}`; return this.http.delete<Hero>(url, this.httpOptions).pipe( tap(_ => this.log(`deleted hero id=${id}`)), catchError(this.handleError<Hero>('deleteHero')) ); }
-
注意以下要点:
1.deleteHero()调用HttpClient.delete ()。 2.URL是英雄资源URL加上要删除的英雄的id。 3.您不会像发送put()和post()那样发送数据。 4.你仍然发送httpOptions。
-
刷新浏览器并尝试新的删除功能。
Search by name
- 在最后的练习中,您将学习如何将Observable操作符链接在一起,这样就可以最小化类似的HTTP请求的数量,并在经济上消耗网络带宽。
2. 您将向仪表板添加一个英雄搜索功能。当用户在搜索框中键入一个名称时,您将对根据该名称过滤的英雄进行重复的HTTP请求。您的目标是根据需要发出尽可能多的请求。
HeroService.searchHeroes()
-
首先向HeroService添加一个searchHeroes()方法。
<-src/app/hero.service.ts-> /* GET heroes whose name contains search term */ searchHeroes(term: string): Observable<Hero[]> { if (!term.trim()) { // if not search term, return empty hero array. return of([]); } return this.http.get<Hero[]>(`${this.heroesUrl}/?name=${term}`).pipe( tap(x => x.length ? this.log(`found heroes matching "${term}"`) : this.log(`no heroes matching "${term}"`)), catchError(this.handleError<Hero[]>('searchHeroes', [])) ); }
- 如果没有搜索项,该方法立即返回一个空数组。其余部分与getHeroes()非常相似,惟一显著的区别是URL,它包含一个查询字符串和搜索词。
Add search to the Dashboard
-
打开DashboardComponent模板,并将hero search元素添加到标记的底部。
<-src/app/dashboard/dashboard.component.html-> <h3>Top Heroes</h3> <div class="grid grid-pad"> <a *ngFor="let hero of heroes" class="col-1-4" routerLink="/detail/{{hero.id}}"> <div class="module hero"> <h4>{{hero.name}}</h4> </div> </a> </div> <app-hero-search></app-hero-search>
-
这个模板看起来很像HeroesComponent模板中的*ngFor中继器。
-
要使其工作,下一步是添加一个与匹配的选择器组件。
-
Create HeroSearchComponent
-
使用CLI创建一个HeroSearchComponent。
ng generate component hero-search
-
CLI生成三个HeroSearchComponent文件,并将该组件添加到AppModule声明中。
-
将生成的HeroSearchComponent模板替换为和匹配的搜索结果列表,如下所示。
<-src/app/hero-search/hero-search.component.html-> <div id="search-component"> <h4><label for="search-box">Hero Search</label></h4> <input #searchBox id="search-box" (input)="search(searchBox.value)" /> <ul class="search-result"> <li *ngFor="let hero of heroes$ | async" > <a routerLink="/detail/{{hero.id}}"> {{hero.name}} </a> </li> </ul> </div>
- 将私有CSS样式添加到hero-search.component.css中,如下面的最终代码审查中所列。
5. 当用户在搜索框中键入内容时,输入事件绑定使用新的搜索框值调用组件的search()方法。
- 将私有CSS样式添加到hero-search.component.css中,如下面的最终代码审查中所列。
AsyncPipe
-
*ngFor重复英雄对象。注意,*ngFor迭代了一个名为heroes 是一个约定,表示英雄$是一个可观察对象,而不是一个数组。
<-src/app/hero-search/hero-search.component.html-> <li *ngFor="let hero of heroes$ | async" >
- 因为*ngFor不能对一个可见对象做任何事情,所以使用带异步的管道字符(|)。这自动识别了Angular的AsyncPipe并订阅了一个可观察对象,所以你不必在组件类中这样做。
编辑HeroSearchComponent类
-
替换生成的HeroSearchComponent类和元数据,如下所示。
<-src/app/hero-search/hero-search.component.ts-> import { Component, OnInit } from '@angular/core'; import { Observable, Subject } from 'rxjs'; import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators'; import { Hero } from '../hero'; import { HeroService } from '../hero.service'; @Component({ selector: 'app-hero-search', templateUrl: './hero-search.component.html', styleUrls: [ './hero-search.component.css' ] }) export class HeroSearchComponent implements OnInit { heroes$: Observable<Hero[]>; private searchTerms = new Subject<string>(); constructor(private heroService: HeroService) {} // Push a search term into the observable stream. search(term: string): void { this.searchTerms.next(term); } ngOnInit(): void { this.heroes$ = this.searchTerms.pipe( // wait 300ms after each keystroke before considering the term debounceTime(300), // ignore new term if same as previous term distinctUntilChanged(), // switch to new search observable each time the term changes switchMap((term: string) => this.heroService.searchHeroes(term)), ); } }
-
注意hero$的声明是一个可观察值:
<-src/app/hero-search/hero-search.component.ts-> heroes$: Observable<Hero[]>;
-
您将在ngOnInit()中设置它。在此之前,请关注searchTerms的定义。
The searchTerms
RxJS subject
-
searchTerms属性是一个RxJS主题。
<-src/app/hero-search/hero-search.component.ts-> private searchTerms = new Subject<string>(); // Push a search term into the observable stream. search(term: string): void { this.searchTerms.next(term); }
-
主体既是可观察值的来源,又是可观察值本身。你可以订阅一个主题,就像你可以订阅任何可见的主题一样。
-
您还可以像search()方法那样调用它的next(value)方法,将值推入到该Observable中。
-
将事件绑定到文本框的输入事件将调用search()方法。
<-src/app/hero-search/hero-search.component.html-> <input #searchBox id="search-box" (input)="search(searchBox.value)" />
-
每当用户在文本框中输入内容时,绑定就会使用文本框值“search term”调用search()。搜索词变成了一个可观察到的稳定的搜索词流。
链接RxJS运营商
-
在每个用户击键之后直接向searchHeroes()传递一个新的搜索项将会创建过多的HTTP请求,消耗服务器资源并烧毁数据计划。
-
相反,ngOnInit()方法通过一系列RxJS操作符来传递可观察到的searchTerms,这些操作符减少了对searchHeroes()的调用数量,最终返回一个可观察到的及时的英雄搜索结果(每个都是英雄[])。
-
下面进一步查看代码。
<-src/app/hero-search/hero-search.component.ts->
this.heroes$ = this.searchTerms.pipe(
// wait 300ms after each keystroke before considering the term
debounceTime(300),
// ignore new term if same as previous term
distinctUntilChanged(),
// switch to new search observable each time the term changes
switchMap((term: string) => this.heroService.searchHeroes(term)),
);
-
每个操作如下:
1.debounceTime(300)将等待新字符串事件流暂停300毫秒,然后传递最新的字符串。请求的频率永远不会超过300ms。 2.distinctUntilChanged()确保仅在过滤器文本更改时才发送请求。 3.switchMap()为通过debounce()和distinctUntilChanged()的每个搜索词调用搜索服务。它取消和丢弃以前的搜索观察结果,只返回最新的搜索服务观察结果。
1.使用switchMap操作符,每个符合条件的密钥事件都可以触发HttpClient.get()方法调用。即使请求之间有300ms的停顿,您也可能有多个HTTP请求在运行,它们可能不会按照发送的顺序返回。 2.switchMap()保留原始的请求顺序,同时只返回来自最近的HTTP方法调用的被观察对象。先前调用的结果被取消和丢弃。 3.请注意,取消之前的searchHeroes()观察对象实际上并不会中止一个挂起的HTTP请求。不需要的结果在到达应用程序代码之前就会被丢弃。
- 请记住,组件类并不订阅heroes$ observable。这就是模板中的AsyncPipe的工作。
Try it
-
再次运行应用程序。在仪表板中,在搜索框中输入一些文本。如果您输入与任何现有英雄名称匹配的字符,您将看到类似这样的内容。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KTxVaEEr-1585827578122)(E:\桌面文件\Desktop\Desktop\微信截图_20200329231510.png)]
Final code review
<-hero.service.ts->
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { catchError, map, tap } from 'rxjs/operators';
import { Hero } from './hero';
import { MessageService } from './message.service';
@Injectable({ providedIn: 'root' })
export class HeroService {
private heroesUrl = 'api/heroes'; // URL to web api
httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
constructor(
private http: HttpClient,
private messageService: MessageService) { }
/** GET heroes from the server */
getHeroes (): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl)
.pipe(
tap(_ => this.log('fetched heroes')),
catchError(this.handleError<Hero[]>('getHeroes', []))
);
}
/** GET hero by id. Return `undefined` when id not found */
getHeroNo404<Data>(id: number): Observable<Hero> {
const url = `${this.heroesUrl}/?id=${id}`;
return this.http.get<Hero[]>(url)
.pipe(
map(heroes => heroes[0]), // returns a {0|1} element array
tap(h => {
const outcome = h ? `fetched` : `did not find`;
this.log(`${outcome} hero id=${id}`);
}),
catchError(this.handleError<Hero>(`getHero id=${id}`))
);
}
/** GET hero by id. Will 404 if id not found */
getHero(id: number): Observable<Hero> {
const url = `${this.heroesUrl}/${id}`;
return this.http.get<Hero>(url).pipe(
tap(_ => this.log(`fetched hero id=${id}`)),
catchError(this.handleError<Hero>(`getHero id=${id}`))
);
}
/* GET heroes whose name contains search term */
searchHeroes(term: string): Observable<Hero[]> {
if (!term.trim()) {
// if not search term, return empty hero array.
return of([]);
}
return this.http.get<Hero[]>(`${this.heroesUrl}/?name=${term}`).pipe(
tap(x => x.length ?
this.log(`found heroes matching "${term}"`) :
this.log(`no heroes matching "${term}"`)),
catchError(this.handleError<Hero[]>('searchHeroes', []))
);
}
//////// Save methods //////////
/** POST: add a new hero to the server */
addHero (hero: Hero): Observable<Hero> {
return this.http.post<Hero>(this.heroesUrl, hero, this.httpOptions).pipe(
tap((newHero: Hero) => this.log(`added hero w/ id=${newHero.id}`)),
catchError(this.handleError<Hero>('addHero'))
);
}
/** DELETE: delete the hero from the server */
deleteHero (hero: Hero | number): Observable<Hero> {
const id = typeof hero === 'number' ? hero : hero.id;
const url = `${this.heroesUrl}/${id}`;
return this.http.delete<Hero>(url, this.httpOptions).pipe(
tap(_ => this.log(`deleted hero id=${id}`)),
catchError(this.handleError<Hero>('deleteHero'))
);
}
/** PUT: update the hero on the server */
updateHero (hero: Hero): Observable<any> {
return this.http.put(this.heroesUrl, hero, this.httpOptions).pipe(
tap(_ => this.log(`updated hero id=${hero.id}`)),
catchError(this.handleError<any>('updateHero'))
);
}
/**
* Handle Http operation that failed.
* Let the app continue.
* @param operation - name of the operation that failed
* @param result - optional value to return as the observable result
*/
private handleError<T> (operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
// TODO: send the error to remote logging infrastructure
console.error(error); // log to console instead
// TODO: better job of transforming error for user consumption
this.log(`${operation} failed: ${error.message}`);
// Let the app keep running by returning an empty result.
return of(result as T);
};
}
/** Log a HeroService message with the MessageService */
private log(message: string) {
this.messageService.add(`HeroService: ${message}`);
}
}
<-in-memory-data.service.ts->
import { InMemoryDbService } from 'angular-in-memory-web-api';
import { Hero } from './hero';
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class InMemoryDataService implements InMemoryDbService {
createDb() {
const heroes = [
{ id: 11, name: 'Dr Nice' },
{ id: 12, name: 'Narco' },
{ id: 13, name: 'Bombasto' },
{ id: 14, name: 'Celeritas' },
{ id: 15, name: 'Magneta' },
{ id: 16, name: 'RubberMan' },
{ id: 17, name: 'Dynama' },
{ id: 18, name: 'Dr IQ' },
{ id: 19, name: 'Magma' },
{ id: 20, name: 'Tornado' }
];
return {heroes};
}
// Overrides the genId method to ensure that a hero always has an id.
// If the heroes array is empty,
// the method below returns the initial number (11).
// if the heroes array is not empty, the method below returns the highest
// hero id + 1.
genId(heroes: Hero[]): number {
return heroes.length > 0 ? Math.max(...heroes.map(hero => hero.id)) + 1 : 11;
}
}
<-app.module.ts->
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryDataService } from './in-memory-data.service';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
import { HeroesComponent } from './heroes/heroes.component';
import { HeroSearchComponent } from './hero-search/hero-search.component';
import { MessagesComponent } from './messages/messages.component';
@NgModule({
imports: [
BrowserModule,
FormsModule,
AppRoutingModule,
HttpClientModule,
// The HttpClientInMemoryWebApiModule module intercepts HTTP requests
// and returns simulated server responses.
// Remove it when a real server is ready to receive requests.
HttpClientInMemoryWebApiModule.forRoot(
InMemoryDataService, { dataEncapsulation: false }
)
],
declarations: [
AppComponent,
DashboardComponent,
HeroesComponent,
HeroDetailComponent,
MessagesComponent,
HeroSearchComponent
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
HeroesComponent
<-heroes/heroes.component.html->
<h2>My Heroes</h2>
<div>
<label>Hero name:
<input #heroName />
</label>
<!-- (click) passes input value to add() and then clears the input -->
<button (click)="add(heroName.value); heroName.value=''">
add
</button>
</div>
<ul class="heroes">
<li *ngFor="let hero of heroes">
<a routerLink="/detail/{{hero.id}}">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</a>
<button class="delete" title="delete hero"
(click)="delete(hero)">x</button>
</li>
</ul>
<-heroes/heroes.component.ts->
import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
@Component({
selector: 'app-heroes',
templateUrl: './heroes.component.html',
styleUrls: ['./heroes.component.css']
})
export class HeroesComponent implements OnInit {
heroes: Hero[];
constructor(private heroService: HeroService) { }
ngOnInit() {
this.getHeroes();
}
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes);
}
add(name: string): void {
name = name.trim();
if (!name) { return; }
this.heroService.addHero({ name } as Hero)
.subscribe(hero => {
this.heroes.push(hero);
});
}
delete(hero: Hero): void {
this.heroes = this.heroes.filter(h => h !== hero);
this.heroService.deleteHero(hero).subscribe();
}
}
<-heroes/heroes.component.css->
import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
@Component({
selector: 'app-heroes',
templateUrl: './heroes.component.html',
styleUrls: ['./heroes.component.css']
})
export class HeroesComponent implements OnInit {
heroes: Hero[];
constructor(private heroService: HeroService) { }
ngOnInit() {
this.getHeroes();
}
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes);
}
add(name: string): void {
name = name.trim();
if (!name) { return; }
this.heroService.addHero({ name } as Hero)
.subscribe(hero => {
this.heroes.push(hero);
});
}
delete(hero: Hero): void {
this.heroes = this.heroes.filter(h => h !== hero);
this.heroService.deleteHero(hero).subscribe();
}
}
HeroDetailComponent
<-hero-detail/hero-detail.component.html->
<div *ngIf="hero">
<h2>{{hero.name | uppercase}} Details</h2>
<div><span>id: </span>{{hero.id}}</div>
<div>
<label>name:
<input [(ngModel)]="hero.name" placeholder="name"/>
</label>
</div>
<button (click)="goBack()">go back</button>
<button (click)="save()">save</button>
</div>
<-hero-detail/hero-detail.component.ts->
import { Component, OnInit, Input } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Location } from '@angular/common';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
@Component({
selector: 'app-hero-detail',
templateUrl: './hero-detail.component.html',
styleUrls: [ './hero-detail.component.css' ]
})
export class HeroDetailComponent implements OnInit {
@Input() hero: Hero;
constructor(
private route: ActivatedRoute,
private heroService: HeroService,
private location: Location
) {}
ngOnInit(): void {
this.getHero();
}
getHero(): void {
const id = +this.route.snapshot.paramMap.get('id');
this.heroService.getHero(id)
.subscribe(hero => this.hero = hero);
}
goBack(): void {
this.location.back();
}
save(): void {
this.heroService.updateHero(this.hero)
.subscribe(() => this.goBack());
}
}
DashboardComponent
<-src/app/dashboard/dashboard.component.html->
<h3>Top Heroes</h3>
<div class="grid grid-pad">
<a *ngFor="let hero of heroes" class="col-1-4"
routerLink="/detail/{{hero.id}}">
<div class="module hero">
<h4>{{hero.name}}</h4>
</div>
</a>
</div>
<app-hero-search></app-hero-search>
HeroSearchComponent
<-hero-search/hero-search.component.html->
<div id="search-component">
<h4><label for="search-box">Hero Search</label></h4>
<input #searchBox id="search-box" (input)="search(searchBox.value)" />
<ul class="search-result">
<li *ngFor="let hero of heroes$ | async" >
<a routerLink="/detail/{{hero.id}}">
{{hero.name}}
</a>
</li>
</ul>
</div>
<-hero-search/hero-search.component.ts->
import { Component, OnInit } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import {
debounceTime, distinctUntilChanged, switchMap
} from 'rxjs/operators';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
@Component({
selector: 'app-hero-search',
templateUrl: './hero-search.component.html',
styleUrls: [ './hero-search.component.css' ]
})
export class HeroSearchComponent implements OnInit {
heroes$: Observable<Hero[]>;
private searchTerms = new Subject<string>();
constructor(private heroService: HeroService) {}
// Push a search term into the observable stream.
search(term: string): void {
this.searchTerms.next(term);
}
ngOnInit(): void {
this.heroes$ = this.searchTerms.pipe(
// wait 300ms after each keystroke before considering the term
debounceTime(300),
// ignore new term if same as previous term
distinctUntilChanged(),
// switch to new search observable each time the term changes
switchMap((term: string) => this.heroService.searchHeroes(term)),
);
}
}
<-hero-search/hero-search.component.css->
import { Component, OnInit } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import {
debounceTime, distinctUntilChanged, switchMap
} from 'rxjs/operators';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
@Component({
selector: 'app-hero-search',
templateUrl: './hero-search.component.html',
styleUrls: [ './hero-search.component.css' ]
})
export class HeroSearchComponent implements OnInit {
heroes$: Observable<Hero[]>;
private searchTerms = new Subject<string>();
constructor(private heroService: HeroService) {}
// Push a search term into the observable stream.
search(term: string): void {
this.searchTerms.next(term);
}
ngOnInit(): void {
this.heroes$ = this.searchTerms.pipe(
// wait 300ms after each keystroke before considering the term
debounceTime(300),
// ignore new term if same as previous term
distinctUntilChanged(),
// switch to new search observable each time the term changes
switchMap((term: string) => this.heroService.searchHeroes(term)),
);
}
}
Summary
-
你已经走到了人生的终点,你已经取得了很多成就。
1.您添加了在应用程序中使用HTTP所需的依赖项。 2.您重构了HeroService以从web API加载英雄。 3.您扩展了HeroService以支持post()、put()和delete()方法。 4.您更新了组件以允许添加、编辑和删除英雄。 5.您已经配置了内存中的web API。 6.你学会了如何使用可观察的事物。
“英雄之旅”教程到此结束。您已经准备好在基础部分学习更多关于Angular开发的内容,从架构指南开始。
webSocket:
1. 封装浏览器提供的兼容w3c的WebSocket对象。
2. 格式:
webSocket<T>(urlConfigOrSource: string | WebSocketSubjectConfig<T>): WebSocketSubject<T>
Parameters:
- urlConfigOrSource: 将WebSocket端点作为一个url或一个带有配置和附加观察者的对象。
2. Returns: 允许通过WebSocket连接发送和接收消息。
-
Description: 通过WebSocket与服务器通信的主题
-
webSocket是一个工厂函数,它生成一个WebSocketSubject,可以使用它与任意端点进行webSocket连接。webSocket可以接受一个带有webSocket端点url的字符串作为参数,也可以接受一个WebSocketSubjectConfig对象作为参数来提供额外的配置,以及跟踪webSocket连接生命周期的观察者。
-
当WebSocketSubject被订阅时,它会尝试建立一个socker连接,除非已经建立了一个。这意味着许多订阅者将始终监听相同的socket,从而节省了资源。但是,如果WebSocketSubject有两个实例,即使这两个实例提供了相同的url,它们也会尝试建立单独的连接。当WebSocketSubject的使用者取消订阅时,仅当没有更多的订阅者仍在监听时,socket连接才会关闭。如果一段时间后用户再次开始订阅,连接将重新建立。
-
一旦建立了连接,无论何时来自服务器的新消息,WebSocketSubject都会将该消息作为一个值发送到流中。默认情况下,通过JSON.parse解析来自socket的消息。如果您想定制如何处理反序列化(如果有的话),您可以在WebSocketSubject中提供定制的resultSelector函数。当连接关闭时,如果没有任何错误,流将完成。如果在任何点(启动、维护或关闭连接)出现错误,流也会因为抛出的任何WebSocket API而出现错误。
-
由于是Subject, WebSocketSubject允许接收和发送来自服务器的消息。为了与连接的端点通信,请使用next、error和complete方法。next方法向服务器发送一个值,因此请记住,**这个值不会被预先序列化。**因此,在调用带有结果的next方法之前,必须手动调用JSON.stringify的值。还要注意,如果在next(时没有socket连接(例如没有人订阅),这些值将被缓冲,并在连接最终建立时再发送 . complete()关闭socket连接。error()执行相同的操作, 通过状态代码和字符串来通知服务器发生了错误。由于WebSocket API中需要状态代码,所以WebSocketSubject不允许像常规Subject一样将任意值传递给error方法。它需要使用一个对象来调用,该对象具有带有状态代码编号的code属性和带有描述错误细节的字符串的可选reason属性。
-
调用next不会影响WebSocketSubject的订阅者——他们不知道有什么东西被发送到服务器(当然,除非服务器以某种方式响应消息)。另一方面,因为调用complete会触发关闭socket连接的尝试。 如果该连接关闭,没有任何错误,流将完成,从而通知所有订阅者。由于调用错误也会关闭套接字连接,只是使用了不同的服务器状态代码,如果关闭本身没有出现错误,则订阅的Observable将不会出现错误(正如人们可能预期的那样),而是像往常一样完成。在这两种情况下(调用complete或error),如果关闭socket连接的进程导致一些错误,则stream将出错。
-
多路复用
-
WebSocketSubject有一个额外的操作符,在其他主题中找不到。它被称为multiplex,用于模拟打开多个socket连接,而实际上只维护一个。例如,一个应用程序同时具有聊天面板和关于体育新闻的实时通知。因为这是两个不同的函数,所以有两个独立的连接是有意义的。甚至可能有两个单独的带有WebSocket端点的服务,它们运行在单独的机器上,只有GUI将它们组合在一起。为每个功能提供socket连接可能会导致资源过于昂贵。通常使用单个WebSocket端点作为其他服务(在本例中是聊天和体育新闻服务)的网关。即使在客户端应用程序中只有一个连接,也需要能够像操作两个独立的socket那样操作流。这消除了在网关中为给定的服务手动注册和取消注册,并过滤掉感兴趣的消息。这正是多路复用法的用途。
-
方法接受三个参数。前两个函数分别返回订阅和取消订阅消息。这些消息将被发送到服务器,无论结果观察对象的使用者何时订阅和取消订阅。服务器可以使用它们来验证某种消息是否应该开始或停止转发到客户机。对于上述示例应用程序,网关服务器在获得正确标识符的订阅消息后,可以决定是否连接到真正的体育新闻服务,并开始从该服务转发消息。请注意,这两条消息都将作为函数的返回发送,它们在默认情况下使用JSON.stringify,就像通过next推送的消息一样。还要记住,这些消息将在每次订阅和取消订阅时发送。这是潜在的危险,因为一个Observable的消费者可能会取消订阅,而服务器可能会停止发送消息,因为它得到了取消订阅的消息。这需要在服务器上处理,或者使用从“multiplex”返回的可观察对象上的发布。
-
multiplex的最后一个参数是messageFilter函数,它应该返回一个布尔值。它用于过滤服务器发送到仅属于模拟WebSocket流的消息。例如,服务器可能在消息对象上用某种字符串标识符标记这些消息,如果被socket发出的对象上有这样的标识符,messageFilter将返回true。messageFilter中返回false的消息将被简单地跳过,而不会传递到流中。
-
multiplex的返回值是一个可观察的消息,来自模拟的socket连接。注意,这不是WebSocketSubject,因此再次调用next或multiplex将失败。要将值推送到服务器,使用根WebSocketSubject。
-
Examples:
-
监听来自服务器的消息
```
import { webSocket } from "rxjs/webSocket";
const subject = webSocket("ws://localhost:8081");
subject.subscribe(
msg => console.log('message received: ' + msg), //每当有来自服务器的消息时调用。
err => console.log(err), // 调用如果在任何点WebSocket API信号某种错误。
() => console.log('complete') // 当连接关闭时调用(无论什么原因)。
);
```
-
将消息推送到服务器
import { webSocket } from "rxjs/webSocket"; const subject = webSocket('ws://localhost:8081'); subject.subscribe(); // 请注意,至少有一个用户必须订阅所创建的subject—否则“附加”的值只会被缓冲而不会被发送, subject.next({message: 'some message'}); // 一旦建立了连接,就会向服务器发送一条消息。记住,值是用JSON序列化的。stringify默认! subject.complete(); // 关闭连接 subject.error({code: 4000, reason: 'I think our app just broke!'}); // 也关闭连接,但是让服务器知道这个关闭是由一些错误引起的。
-
多路复用WebSocket
import { webSocket } from "rxjs/webSocket"; const subject = webSocket('ws://localhost:8081'); const observableA = subject.multiplex( () => ({subscribe: 'A'}), // 当服务器收到此消息,它将开始发送消息'A'… () => ({unsubscribe: 'A'}), // ...当得到这个的时候,它就会停止。 message => message.type === 'A' // 如果函数返回“true”,则消息将沿流传递。如果函数返回false,则跳过。 ); const observableB = subject.multiplex( // B也是一样。 () => ({subscribe: 'B'}), () => ({unsubscribe: 'B'}), message => message.type === 'B' ); const subA = observableA.subscribe(messageForA => console.log(messageForA)); // 此时WebSocket连接已经建立。服务器获取'{"subscribe": "A"}'消息,并开始向'A'发送消息,我们把它写在这里。 const subB = observableB.subscribe(messageForB => console.log(messageForB)); // 因为我们已经有一个连接,我们只是发送'{"subscribe": "B"}'消息到服务器。它开始向B发送信息,我们把它写在这里。 subB.unsubscribe(); // 消息‘{‘unsubscribe’:‘B’}’被发送到服务器,服务器停止发送‘B’消息。 subA.unsubscribe(); //消息‘{‘unsubscribe’:‘A’}’使服务器停止向‘A’发送消息。由于不再有根主题的订阅者,连接关闭。
multiplex()
-
创建一个可观察对象,当订阅该对象时,将subMsg函数定义的消息发送到socket上的服务器,开始对socket上的数据进行订阅。一旦数据到达,messageFilter参数将用于为结果观察选择适当的数据。当拆卸发生时,由于取消订阅、完成或错误,unsubMsg参数定义的消息将通过WebSocketSubject发送到服务器。
-
格式:
multiplex(subMsg: () => any, unsubMsg: () => any, messageFilter: (value: T) => boolean)
1.subMsg: 生成要发送到服务器的订阅消息的函数。这仍然由WebSocketSubject配置中的序列化器处理。(默认为JSON序列化)
2.unsubMsg: 一个函数,用于生成要在拆卸时发送到服务器的取消订阅消息。这仍然由WebSocketSubject配置中的序列化器处理。
3.messageFilter: 用于为输出流从服务器选择适当消息的谓词。
WebSocketSubjectConfig (websocket另一个参数)
-
WebSocketSubjectConfig是一个普通对象,它允许我们配置webSocket。
interface WebSocketSubjectConfig<T> { url: string protocol?: string | Array<string> resultSelector?: (e: MessageEvent) => T serializer?: (value: T) => WebSocketMessage deserializer?: (e: MessageEvent) => T openObserver?: NextObserver<Event> closeObserver?: NextObserver<CloseEvent> closingObserver?: NextObserver<void> WebSocketCtor?: {...} binaryType?: 'blob' | 'arraybuffer' }
-
Description: 为webSocket提供了灵活性 。 它定义了一组属性,以在socket生命周期的特定时刻提供自定义行为。当连接打开时,我们可以使用openObserver,当连接关闭时,我们可以使用closeObserver,如果我们有兴趣监听来自server: deserializer的数据交换,它允许我们在将数据传递给socket客户端之前定制数据的反序列化策略。默认情况下,反序列化器将应用JSON.parse来自服务器的每个消息。
-
Example: deserializer (反序列化器),这个属性的默认值是JSON.parse。但对于输入数据只有两个选项,要么是文本,要么是二进制数据。我们可以应用自定义反序列化策略,或者直接跳过默认行为。
import { webSocket } from 'rxjs/webSocket'; const wsSubject = webSocket({ url: 'ws://localhost:8081', //应用您选择的任何转换。 deserializer: ({data}) => data }); wsSubject.subscribe(console.log); // 假设我们在服务器上有这个:ws.send(“这是来自服务器的一条消息”) //output // // 这是来自服务器的一条消息
-
但是对于传出的消息 , serializer(序列化器)允许我们tom应用自定义的序列化策略
import { webSocket } from 'rxjs/webSocket'; const wsSubject = webSocket({ url: 'ws://localhost:8081', //应用您选择的任何转换。 serializer: msg => JSON.stringify({channel: "webDevelopment", msg: msg}) }); wsSubject.subscribe(() => subject.next("msg to the server")); // 假设我们在服务器上有这个:ws.send(“这是来自服务器的一条消息”) //output // // {"channel":"webDevelopment","msg":"msg to the server"}
-
closeObserver允许我们在错误发生时设置自定义错误。
import { webSocket } from 'rxjs/webSocket';
const wsSubject = webSocket({
url: 'ws://localhost:8081',
closeObserver: {
next(closeEvent) {
const customError = { code: 6666, reason: "Custom evil reason" }
console.log(`code: ${customError.code}, reason: ${customError.reason}`);
}
}
});
//output
// code: 6666, reason: Custom evil reason
- 假设我们需要在向webSocket发送/接收消息或发送连接成功的通知之前执行某种init任务,这时openObserver是有用的。
import { webSocket } from 'rxjs/webSocket';
const wsSubject = webSocket({
url: 'ws://localhost:8081',
openObserver: {
next: () => {
console.log('connetion ok');
}
},
});
//output
// connetion ok`
Properties:
-
url : string 要连接的服务器url
-
protocol: string | Array 连接所用的协议
-
resultSelector: (e: MessageEvent) => T
-
serializer : (value: T) => WebSocketMessage 在将消息发送到服务器之前,用于从传递的值创建消息的序列化程序。默认为JSON.stringify。
-
deserializer : (e: MessageEvent) => T 用于从服务器到达socket的消息的反序列化器。默认为JSON.parse。
-
openObserver : NextObserver 在底层web套接字上监视打开事件何时发生的观察者。
-
closeObserver : NextObserver 当关闭事件发生在底层webSocket上时,一个观察者将进行监视
这是来自服务器的一条消息”)
//output
//
// {“channel”:“webDevelopment”,“msg”:“msg to the server”} -
closeObserver允许我们在错误发生时设置自定义错误。
import { webSocket } from 'rxjs/webSocket';
const wsSubject = webSocket({
url: 'ws://localhost:8081',
closeObserver: {
next(closeEvent) {
const customError = { code: 6666, reason: "Custom evil reason" }
console.log(`code: ${customError.code}, reason: ${customError.reason}`);
}
}
});
//output
// code: 6666, reason: Custom evil reason
- 假设我们需要在向webSocket发送/接收消息或发送连接成功的通知之前执行某种init任务,这时openObserver是有用的。
import { webSocket } from 'rxjs/webSocket';
const wsSubject = webSocket({
url: 'ws://localhost:8081',
openObserver: {
next: () => {
console.log('connetion ok');
}
},
});
//output
// connetion ok`
Properties:
- url : string 要连接的服务器url
- protocol: string | Array 连接所用的协议
- resultSelector: (e: MessageEvent) => T
- serializer : (value: T) => WebSocketMessage 在将消息发送到服务器之前,用于从传递的值创建消息的序列化程序。默认为JSON.stringify。
- deserializer : (e: MessageEvent) => T 用于从服务器到达socket的消息的反序列化器。默认为JSON.parse。
- openObserver : NextObserver 在底层web套接字上监视打开事件何时发生的观察者。
- closeObserver : NextObserver 当关闭事件发生在底层webSocket上时,一个观察者将进行监视
- binaryType : ‘blob’ | ‘arraybuffer’ 设置底层WebSocket的binaryType属性。