javascriptangulartypescriptangular-event-emitter

Can't get Angular 2 table component to pick up on changes to array in shared service class


I want to display an array of data fetched by a service in a table component after the service is triggered by a button elsewhere. I've tried to do it using ngOnChanges() but that doesn't appear to notice any changes to the array in the service class after init. I want the flow to be something like this:

PixSearchComponent button click (code not shown) --> PixSearchService data fetch triggered (got this part) --> updated array displayed in PixTableComponent

I did some logging/debugging and the service method is definitely being called. I know it's not something wrong with the table's field binding because I've tested that. Can anyone tell me how to in a sense push the updated array from the service to the table component so that the changes will be reflected in the table? Thanks.

pix-search.service.ts

import {
  HttpClient,
  HttpErrorResponse,
  HttpHeaders,
} from '@angular/common/http';
import { EventEmitter, Inject, Injectable, Optional } from '@angular/core';
import { catchError, map, tap, throwError } from 'rxjs';
import { IPix } from './model/IPix';

@Injectable({
  providedIn: 'root',
})
export class PixSearchService {

  constructor(private http: HttpClient) {}

  pixUpdated: EventEmitter<IPix[]> = new EventEmitter();

  setPixData(pixData: IPix[]) {
    this.pixData = pixData;
    return this.pixUpdated.emit(this.pixData);
  }

  getPixData()  {
    return this.pixData;
  }

  pixData!: IPix[];

  pixUrl: string = 'https://example.ckp-dev.example.com/example';

  retrievePixData(): void {
    const headers = new HttpHeaders({
      'x-api-key':
        'ewogICAgImFwaUtleSIgOiAiMTIzIiwKICAgICJ1c2VySWQiID3649807253098ESSBEZXZlbG9wZXIiCn0=',
    });

    this.setPixData(this.http
      .get<any>(this.pixUrl, {
        headers
      })
      .pipe(
        tap((data) => console.log('All:', JSON.stringify(data))),
        map((data: any) => data.results),
        catchError(this.handleError)
      ) as unknown as IPix[]);
  }

  handleError(err: HttpErrorResponse) {
    let errorMessage = '';
    if (err.error instanceof ErrorEvent) {
      errorMessage = `An error occurred: ${err.error.message}`;
    } else {
      errorMessage = `Server returned code:: ${err.status}, error message is: ${err.message}`;
    }
    console.error(errorMessage);
    return throwError(() => errorMessage);
  }
}

pix-table.component.ts

import {
  Component,
  Inject,
  Input,
  OnChanges,
  OnDestroy,
  OnInit,
  Optional,
} from '@angular/core';
import type { TableSize } from '@dauntless/ui-kds-angular/table';
import type { TableStickyType } from '@dauntless/ui-kds-angular/table';
import type { TableScrollType } from '@dauntless/ui-kds-angular/table';
import { CardElevation } from '@dauntless/ui-kds-angular/types';
import { PixSearchService } from '../pix-search.service';
import { Observable, Subscription } from 'rxjs';
import { IPix } from '../model/IPix';
import { IContract } from '../model/IContract';
import { IAudit } from '../model/IAudit';
import { ICapitation } from '../model/ICapitation';
import { IChangeRequest } from '../model/IChangeRequest';
import { IHnetAudit } from '../model/IHnetAudit';
import { IProduct } from '../model/IProduct';
import { IProvider } from '../model/IProvider';

@Component({
  selector: 'pix-table-component',
  templateUrl: 'pix-table.component.html',
  styleUrls: ['pix-table.component.css'],
  providers: [PixSearchService]
})
export class PixTableComponent implements IPix {
  constructor(private pixSearchService: PixSearchService) {
    this.pixSearchService.pixUpdated.subscribe((pix) => {
      this.pixRecords = this.pixSearchService.getPixData() as unknown as IPix[];
    });
  }

  columns = [
    'ID',
    'Network',
    'LOB',
    'HP Code',
    'Atypical',
    'TIN',
    'GNPI',
    'Org',
    'Business Unit Code',
    'National Contract',
    'National ContractType',
    'Contract Type',
    'Super Group',
    'Contract ID',
    'Amendment ID',
    'Contract Effective Date',
    'Contract Termination Date',
  ];

  rows: any;
  tableSize: TableSize = 'small';
  showHover = true;
  sticky: TableStickyType = 'horizontal';
  scrollType: TableScrollType = 'both';
  label = 'Payment Index Management';
  disabled = 'disabled';
  error = 'error';
  maxlength = 'maxlength';
  showCounter = false;
  elevation: CardElevation = 'medium';

  legacyConfigTrackerId!: number;
  contract!: IContract;
  audit!: IAudit;
  capitation!: ICapitation;
  changeRequest!: IChangeRequest;
  claimType!: string;
  deleted!: string;
  hnetAudit!: IHnetAudit;
  id!: string;
  noPayClassReason!: string;
  payClass!: string;
  product!: IProduct;
  provider!: IProvider;
  rateEscalator!: string;
  status!: string;
  selected: boolean = false;

  pixRecords: IPix[] = [];
  errorMessage: string = '';
}


Solution

  • I ended up using a Subject() and subscription to alert the table component to the change in the service class' IPix array:

    pix-search.component.ts

     constructor(private _pixSearchService: PixSearchService) {}
    
     //trigger update of the IPix array in the service class
     buttonClicked(button: any) {
       this._pixSearchService.getPixData();
      }
    

    pix-table.component.ts

    pixRecords$!: Observable<IPix[]>;
    
      constructor(private _pixSearchService: PixSearchService) {
        //subscribe to the service class' Subject()
        this._pixSearchService.invokeEvent.subscribe((pixData) => {
          console.log(pixData);
          this.pixRecords$ = pixData;
          console.log(JSON.stringify(pixData));
        });
      }
    
    

    pix-search.service.ts

     public invokeEvent: Subject<any> = new Subject();
    
     pixData!: Observable<IPix[]>;
    
     getPixData(): void {
        const headers = new HttpHeaders({
          'x-api-key':
            'edfg45mFwaUtle345345yICAgICJ1c2VySWQiIDogIlBESSBEZfes39wZXIiCn0=',
        });
    
        //assign GET result to pixData array
        this.pixData = this.http
          .get<any>(this.pixUrl, {
            headers
          })
          .pipe(
            tap((data) => console.log(JSON.stringify(data))),
            map((data: any) => data.results),
            catchError(this.handleError)
          );
        //alert subscriber (pix-table.component.ts) of the change to pixData
        this.invokeEvent.next(this.pixData);
      }
    
    

    Then in the html I used '| async' to allow iteration over the Observable

    pix-table.component.html

    *ngFor="let row of pixRecords$ | async"