无法让Angular 2表格组件检测到共享服务类中数组的更改。

huangapple go评论67阅读模式
英文:

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 = '';
}

(Note: The provided code snippets are in TypeScript.)

英文:

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 &#39;@angular/common/http&#39;;
import { EventEmitter, Inject, Injectable, Optional } from &#39;@angular/core&#39;;
import { catchError, map, tap, throwError } from &#39;rxjs&#39;;
import { IPix } from &#39;./model/IPix&#39;;
@Injectable({
providedIn: &#39;root&#39;,
})
export class PixSearchService {
constructor(private http: HttpClient) {}
pixUpdated: EventEmitter&lt;IPix[]&gt; = new EventEmitter();
setPixData(pixData: IPix[]) {
this.pixData = pixData;
return this.pixUpdated.emit(this.pixData);
}
getPixData()  {
return this.pixData;
}
pixData!: IPix[];
pixUrl: string = &#39;https://example.ckp-dev.example.com/example&#39;;
retrievePixData(): void {
const headers = new HttpHeaders({
&#39;x-api-key&#39;:
&#39;ewogICAgImFwaUtleSIgOiAiMTIzIiwKICAgICJ1c2VySWQiID3649807253098ESSBEZXZlbG9wZXIiCn0=&#39;,
});
this.setPixData(this.http
.get&lt;any&gt;(this.pixUrl, {
headers
})
.pipe(
tap((data) =&gt; console.log(&#39;All:&#39;, JSON.stringify(data))),
map((data: any) =&gt; data.results),
catchError(this.handleError)
) as unknown as IPix[]);
}
handleError(err: HttpErrorResponse) {
let errorMessage = &#39;&#39;;
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(() =&gt; errorMessage);
}
}

pix-table.component.ts

import {
Component,
Inject,
Input,
OnChanges,
OnDestroy,
OnInit,
Optional,
} from &#39;@angular/core&#39;;
import type { TableSize } from &#39;@dauntless/ui-kds-angular/table&#39;;
import type { TableStickyType } from &#39;@dauntless/ui-kds-angular/table&#39;;
import type { TableScrollType } from &#39;@dauntless/ui-kds-angular/table&#39;;
import { CardElevation } from &#39;@dauntless/ui-kds-angular/types&#39;;
import { PixSearchService } from &#39;../pix-search.service&#39;;
import { Observable, Subscription } from &#39;rxjs&#39;;
import { IPix } from &#39;../model/IPix&#39;;
import { IContract } from &#39;../model/IContract&#39;;
import { IAudit } from &#39;../model/IAudit&#39;;
import { ICapitation } from &#39;../model/ICapitation&#39;;
import { IChangeRequest } from &#39;../model/IChangeRequest&#39;;
import { IHnetAudit } from &#39;../model/IHnetAudit&#39;;
import { IProduct } from &#39;../model/IProduct&#39;;
import { IProvider } from &#39;../model/IProvider&#39;;
@Component({
selector: &#39;pix-table-component&#39;,
templateUrl: &#39;pix-table.component.html&#39;,
styleUrls: [&#39;pix-table.component.css&#39;],
providers: [PixSearchService]
})
export class PixTableComponent implements IPix {
constructor(private pixSearchService: PixSearchService) {
this.pixSearchService.pixUpdated.subscribe((pix) =&gt; {
this.pixRecords = this.pixSearchService.getPixData() as unknown as IPix[];
});
}
columns = [
&#39;ID&#39;,
&#39;Network&#39;,
&#39;LOB&#39;,
&#39;HP Code&#39;,
&#39;Atypical&#39;,
&#39;TIN&#39;,
&#39;GNPI&#39;,
&#39;Org&#39;,
&#39;Business Unit Code&#39;,
&#39;National Contract&#39;,
&#39;National ContractType&#39;,
&#39;Contract Type&#39;,
&#39;Super Group&#39;,
&#39;Contract ID&#39;,
&#39;Amendment ID&#39;,
&#39;Contract Effective Date&#39;,
&#39;Contract Termination Date&#39;,
];
rows: any;
tableSize: TableSize = &#39;small&#39;;
showHover = true;
sticky: TableStickyType = &#39;horizontal&#39;;
scrollType: TableScrollType = &#39;both&#39;;
label = &#39;Payment Index Management&#39;;
disabled = &#39;disabled&#39;;
error = &#39;error&#39;;
maxlength = &#39;maxlength&#39;;
showCounter = false;
elevation: CardElevation = &#39;medium&#39;;
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 = &#39;&#39;;
}

答案1

得分: 1

以下是您要翻译的内容:

EventEmitter通常用于组件之间的通信,从子组件到父组件。

当涉及到服务时,Subjects是您的最佳选择(SubjectBehaviorSubjectReplaySubjectAsyncSubject)。

对于您的特定情况,使用BehaviorSubject可能足够了,您可以实现以下内容:

服务

@Injectable({
  providedIn: 'root',
})
export class PixSearchService {
  private pixUpdated = new BehaviorSubject<IPix[]>([]);

  constructor(private http: HttpClient) {}

  fetchData(): void {
    this.http.get(...).pipe(take(1))
    .subscribe(response => this.pixUpdated.next(response))
  }

  getData(): Observable<IPix[]> {
    return this.pixUpdated.asObservable();
  }
}

组件

@Component({...})
export class PixTableComponent implements OnInit, IPix {
  dataSource$!: Observable<IPix[]>; 
 
  constructor(private pixService: PixSearchService) {}

  ngOnInit(): void {
    this.fetch();
    this.load(); // 你可以在需要时从另一个函数中调用此函数,而无需再次从后端获取数据
  }

  private fetch(): void {
    this.pixService.fetchData();  
  }

  private load(): void {
    this.dataSource$ = this.pixService.getData();
  }
}

我不确定您是否使用了Angular Material的表格,还是只是标准的表格,因此以下是处理表格数据的两种可能方法:

HTML

<!-- Angular Material 表格 -->
<table mat-table [dataSource]="dataSource$ | async">
 ....
</table>

<!-- 标准表格 -->
<table>
 <thead>
   <tr>...</tr>
 </thead>
 <tbody>
  <tr *ngFor="let item of (dataSource$ | async)">
    ....
  </tr>
 </tbody>
</table>

通过使用async管道,您可以在不需要处理订阅的情况下以反应式方式进行订阅。

有关Subjects的更多信息在此处

有关Angular组件通信的更多信息在此处

英文:

EventEmitter is commonly used for communication between components, children to a parent component.

When it comes to services, Subjects are your best bet (Subject, BehaviorSubject, ReplaySubject, AsyncSubject).

For your particular case, the use of a BehaviorSubject might be enough, you could implement the following:

Service

@Injectable({
  providedIn: &#39;root&#39;,
})
export class PixSearchService {
  private pixUpdated = new BehaviorSubject&lt;IPix[]&gt;([]);

  constructor(private http: HttpClient) {}

  fetchData(): void {
    this.http.get(...).pipe(take(1))
    .subscribe(response =&gt; this.pixUpdated.next(response))
  }

  getData(): Observable&lt;IPix[]&gt; {
    return this.pixUpdated.asObservable();
  }

Component

@Componet({...})
export class PixTableComponent implements OnInit, IPix {
  dataSource$!: Observable&lt;IPix[]&gt;; 
 
  constructor(private pixService: PixSearchService) {}

  ngOnInit(): void {
    this.fetch();
    this.load(); // you can call this function whenever you need from another function
                 // without fetching again data from the backend
  }

  private fetch(): void {
    this.pixService.fetchData();  
  }

  private load(): void {
    this.dataSource$ = this.pixService.getData();
  }
}

I'm not sure if you are using Angular Material&#39;s table or just a standard table so here are two possible approaches for handling the table's data:

HTML

&lt;!-- Angular Material Table --&gt;
&lt;table mat-table [dataSource]=&quot;dataSource$ | async&quot;&gt;
 ....
&lt;/table&gt;

&lt;!-- standard table --&gt;
&lt;table&gt;
 &lt;thead&gt;
   &lt;tr&gt;...&lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
  &lt;tr *ngFor=&quot;let item of (dataSource$ | async)&quot;&gt;
    ....
  &lt;/tr&gt;
 &lt;tbody&gt;
&lt;/table&gt;

With the use of the async pipe, you subscribe reactively without the need of handling the subscription

More information about subjects here

More information about Angular component's communication here

答案2

得分: 0

我使用了一个Subject()和订阅来通知表格组件有关服务类中IPix数组的更改:

pix-search.component.ts

constructor(private _pixSearchService: PixSearchService) {}

// 触发服务类中IPix数组的更新
buttonClicked(button: any) {
  this._pixSearchService.getPixData();
}

pix-table.component.ts

pixRecords$!: Observable<IPix[]>;

constructor(private _pixSearchService: PixSearchService) {
  // 订阅服务类的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=',
   });

   // 将GET结果分配给pixData数组
   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)
     );
   // 通知订阅者(pix-table.component.ts)pixData的更改
   this.invokeEvent.next(this.pixData);
}

然后在HTML中,我使用| async来允许对Observable进行迭代:

pix-table.component.html

*ngFor="let row of pixRecords$ | async"
英文:

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&lt;IPix[]&gt;;
constructor(private _pixSearchService: PixSearchService) {
//subscribe to the service class&#39; Subject()
this._pixSearchService.invokeEvent.subscribe((pixData) =&gt; {
console.log(pixData);
this.pixRecords$ = pixData;
console.log(JSON.stringify(pixData));
});
}

pix-search.service.ts

 public invokeEvent: Subject&lt;any&gt; = new Subject();
pixData!: Observable&lt;IPix[]&gt;;
getPixData(): void {
const headers = new HttpHeaders({
&#39;x-api-key&#39;:
&#39;edfg45mFwaUtle345345yICAgICJ1c2VySWQiIDogIlBESSBEZfes39wZXIiCn0=&#39;,
});
//assign GET result to pixData array
this.pixData = this.http
.get&lt;any&gt;(this.pixUrl, {
headers
})
.pipe(
tap((data) =&gt; console.log(JSON.stringify(data))),
map((data: any) =&gt; 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=&quot;let row of pixRecords$ | async&quot;

huangapple
  • 本文由 发表于 2023年6月16日 02:52:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/76484702.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定