Search code examples
angularrxjsdatasource

Angular makes http call twice


I'm developing an Angular application. I have a custom dataSource, which fetch data from server and fill the material table.

Here is the method from service:

dataChange: BehaviorSubject<Segment[]> = new BehaviorSubject<Segment[]>([]);

 getSegmentsByProgramId(programId: number) {
    this.subscription = this.httpClient
      .get<Segment[]>(`${environment.baseUrl}/segment/program/${programId}`)

      .subscribe(
        segments => {
          this.dataChange.next(segments);
        },
        (error: HttpErrorResponse) => {
          console.log(error.message);
        },
        () => console.log('completed')
      );
  }

Here is the template of the parent component:

<mat-card class="mat-elevation-z10 mt-2">
  <mat-card-header>
    <mat-card-title>
      <h4 class="card-title">Segments</h4>
    </mat-card-title>
  </mat-card-header>
  <mat-card-content class="mr-3">
    <mat-tab-group>
      <mat-tab *ngFor="let program of programs$ | async">
        <ng-template mat-tab-label>
          <span> {{ program.name }} </span></ng-template
        >
        <app-segment-table [programId]="program.id"></app-segment-table>
      </mat-tab>
    </mat-tab-group>
  </mat-card-content>
</mat-card>

Here is the .ts of parent component:

export class SegmentListComponent implements OnInit {
  programs$: Observable<Program[]>;
  members$: Observable<number>;

  @Output() createSegmentEvent = new EventEmitter();

  constructor(private programService: ProgramService, private membersService: MembersService) {}

  ngOnInit() {
    this.programs$ = this.programService.programs$;
    this.members$ = this.membersService.members$.pipe(map(members => members.length));
  }

  createSegment() {
    this.createSegmentEvent.next(true);
  }
}

In child ts:

export class SegmentTableComponent implements OnInit {
  segmentsDatabase: SegmentsService | null;
  dataSource: SegmentsDataSource | null;
  displayedColumns = ['name', 'startDate', 'endDate', 'cashback', 'strategy', 'status', 'members', 'actions'];
  index: number;
  id: number;

  isLoading = true;

  @Input() programId: number | any;

  @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
  @ViewChild(MatSort, { static: true }) sort: MatSort;
  @ViewChild('filter', { static: true }) filter: ElementRef;

  constructor(public httpClient: HttpClient, private segmentsService: SegmentsService, public dialog: MatDialog) {}

  ngOnInit() {
    this.loadData(this.programId);
  }

  public loadData(programId: number | any) {
    this.segmentsDatabase = new SegmentsService(this.httpClient);
    this.dataSource = new SegmentsDataSource(this.segmentsDatabase, this.paginator, this.sort, programId);

    fromEvent(this.filter.nativeElement, 'keyup')
      .pipe(debounceTime(150), distinctUntilChanged())
      .subscribe(() => {
        if (!this.dataSource) {
          return;
        }
        this.dataSource.filter = this.filter.nativeElement.value;
      });
  }
}

And here is the dataSuorce:

export class SegmentsDataSource extends DataSource<Segment> {
  _filterChange = new BehaviorSubject('');

  get filter(): string {
    return this._filterChange.value;
  }

  set filter(filter: string) {
    this._filterChange.next(filter);
  }

  filteredData: Segment[] = [];
  renderedData: Segment[] = [];

  constructor(
    public _segmentsDataBase: SegmentsService,
    public _paginator: MatPaginator,
    public _sort: MatSort,
    public _programId: number | any
  ) {
    super();
    this._filterChange.subscribe(() => (this._paginator.pageIndex = 0));
  }

  connect(): Observable<Segment[]> {
    const displayDataChanges = [
      this._segmentsDataBase.dataChange,
      this._sort.sortChange,
      this._filterChange,
      this._paginator.page,
      this._programId
    ];

    this._segmentsDataBase.getSegmentsByProgramId(this._programId);

    return merge(...displayDataChanges).pipe(
      map(() => {
        this.filteredData = this._segmentsDataBase.data.slice().filter((segment: Segment) => {
          const searchStr = (
            segment.name +
            segment.startDate +
            segment.endDate +
            segment.cashback +
            segment.status
          ).toLowerCase();
          return searchStr.indexOf(this.filter.toLowerCase()) !== -1;
        });
        const sortedData = this.sortData(this.filteredData.slice());

        const startIndex = this._paginator.pageIndex * this._paginator.pageSize;
        this.renderedData = sortedData.splice(startIndex, this._paginator.pageSize);
        return this.renderedData;
      })
    );
  }

  disconnect() {}
}

When I am on this page and reload it - everything works fine. Service is called once. But when I transfer from another page ... my server is being called twice.

I have no idea what causes this issue... Maybe someone can help me to resolve it.


Solution

  • better work with pipe in the this.httpClient-request to get the result asynchron. Evtl. you run in an error situation when you transfer to another site and the retry-option of angular will retry the request.

    for example:

    getHero(id: number): Observable<Hero> {
      const url = `${this.heroesUrl}/${id}`;
      return this.http.get<Hero>(url).pipe(
        tap(_ => this.log(`fetched hero id=${id}`)),
        catchError(this.handleError<Hero>(`getHero id=${id}`))
      );
    }
    

    Here is a ref