Reputation: 40896
My Angular 4 / TypeScript 2.3 service has a function build()
that errors if a class property isn't initialized. I'm trying to build a safer version -- safeBuild()
-- that will return an Observable that will wait and listen for the property to be initialized before trying to call build()
export class BuildService {
renderer:Renderer2; // must be set for build() below to work
// emits the new Renderer2 when renderer is set
private rendererSet$:BehaviorSubject<Renderer2> = new BehaviorSubject(null);
/** Set renderer, and notify any listener */
setRenderer(renderer:Renderer2){
this.renderer = renderer;
this.rendererSet$.next(renderer);
}
/** Returns a new DOM element. Requires renderer to be set */
build(elemTag:string){
// if renderer is not set, we can't proceed
// why is this error thrown when safeBuild() is called?
if (!this.renderer)
throw new Error('Renderer must be set before build() is run');
return this.renderer.createElement(elemTag);
}
/**
* A safe version of build(). Will wait until renderer is set
* before attempting to call build (Asynchronous)
*/
safeBuild(elemTag:string):Observable<any> {
// inform user that renderer should be set
// this warning is printed to the console as expected
if (!this.renderer)
console.warn('The build will be delayed until setRenderer() is called');
// Listen to rendererSet$, filter out the null output, and call build()
// only once the renderer is set. Why does the error still get thrown?
return Observable.concat(
this.rendererSet$.filter(e=>!!e).take(1),
Observable.of(this.build(elemTag))
)
}
}
I try to build like this (from another service):
this.buildService.safeBuild(elemTag).subscribe(...)
In the console I see:
Warn: The build will be delayed until setRenderer() is called
Error: Renderer must be set before build() is run
I expected the warning, but then nothing to happen until another part of my app calls setRenderer()
. At that point, the code in subscribe()
would run.
Why do I see the error?
Upvotes: 2
Views: 1067
Reputation: 58400
The problem is that this.build(elemTag)
is called when composing the concat
observable - not when the concatenation is performed.
You could solve the problem using defer
:
import 'rxjs/add/observable/defer';
...
return Observable.concat(
this.rendererSet$.filter(e => !!e).take(1),
Observable.defer(() => Observable.of(this.build(elemTag)))
);
Or, as pointed out in the comments, using map
:
return this.rendererSet$
.filter(e => !!e)
.take(1)
.map(() => this.build(elemTag));
Upvotes: 3
Reputation: 8859
It is because, you create an Observable of whatever this.build
function returns. Since, you haven't set the renderer yet, the line below throws an error. Make sure you
call setRenderer
function first
if (!this.renderer)
throw new Error('Renderer must be set before build() is run');
You should be able to solve this by returning an Observable as follows
return Observable.concat(
this.rendererSet$.filter(e=>!!e).take(1),
this.rendererSet$.asObservable().map(() => this.build(elemTag)) // this line will execute when there is a new value set to rendererSet
)
Upvotes: -1