如何从使用 rjxs 从 API 中获取值的服务 return 映射字符串?
How to return a mapped string from a Service that fetches values from an API with rjxs?
我在 Angular 中有一项名为 MappingService
的服务 7. 该服务从 API:
中获取姓名列表
{
'guid1': 'Item 1',
'guid2': 'Item 2',
'guide': 'Item 3',
}
这些值稍后应该由它们的 ID 映射。当然我可以在 componentn 中订阅 items$
然后做类似的事情:
{{ mapping[id] }}
但我认为这会很好,如果我不必订阅该服务或其列表,那么我就不必一直传递它,因为映射一旦加载就固定了并且是多个组件需要。
所以我想我添加了一个方法 map
到执行此操作的服务:
- 获取一个 id
- returns对应的字符串
喜欢:
{{ mappingSrervice.map(id) }}
但这就是我卡住的地方:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { mergeMap, tap, catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class MappingService {
items$ = new BehaviorSubject<any>(null);
constructor(private http: HttpClient) { }
getAll(): Observable<any> {
return this.items$.pipe(mergeMap(items => {
if (items) {
return of(items);
} else {
return this.http.get<any>(
'http://api'
).pipe(
tap((response: any) => response),
catchError(error => {
return of([]);
})
);
}
}));
}
map(id: number): string {
return this.items$[id] || null;
}
}
如何正确实施 map()
?我应该在构造函数中 运行 getAll()
吗?问题当然还是在getAll()
完成之前items$
不会被填满
我可以让它像我想的那样工作吗? string
是正确的 return 类型吗?
不知这种做法是否可行?还是我需要 return promises 来代替?但这会产生一个新的抽象层次,使它不再那么简单,而不是简单的方法(只需订阅 items$
.
Resolver可能对你有帮助。
In summary, you want to delay rendering the routed component until all necessary data have been fetched.
如果您在解析器的 resolve()
方法中 return Observable
或 Promise
,则在 Observable
完成或Promise
已完成。
在您的组件中,您可以使用 route.snapshot.data.xxx
.
访问解析器提供的数据
这种方法的缺点是它会延迟组件的呈现。
实际上,我认为在这种情况下,简单地订阅 items$
会更好。
先说说缓存。您已经尝试使用 BehaviorSubject
,这是个好主意。与异步操作一样,数据在一段时间内不可用,因为它仍在加载。
这不是问题,我们可以在我们的组件中处理这个问题。
在应用程序加载之前没有真正酷的方法来执行请求(有一些方法,但我不想推荐其中的任何一种)。
说到这里,a BehaviorSubject
并不是最好的选择,因为我们总是需要用一个值来初始化它。
更合适的是ReplaySubject
:它将最后的元素保留在其缓冲区中并将它们重播给每个新订阅者。
虽然按照您的方式做这一切很酷,但我们可以做得更简单:使用 shareReplay()
运算符。
这使得一个冷的 Observable 成为热的,这意味着:它将普通的 HTTP Observable(为每个订阅者发出 HTTP 请求)转换为一个共享的 Observable,它只执行一次任务并与所有订阅者共享它的值。
代码可能如下所示:
export class MappingService {
items$ = this.getAll().pipe(shareReplay(1));
getAll(): Observable<any>() {
return this.http.get('http://api'); // also add catchError here as you already did
}
}
对于第一个订阅者,将调用 getAll()
并执行 HTTP 请求。对于所有后续订阅者,将使用缓存的值。
请注意,您仍然需要订阅 items$
Observable,但在组件中订阅 Observable 不是问题或挑战。
对于映射,我建议使用 RxJS map()
运算符。
你应该提供一个服务方法,它接受一个 ID 和 returns 一个带有结果的 Observable。它可能看起来像这样:
getItem(id: number): Observable<string> {
return this.items$.pipe(
map(items => items[id])
);
}
这样——在任何地方都使用 Observables——你也可以通过异步操作避免竞争条件。您的每个 Observables 都会 return 数据到达后立即。
我在 Angular 中有一项名为 MappingService
的服务 7. 该服务从 API:
{
'guid1': 'Item 1',
'guid2': 'Item 2',
'guide': 'Item 3',
}
这些值稍后应该由它们的 ID 映射。当然我可以在 componentn 中订阅 items$
然后做类似的事情:
{{ mapping[id] }}
但我认为这会很好,如果我不必订阅该服务或其列表,那么我就不必一直传递它,因为映射一旦加载就固定了并且是多个组件需要。
所以我想我添加了一个方法 map
到执行此操作的服务:
- 获取一个 id
- returns对应的字符串
喜欢:
{{ mappingSrervice.map(id) }}
但这就是我卡住的地方:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { mergeMap, tap, catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class MappingService {
items$ = new BehaviorSubject<any>(null);
constructor(private http: HttpClient) { }
getAll(): Observable<any> {
return this.items$.pipe(mergeMap(items => {
if (items) {
return of(items);
} else {
return this.http.get<any>(
'http://api'
).pipe(
tap((response: any) => response),
catchError(error => {
return of([]);
})
);
}
}));
}
map(id: number): string {
return this.items$[id] || null;
}
}
如何正确实施 map()
?我应该在构造函数中 运行 getAll()
吗?问题当然还是在getAll()
完成之前items$
不会被填满
我可以让它像我想的那样工作吗? string
是正确的 return 类型吗?
不知这种做法是否可行?还是我需要 return promises 来代替?但这会产生一个新的抽象层次,使它不再那么简单,而不是简单的方法(只需订阅 items$
.
Resolver可能对你有帮助。
In summary, you want to delay rendering the routed component until all necessary data have been fetched.
如果您在解析器的 resolve()
方法中 return Observable
或 Promise
,则在 Observable
完成或Promise
已完成。
在您的组件中,您可以使用 route.snapshot.data.xxx
.
这种方法的缺点是它会延迟组件的呈现。
实际上,我认为在这种情况下,简单地订阅 items$
会更好。
先说说缓存。您已经尝试使用 BehaviorSubject
,这是个好主意。与异步操作一样,数据在一段时间内不可用,因为它仍在加载。
这不是问题,我们可以在我们的组件中处理这个问题。
在应用程序加载之前没有真正酷的方法来执行请求(有一些方法,但我不想推荐其中的任何一种)。
说到这里,a BehaviorSubject
并不是最好的选择,因为我们总是需要用一个值来初始化它。
更合适的是ReplaySubject
:它将最后的元素保留在其缓冲区中并将它们重播给每个新订阅者。
虽然按照您的方式做这一切很酷,但我们可以做得更简单:使用 shareReplay()
运算符。
这使得一个冷的 Observable 成为热的,这意味着:它将普通的 HTTP Observable(为每个订阅者发出 HTTP 请求)转换为一个共享的 Observable,它只执行一次任务并与所有订阅者共享它的值。
代码可能如下所示:
export class MappingService {
items$ = this.getAll().pipe(shareReplay(1));
getAll(): Observable<any>() {
return this.http.get('http://api'); // also add catchError here as you already did
}
}
对于第一个订阅者,将调用 getAll()
并执行 HTTP 请求。对于所有后续订阅者,将使用缓存的值。
请注意,您仍然需要订阅 items$
Observable,但在组件中订阅 Observable 不是问题或挑战。
对于映射,我建议使用 RxJS map()
运算符。
你应该提供一个服务方法,它接受一个 ID 和 returns 一个带有结果的 Observable。它可能看起来像这样:
getItem(id: number): Observable<string> {
return this.items$.pipe(
map(items => items[id])
);
}
这样——在任何地方都使用 Observables——你也可以通过异步操作避免竞争条件。您的每个 Observables 都会 return 数据到达后立即。