3

EDIT 30-07-2018 01:57 CET: Still looking for a solution to the problem below:

I have the following code:

getBoolean(dataRef1, dataRef2) {
  const myFirstObservable = this.af.object(dataRef1).valueChanges();
  const mySecondObservable = this.af.object(dataRef2).valueChanges();

  return combineLatest(myFirstObservable, mySecondObservable).pipe(map(([data1, data2]) => data1 && data2)))
}

Basically what the code does is: it combines the results of the two observables and then checks each whether it has returned any value. If both observables have a value, return true else return false.

So far so good, everything is working as expected. I then call the method from my View, provide dynamic node references for the observables, and use the boolean to add a conditional element:

<div *ngIf="getBoolean('dataN', 'dataK') | async"> My lovely div content goes here</div>

However, my div does not display on my view at all.

Can someone tell me where I am wrong and help me achieve my desired result?

4 Answers4

1

getBoolean() is returning nothing. The return inside the subscribe callback is returning for the subscribe. A way to implement this logic.

getBoolean(dataRef1, dataRef2): Observable<boolean> {
  const myFirstObservable = this.af.object(dataRef1).valueChanges();
  const mySecondObservable = this.af.object(dataRef2).valueChanges();
  return combineLatest(myFirstObservable, mySecondObservable, (data1, data2) => data1 && data2)
}

And the magic: you subscribe in the HTML with async pipe link, which will subscribe for you:

<div *ngIf="getBoolean('dataN', 'dataK') | async"> My lovely div content goes here</div>
Pedro Arantes
  • 5,113
  • 5
  • 25
  • 60
  • Silly me. Indeed, this solved the issue! However, the example I provided was oversimplified, since now in my case the view provides a dynamic reference for `data1` and `data2` for the observables. And hence, I cannot create only one boolean since I am gonna need roughly 20 of them. So my question: how can I directly call the method from my view and expect a boolean value to be returned? I want to avoid storing the values in boolean global variables. Is this possible? –  Jul 29 '18 at 22:25
  • @JohnDoe, do you mean you have `const myNObservable = this.af.object(`dataN`).valueChanges();` and want to analyse anyone of them? – Pedro Arantes Jul 29 '18 at 22:33
  • yes. So my observables' nodes will change. So I cannot store the result in a single boolean and put it in my view. Is there a way I can return the value in the method and use it directly as a condition in the view? E.g. `*ngIf="getBoolean('data32', 'data33')"` –  Jul 29 '18 at 22:36
  • Got it. I'll change the answer. Could you change the question too? Maybe someone can be helped with this thread – Pedro Arantes Jul 29 '18 at 22:39
  • What about now? Can it help? – Pedro Arantes Jul 29 '18 at 22:50
  • When using it the exact way you have provided, I get the following error: `Type 'Observable<{}>' is not assignable to type 'Observable'. Type '{}' is not assignable to type 'boolean'.` I added my part of the code (with the local `bool1`, `bool2` and the return of their **&&**) and then added the async pipe to the html. However, the condition does not seem to apply. My element is not displayed on the view. –  Jul 29 '18 at 22:58
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/176979/discussion-between-pedro-arantes-and-john-doe). – Pedro Arantes Jul 29 '18 at 23:01
  • 1
    Pedro and I tried to implement the wanted result through various methods but still no success for now. Maybe someone will have an idea of how to achieve the desired result. –  Jul 29 '18 at 23:52
1

Most likely the problem is that you're calling a getBoolean('dataN', 'dataK') method in your template. On every change detection Angular invokes getBoolean method that returns a new Observable. The old one is unsubscribed by async and only the new one is active.

At the same time combineLatest operator requires all source Observables to emit at least once which is not very likely to happen because async subscribes/unsubscribes on ever change detection cycle that can happen eve multiple times a second.

So I'd recommend you to store the Observable in a property:

public obs$ = combineLatest(myFirstObservable, mySecondObservable, (data1, data2) => data1 && data2);

Then only use obs$ | async in templates.

Eventually, you can add some logic inside getBoolean that checks whether obs$ exists and if it does you'll just return it as is.

martin
  • 93,354
  • 25
  • 191
  • 226
  • I think this might work however, how can I do this for say, 20 observables? Isn't it too much to create 10 variables for each pair of observables in order to hold their combineLatest? How can I simplify this? –  Aug 07 '18 at 22:18
  • @JohnDoe Just add all Observables to an array and use `combineLatest(array)`, see https://github.com/ReactiveX/rxjs/blob/master/src/internal/observable/combineLatest.ts#L39 – martin Aug 08 '18 at 08:16
  • I stored all observables in an Array and called combineLatest on it. Worked like a charm. Thank you. –  Aug 19 '18 at 11:38
0

First of all, you application stuck on infinite loop because you bind your view with a returned function which does not return nothing. And besides, I dont recommend to bind function from view, but to a variable.
You might try to add a BehaviorSubject which binds the view and the logic to the right value.
I've made a sandbox to just check if it works, and it did.

TL;DR - I just construct two BehaviorSubject to manipulate the value of the third one, which in your case is the desired boolean value. I bind the div to the third BSubject through combineLatest and it works. The point is to keep always a valid variable to bind, which get updated via the subscription of the combineLatest, and I update its value through the BehaviorSubject


k1 = new BehaviorSubject<boolean>(false);
k2 = new BehaviorSubject<boolean>(false);
k3 = new BehaviorSubject<boolean>(false);
k3$: Observable<boolean>;

  constructor() {
    this.k3$ = this.k3.asObservable();
  }

  ngOnInit() {
    combineLatest(this.k1.asObservable(), this.k2.asObservable(), (d1, d2) => {
      return d1 && d2;
    }).subscribe(b => {
      console.log(b);
      this.k3.next(b);
    });
  }

  toggle1 = () => {
    this.k1.next(true);
  };
  toggle2 = () => {
    this.k2.next(true);
  };

and in HTML:

<div *ngIf='k3$ | async'>
Example
</div>

It is works, try to look at the example and project it to your code. Check the CodeSandbox Example


Good luck !

dAxx_
  • 2,210
  • 1
  • 18
  • 23
0

I was looking for an alternative to forkJoin and I found combineLatest but it was calling multiple times my observables. And I found another one zip which meets my needs.

  1. It waits for all argument observables to complete and then emit an array of last emitted values
forkJoin([observable1$, observable2$])
  .pipe(
    map((results) => {
      console.log('Result observable1', results[0]);
      console.log('Result observable2', results[1]);
    })
  )
  .subscribe();
  1. It returns a value every time an observable completes.
combineLatest([observable1$, observable2$])
  .pipe(
    map((results) => {
      console.log('Result observable1', results[0]);
      console.log('Result observable2', results[1]);
    })
  )
  .subscribe();

Called 2 times in this example. Don't forget to unsubscribe from it.

  1. Same as forkJoin but it emits an array of values with same emission index
zip([observable1$, observable2$])
  .pipe(
    map((results) => {
      console.log('Result observable1', results[0]);
      console.log('Result observable2', results[1]);
    })
  )
  .subscribe();
Jaraxxus
  • 116
  • 1
  • 5