cyberguy
cyberguy

Reputation: 253

How to use material table with async pipe and change detection? (Not working properly)

I've spent about 3 whole days trying to figure this out.

Behavior expected: Material Table always renders with most current data from backend. When a new item is added to table from a separate component, a router redirect to the page shows updated data.

Actual behavior: Upon first navigation to page, table is empty. Table is rendered, column headers rendered, but no rows populated. Navigating away then back populates the table properly. When adding a new item to the table, a service sends the item to the backend and then makes a call for all items. The service then updates the store (another service). On redirect to the table component the table seems to flash the old data quickly and then again appears blank. Once I navigate away and back the table is properly loaded.

I am using the async pipe below with services. This doesn't seem right to me... further, ngOnChanges logs nothing.

appStore.service.ts

private _pets: ReplaySubject<Pet[]> = new ReplaySubject(1);
public readonly pets: Observable<Pet[]> = this._pets.asObservable();
getPets(): Observable<Pet[]> {
    return this.pets;
}

appDispatch.service.ts

public getPets(): Observable<Pet[]> {
    return this.appStore.getPets();
}

private setPets(pets: Pet[]) {
    this.appStore.setPets(pets);
}

petTableComponent.component.ts

...
changeDetection: ChangeDetectionStrategy.OnPush

constructor(private appDispatch: AppDispatchService,
              private router: Router) {
}

ngOnChanges(changes: SimpleChanges): void {
    console.log(changes.value.currentValue);
}

petTableComponent.component.html

<table
  mat-table
  [dataSource]="appDispatch.getPets() | async" multiTemplateDataRows
  class="mat-elevation-z8">

    <ng-container matColumnDef="name">
      <th mat-header-cell *matHeaderCellDef scope="col" [attr.role]="null"> <h4>Name</h4></th>
      <td mat-cell *matCellDef="let element" scope="row" [attr.role]="null">
        <p>{{element.pet.name}}</p>
      </td>
    </ng-container>
    ...

EDIT I tried changing the following per request, exact same outcome as before.

//changeDetection: ChangeDetectionStrategy.OnPush

pets-table.component.ts

constructor(private appDispatch: AppDispatchService,
              private router: Router,
              private cdr: ChangeDetectorRef) {
  }

  ngOnChanges(changes: SimpleChanges): void {
    console.log(changes.value.currentValue());
  }

  ngOnInit(): void {
    this.subscription = this.appDispatch.getReleaseItems()
      .subscribe(value => {
        this.data = value;
        this.cdr.detectChanges();
      });
  }

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

And I changed the datasource to [dataSource]="data"

Not sure...

Upvotes: 5

Views: 10546

Answers (3)

trudesign
trudesign

Reputation: 117

You are using a ReplaySubject for your data, which will 'replay' all the nexted data but seemingly not the initialized data new ReplaySubject(1). If you want to initialize the subject with the data, and only pass the last best set to the subscribers (instead of multiple results like a replay subject would put out), use a BehaviorSubject

https://www.learnrxjs.io/learn-rxjs/subjects/replaysubject

Upvotes: 0

MoxxiManagarm
MoxxiManagarm

Reputation: 9124

Try to store the observable in the component

pets$: Observable<Pet[]> = this.appDispatch.getPets();

<table
  mat-table
  [dataSource]="pets$ | async" multiTemplateDataRows
  class="mat-elevation-z8">

Upvotes: -1

Chirag Patel
Chirag Patel

Reputation: 374

this is because you have set ChangeDetectionStrategy.OnPush this means your component thinks you only relying on your @input attributes so change detection only takes place when those values are changed or a DOM object event is raised from the Component or its children.

have a look at this article that has a really good explanation with examples: https://netbasal.com/a-comprehensive-guide-to-angular-onpush-change-detection-strategy-5bac493074a4

you could also run change detection manually:

constructor(private cdr: ChangeDetectorRef, private appDispatch: AppDispatchService) {
  ......
  this.appDispatch.Pets.subscribe(() => {
    this.cdr.detectChanges();  
  })
  

}

Upvotes: 0

Related Questions