1

I declare a collected observable in my ts file like so:

    getData = (): Observable<IBookingState> => {
        return combineLatest(this.user$, this.files$).pipe(
            map(([user, files]) => ({ user, files })),
            tap(v => {
                this.bookingForm.get('displayName')!.setValue(v.user.displayName)
                this.dataSource = new MatTableDataSource(v.files)
                this.dataSource.connect()
            }),
            filter(v => !!v),
            takeUntil(this.destroyed$),
            tap(v => console.log(v)),
            // startWith({ files: [] as MediaFile[] } as IBookingState),
            finalize(() => {
                this.loadSrv.setLoading(false)
            })
        )
    }

This contains a user object and file[] of input files, that i get from a service as a behavior subject like so:

files$: Observable<MediaFile[]> = this.bookingSrv.files$.pipe(
        takeUntil(this.destroyed$),
        startWith([])
    )

bookingSrv.files$ is just a behavior subject (.asObservable()) with initial value as [], and its being called with | async pipe:

private filesSubj$ = new BehaviorSubject<MediaFile[]>([] as MediaFile[])
files$ = this.filesSubj$.asObservable()
... // in component html:
*ngIf="data$ | async as data"
// data$ contains a mapped observable with the files$

What is wrong: When i go from a random other component into this component, the files$: Observable<[]> is cold, and my component is therefore not rendered.

What should happen: The files observable is being unwrapped with async pipe in HTML, but contains just an empty array (no files).

Other: When i refresh the component, everything is fine and i see the console output as empty array ([]).

How to specify and trigger an observable with empty array as initial value?

EDIT: I've been suggested to use the withlatestfrom operator, so i did that:

        return this.user$.pipe(
            withLatestFrom(this.files$),
            map(([user, files]) => ({ user, files })),
            tap(v => {
                this.dataSource.data = [...v.files];
                this.bookingForm.get('displayName')!.setValue(v.user.displayName)
            }),
            filter(v => !!v),
        )

Still no different. The files$ Obs. is cold, not triggered from routing into the component, but will get hot upon refreshing the page.

blixenkrone
  • 388
  • 2
  • 7
  • 20
  • Are you sure that `this.user$` has a value straight away? `combineLatest` only emits when all inner Observables emitted. It would help if you showed more code from your component that details how it's all connected, i.e. the constructor, ngOnInit, when is `getData` called, where is `files$` and `data$` initialized. What console output are you talking about? The `tap(v => console.log(v))` in your combined Observable? What's with the commented line `startWith({ files: [] as MediaFile[] } as IBookingState)`? What output do you get when it's uncommented? – frido Nov 28 '19 at 14:01
  • where is bookingSrv.files$ created? is the code you posted from an constructor? or from within an method within that service? still this is no minimal example... – Stefan Vitz Nov 28 '19 at 14:16
  • @fridoo im positive. As stated, when i refresh the component route (i.e. not routing into the component), the console output gives me user object (with info) and empty array (files$). @StefanVitz `bookingSrv.files$` is from a singleton service.ts file: ``` private filesSubj$ = new BehaviorSubject([] as MediaFile[]) files$ = this.filesSubj$.asObservable() ``` In the component.ts i set the component observable as: ``` files$: Observable = this.bookingSrv.files$.pipe( takeUntil(this.destroyed$), startWith([]) ) ``` Can i elaborate this more somehow? – blixenkrone Nov 28 '19 at 14:28
  • Well. the initialisation of the subject is correct and should work exactly this way. But without a look upon the missing pieces of the puzzle it is hard to say what's wrong. when it does work the first time you will have to analyze what happens when you close and reenter the component. something has not the correct scope here. Perhaps make a splunker with an minimal example or add some debug messages to your code. btw: if you are developing angular in typescript.... i would encourage you to move that initialising code into an constructor() and structure your code a bit better. – Stefan Vitz Nov 28 '19 at 15:22
  • @StefanVitz thanks, ill create a small reproduction of the code later! – blixenkrone Nov 28 '19 at 15:23
  • btw.... when we suppose the observable works (you could make a debug logging there) perhaps it would be interesting HOW exactly does "data$ contains a mapped observable with the files$" look in your code. – Stefan Vitz Nov 28 '19 at 15:27
  • Like this log: `files: Array []` `user: Object { userId: '...', userName: '', }` – blixenkrone Nov 28 '19 at 15:29
  • i would assume fridoo is right with his comment. i do not really like the code of your get Data method ad all. You can retrieve the latest value of an behaviour subject with .getValue(). So perhaps .combineLatest() does only fire if there are new events on the subject. But when you use behaviour subjects you could use the above mentioned way to retrieve the latest value synchronously. – Stefan Vitz Nov 28 '19 at 15:32
  • Ill look into it - but `getValue()` is an antipattern to RxJS and functional programming. If the value exists, it can be retrieved both ways. The observable being cold, i can guarantee the output from a getValue() print is undefined. – blixenkrone Nov 28 '19 at 15:37
  • Look here: https://stackoverflow.com/a/45227115/5368380 – blixenkrone Nov 28 '19 at 15:38
  • Where do you get `this.user$` from? Please post the code. Also I was asking previously whether you're sure `this.user$` emits a value when you navigate into the component not when you refresh. – frido Nov 28 '19 at 16:55
  • @fridoo i get it from a BE i wrote with JWT auth over http. I'm sure there's a value every time i refresh, as i said, on every reload `user: Object { userId: '...', userName: '',}` with correct info displays. – blixenkrone Nov 29 '19 at 09:18
  • Ok, so there's a user every time you refresh, but when you refresh everything works, so refreshing isn't your problem! You have a problem when you **navigate** from a different component to your component! As I asked before: **do you know `user$` emits a value when you navigate to your component?** I think `user$` is your problem not `files$`! There's really no way someone can help you if you don't post more relevant code as I said in my previous comments. Post the code where you fetch your user data and assign `user$`. – frido Nov 29 '19 at 11:41

1 Answers1

1

I did a bit research after @fridoo comment:

BehaviorSubject to Observable via combineLatest will produce a cold observable. This seams to be the intended behaviour. An suggestion was e.g. using withLatestFrom() instead.

RxJs issue comment on that

Stefan Vitz
  • 141
  • 9
  • This might be the solution. I already looked into withLatestFrom() method, but the combined behavior was better suited in the template. I'll try to implement it instead, cheers! – blixenkrone Nov 29 '19 at 09:19
  • look at my edit - same behavior still, but i appreciate your research. – blixenkrone Nov 29 '19 at 09:27