Sergej Bjakow
Sergej Bjakow

Reputation: 83

Passing Observables from Child components to ancestors or distant components

I have some serious trouble handling observables in angular (sap composable storefront), that I want to acess from another component. I never seem to get them up to date.

CURRENT STATE I have cart page with Cart-Items and each Item is fetching it's own Availability Status by some special http-request. While Fetching the Data, a Loading icon Shows.

So Each CartItemComponent-Instance has its "availability$"-Observable (and I want it seperated from the rest of the item object, since I want all other item Data displayed while Availability is beging requested meanwhile. The Availability-Status is also refetched once e.g. the quantity counter is changed.

THE REQUIREMENT Outside the "CartListComponent" (which holds the Array of CartItemComponents) there is CheckoutButtonComponent, that needs to get an ulttimate summary of the entire CartItems availabilities. If one of the Cart Items is not available, then the checkoutbutton must be disabled.

FAILED APPROACHES SO FAR I tried the easy way and simply reiterated all cartItems in the CheckoutButtonComponent and and got all the availabilities, but this means sending each request twice. I would rather want to make use of the already received responses from the CartListComponent, but this is where things get complicated.

First I tried handling this with BehaviorSubject, but adding an array of Observables to a Subject looked very unusual and I had trouble accessing the data.

Then I tried applying a @ViewChildren-Marker on the CartItems, and called it within the "onChanges"-Lifecycle Hook of "CartListComponent", but the "availability$"-Observable is never up to date at that point - it's always null since it is still being fetched by the time I try to work with it.

export class CartItemListComponent{
    cartItems: Item[];
   @ViewChildren('entryList') entryList: QueryList<CartItemComponent>;

   ngOnChanges(changes: SimpleChanges): void {
        if (changes.items && this.entryList) {
            const availabilities$: Observable<string>[] = this.entryList.map(entry => entry.availability$);
            //Note that "availability$ is always null at this point
        }
    }
}


<ng-container *ngFor="let item of cartItems;">
    <cart-item #entryList [item]="item">
    </cart-item>
</ng-container>


export class CartItemComponent{
  @Input() item: OrderEntry;

  availability$: Observable<string> = availabilityService.get(this.item);

}

Currently I'm not even sure if this approach is even going to work, because ultimately I need that data within a distant component, that is not a child component of CartListComponent, but I don't want to retrigger the same requests. How would you solve this dilemma?

Upvotes: 1

Views: 61

Answers (1)

Naren Murali
Naren Murali

Reputation: 57986

You can use ngAfterViewInit and listen for observable emits, only in this hook, viewchild will get processed and available if static is false (default).

Then use combineLatest to trigger each time an emission happens, then use debounceTime to reduce the number of times the button is calculated. Only taking the latest value using debounce.

export class CartItemListComponent{
   private sub: Subscription = new Subscription();
   buttonDisabled = false;
   cartItems: Item[];
   @ViewChildren('entryList') entryList: QueryList<CartItemComponent>;

   ngAfterViewInit(): void {
        if (changes.items && this.entryList) {
            const availabilities$: Observable<string>[] = this.entryList.map(entry => entry.availability$);
            //Note that "availability$ is always null at this point
            this.sub.add(
            combineLatest(availabilities$).pipe(
                debounceTime(250),
                tap((arr: Array<string>) => {
                  this.buttonDisabled = arr.every(x => x); // check if all are true
                }),
            ).subscribe()
            );
        }
    }

   ngOnDestroy(): void {
        this.sub.unsubscribe();
   }
}

Upvotes: 2

Related Questions