Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Weird behavior of BehaviorSubject, async pipe, and *ngIf combination: ngOnChanges receives the change that theoretically cannot be possible

I have two components – Parent component which fetches the data from the server, and child component which is displaying fetched entries as a list – but only if the list is not null or empty.

My problem is (short): Even if there is *ngIf checking that the list has some values, emission is done and in child component I get first emission with null items: ![First emission] (https://i.stack.imgur.com/OHRB5.png).

Second emission is though okay… ![Secod emission] (https://i.stack.imgur.com/vK8Op.png).

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

Does somebody have an idea?


The parent component also contains search field where you can search entries. Here is the code:

<lookup-area [debounceTime]="100"
             (searchValue)="searchChanged($event)"></lookup-area>

<app-virtual-list *ngIf="(channels$ | async)?.length > 0"
                  [items]="channels$ | async"
                  [itemHeightPx]="32"
                  [maxContainerHeightPx]="190 - ((channels$ | async)?.length ? 29 : 0)"
                  [trackByFn]="trackByFn">
    <ng-template let-item>Some template</ng-template>
</app-virtual-list>
@Component({
             selector: 'app-filter',
             templateUrl: './filter.component.html',
             changeDetection: ChangeDetectionStrategy.OnPush,
             encapsulation: ViewEncapsulation.None
           })
export class FilterComponent implements OnInit, OnDestroy {

    public search$ = new BehaviorSubject('');

    constructor(private service: YoutubeSearchService) {
    }

    public ngOnInit(): void {
        this.channels$ = combineLatest([this.service.getYoutubeChannels(), this.search$]).pipe(
            map(([channels, search]) => search ? channels.filter(channel => (channel.name || channel.id).startsWith(search)) : channels),
            map(channels => channels?.sort((a, b) => (a.name || a.id).localeCompare((b.name || b.id), undefined, { sensitivity: 'base' })))
        );
    }

    public ngOnDestroy(): void {
        this.search$.complete();
    }

    public searchChanged(search: string): void {
        this.search$.next(search);
    }

    public trackByFn(index: number, item: YoutubeChannel): any {
        return item.id;
    }

}

And the child component contains the logic for displaying. Here is the code:

@Component({
             selector: 'app-virtual-list',
             templateUrl: './virtual-list.html',
             styleUrls: ['./virtual-list.less'],
             changeDetection: ChangeDetectionStrategy.OnPush,
             encapsulation: ViewEncapsulation.None
           })
export class VirtualList<T> implements OnChanges {

  // Mandatory
  @Input() public items: T[];
  @Input() public itemHeightPx: number;

  // Optional
  @Input() public maxContainerHeightPx: number | undefined; // If undefined, scroll container's height is 100%

  
  public ngOnChanges(changes: SimpleChanges): void {
      const { items, itemHeightPx, maxContainerHeightPx } = changes;

      if (this.maxContainerHeightPx && (items || itemHeightPx || maxContainerHeightPx)) {
         this.setContainerScrollHeight();
      }
  }

}

I tried:

  1. wrapping everything in <ng-container *ngIf="(channels$ | async)?.length > 0">, but no success
  2. wrapping in another div, still did not work

I have no clue what else to try because it seems that *ngIf does not work which is really weird.

I would expect that the first change comes with the items inside since that is what *ngIf is telling.

>Solution :

Not sure I understand your issue, but I already see a bad thing in your code, so try to resolve it and see if the issue persists.

You don’t need to create 3 subscriptions, a single one is enough.

<ng-container *ngIf="channels$ | async as channels">
  <app-virtual-list 
    *ngIf="channels?.length"
    [items]="channels"
    [itemHeightPx]="32"
    [maxContainerHeightPx]="190 - (channels?.length ? 29 : 0)"
    [trackByFn]="trackByFn"
  >
    <ng-template let-item>Some template</ng-template>
  </app-virtual-list>
</ng-container>
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading