Reputation: 433
I have a function that returns an Observable as result of a http request. I want to create a store in the memory for these functions but when I call one I want to emit the new result of this for the original context of that function.
In my case I have a theme service that has a store, a relaod contents function and register new loader function.
Theme service class
....
contentLoaderRegistry = [];
public reloadRegistratedContents(){
this.contentLoaderRegistry.forEach((loader) => {
loader();
});
}
public registerThemeDependentContentLoader(loader:()=>Observable<SafeHtml>):Observable<SafeHtml>{
const loaderWrapper:() => Observable<SafeHtml> = ():Observable<SafeHtml> => {
return Observable.create((observer) => {
try{
observer.next( ...??? loader());
}catch(err){
observer.error(err);
}
return () => {
//observer.complete();
};
});
};
this.contentLoaderRegistry.push(loaderWrapper);
return loaderWrapper();
}
....
The loader parameter is implementing the http call of an svg image. When theme is changed reloadRegistratedContents function will be called and here the view should be patched with the new svg contents.
I also have a pipe that should do the magic:
@Pipe({
name: 'themeDependentContent'
})
export class ThemeDependentContentPipe implements PipeTransform {
constructor(
private _theme:ThemeService,
private _http:HttpClient,
private _sanitizer:DomSanitizer
){}
transform(url:string):Observable<SafeHtml> {
let themeDependentContentLoader:()=>Observable<SafeHtml> = () => {
return this._http.get(url, {headers: new HttpHeaders().set('Content-Type', 'image/svg+xml'), responseType: 'text'}).pipe(map<string, SafeStyle>((svg:string) => {
return this._sanitizer.bypassSecurityTrustHtml(svg);
}));
}
return this._theme.registerThemeDependentContentLoader(themeDependentContentLoader);
}
}
If the result of registerThemeDependentContentLoader function could emit(next) the svg at the beginning and every time when reloadRegistratedContents is called it would be nice. On the other hand this code returns something like: Observable<Observable<SafeHtml>>
insted of Observable<SafeHtml>
. SO probably I should wrap the result of loader parameter function's observable but I dont know how to.
Could someone who have more experience help me out in this? Thank you for your time and answers.
I could make it work with Subject but I guess it is not the best practice here:
public registerThemeDependentContentLoader(loader:()=>Observable<SafeHtml>):Observable<SafeHtml>{
let observer = new Subject<SafeHtml>();
const loaderWrapper = () => {
loader().subscribe((svg) => {
observer.next(svg);
}, (err) => {
observer.error(err);
});
}
this.contentLoaderRegistry.push(loaderWrapper);
return observer;
}
Upvotes: 1
Views: 822
Reputation: 11345
You can just pass in observer in your loader().subscribe for it to work
try{
loader().subscribe(observer)
}catch(err){
observer.error(err);
}
Upvotes: 1
Reputation: 60518
I don't understand your use case enough to provide the exact code you need, but to generally answer the question of how to create a new Observable from the result of another Observable, I use code like this:
selectedProductSuppliers$ = this.selectedProduct$
.pipe(
switchMap(selectedProduct =>
forkJoin(selectedProduct.supplierIds.map(supplierId => this.http.get<Supplier>(`${this.suppliersUrl}/${supplierId}`)))
)
);
Each time a new product is selected, the product is emitted into the this.selectedProduct$
stream.
This uses forkJoin
to create a new Observable from that selected product's list of suppliers, get each one via http.get
and join the result.
In general, whenever you need to create an Observable within another Observable, you want to use a higher-order mapping operator (such as switchMap, mergeMap, or concatMap). The higher-order mapping operators handle the subscriptions to the inner Observables.
Upvotes: 2