typescript - Angular 4 ve RxJS 5: Observable.concat() beklenmedik şekilde davranıyor
Benim Angular 4/TypeScript 2.3 servisimin, bir sınıf özelliği başlatılmamışsa hata veren bir build() adlı fonksiyonu var. Daha güvenli bir versiyonunu oluşturmaya çalışıyorum -- safeBuild() -- bu fonksiyon, build() çağırmayı denemeden önce özelliğin başlatılmasını bekleyecek ve dinleyecek bir Observable döndürecek.
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))
)
}
}
Başka bir hizmetten olduğu gibi böyle inşa etmeye çalışıyorum:
this.buildService.safeBuild(elemTag).subscribe(...)
Konsolda şunu görüyorum:
Uyarı: Derleme, setRenderer() çağrılana kadar ertelenecek
Hata: build() çalıştırılmadan önce Renderer ayarlanmalıdır
Uyarıyı bekliyordum, ama sonra uygulamamın başka bir bölümü setRenderer() çağırana kadar hiçbir şey olmadı. O noktada, subscribe() içindeki kod çalışırdı.
Neden hatayı görüyorum?
Cevap #1
Sorun şu ki, this.build(elemTag), birleştirme (concat) observable'ı oluşturulurken çağrılır - birleştirme gerçekleştirildiğinde değil.
Sorunu defer kullanarak çözebilirsiniz:
import 'rxjs/add/observable/defer';
...
return Observable.concat(
this.rendererSet$.filter(e => !!e).take(1),
Observable.defer(() => Observable.of(this.build(elemTag)))
);
Veya, yorumlarda belirtildiği gibi, map kullanarak:
return this.rendererSet$
.filter(e => !!e)
.take(1)
.map(() => this.build(elemTag));
Cevap #2
Bunun nedeni, this.build fonksiyonunun ne döndürdüğünden bağımsız olarak bir Observable oluşturmanızdır. Henüz renderleyiciyi ayarlamadığınız için, aşağıdaki satır bir hata fırlatır. Önce setRenderer fonksiyonunu çağırdığınızdan emin olun.
if (!this.renderer)
throw new Error('Renderer must be set before build() is run');
Bunu, aşağıdaki gibi bir Observable döndürerek çözebilmeniz gerekir
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
)
