0

I inherited some Angular/ng-boostrap code that defined a table with static data that works fine. Now we need to fetch the data from an API call so I tried to modify it as below. I used the answer from this question as a base.

here is a minimal version showcasing the problem. (https://stackblitz.com/~/github.com/cherfim/angular-problem if the link is not working, copy paste will work)

executions.service.ts where the API call is made:

import { Injectable, PipeTransform } from '@angular/core';
import { DecimalPipe } from '@angular/common';
import { BehaviorSubject, Observable, of, Subject } from 'rxjs';
import { debounceTime, delay, map, switchMap, tap } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';

interface SearchResult {
  jobs: any[];
  total: number;
}

interface State {
  page: number;
  pageSize: number;
  searchTerm: string;
  startIndex: number;
  endIndex: number;
  totalRecords: number;
}

const compare = (v1: string | number, v2: string | number) => v1 < v2 ? -1 : v1 > v2 ? 1 : 0;

/**
 * Table Data Match with Search input
 * @param  job field value fetch
 * @param term Search the value
 */
function matches(job: any, term: string, pipe: PipeTransform) {
  return true;
}

@Injectable({
  providedIn: 'root'
})

export class AdvancedService {
  fetchedJobs: any[] = [];
  fetchedJobs$: Observable<any[]>;
  private _loading$ = new BehaviorSubject<boolean>(true);
  private _search$ = new Subject<void>();
  private _jobs$ = new BehaviorSubject<any[]>([]);
  private _total$ = new BehaviorSubject<number>(0);
  private _state: State = {
    page: 1,
    pageSize: 10,
    searchTerm: '',
    startIndex: 0,
    endIndex: 9,
    totalRecords: 0
  };

  constructor(private pipe: DecimalPipe,
    private http: HttpClient) {

    this.fetchedJobs$ = this.http.get<any[]>(`https://api.publicapis.org/entries`).pipe(
      tap((res: any[])=>{
        this.fetchedJobs=res;
    }));

    this._search$.pipe(
      tap(() => this._loading$.next(true)),
      debounceTime(200),
      switchMap(() => this._search()),
      delay(200),
      tap(() => this._loading$.next(false))
    ).subscribe(result => {
      this._jobs$.next(result.jobs);
      this._total$.next(result.total);
    });
    this._search$.next();
  }

  /**
   * Returns the value
   */
  get jobs$() { return this._jobs$.asObservable(); }
  get total$() { return this._total$.asObservable(); }
  get loading$() { return this._loading$.asObservable(); }
  get page() { return this._state.page; }
  get pageSize() { return this._state.pageSize; }
  get searchTerm() { return this._state.searchTerm; }

  get startIndex() { return this._state.startIndex; }
  get endIndex() { return this._state.endIndex; }
  get totalRecords() { return this._state.totalRecords; }

  /**
   * set the value
   */
  set page(page: number) { this._set({ page }); }
  set pageSize(pageSize: number) { this._set({ pageSize }); }
  set startIndex(startIndex: number) { this._set({ startIndex }); }
  set endIndex(endIndex: number) { this._set({ endIndex }); }
  set totalRecords(totalRecords: number) { this._set({ totalRecords }); }
  set searchTerm(searchTerm: string) { this._set({ searchTerm }); }

  private _set(patch: Partial<State>) {
    Object.assign(this._state, patch);
    this._search$.next();
  }

  /**
   * Search Method
   */
  private _search(): Observable<SearchResult> {
    const { pageSize, page, searchTerm } = this._state;

    return this.fetchedJobs$.pipe(
      map((res: any[]) => {

      let jobs = Object.values(res);

      // 2. filter
      jobs = jobs.filter(job => matches(job, searchTerm, this.pipe));
      const total = jobs.length;

      // 3. paginate
      this.totalRecords = jobs.length;
      this._state.startIndex = (page - 1) * this.pageSize + 1;
      this._state.endIndex = (page - 1) * this.pageSize + this.pageSize;
      if (this.endIndex > this.totalRecords) {
        this.endIndex = this.totalRecords;
      }
      jobs = jobs.slice(this._state.startIndex - 1, this._state.endIndex);

      return  { jobs, total } as SearchResult;

    }));

  }
}


app.component.ts where the subscription is done:

import { Component, OnInit, ViewChildren, QueryList } from '@angular/core';
import { Observable } from 'rxjs';
import { AdvancedService } from './executions.service';
import { CommonModule, DecimalPipe } from '@angular/common';


@Component({
  selector: 'app-component',
  standalone: true,
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
  imports: [CommonModule],
  providers: [AdvancedService, DecimalPipe]
})

/**
 * Datatable Component
 */
export class AppComponent implements OnInit {

  tables$: Observable<any[]>;
  total$: Observable<number>;
  testbooks: any[] = [];

  constructor(public service: AdvancedService) {
    this.tables$ = service.jobs$;
    this.total$ = service.total$;
  }

  ngOnInit(): void {

    this.tables$.subscribe((data: any) => {
      console.log(data.length);
      this.testbooks = data;
    });
  }
}

then using it in the template with

<div class="row">
    <div class="col-lg-12">
        <div class="card">
            <div class="card-body">
                <div class="table-responsive">
                    <table id="datatable" class="table table-bordered dt-responsive nowrap w-100 datatables">
                        <tbody *ngFor="let job of testbooks;let i=index;">
                            <tr>
                                <td>
                                    <div class="card">
                                        <div class="card-body">
                                            <h1>test</h1>

                                        </div>
                                    </div>
                                </td>
                            </tr>
                        </tbody>
                    </table>
                </div>
            </div>
            <!-- end card body -->
        </div>
        <!-- end card -->

    </div>
    <!-- end col -->
</div>
<!-- end row -->

I see in the browser console that he call is made, data fetched and displayed, my problem is that the API is being called non stop and data refreshed as a result (like once a second).

what am I doing wrong ?

5
  • 2
    There is a lot of code here. It would be much easier for others to help if you created a Minimal Reproducible Example. Also, the title of your post mentions using fetched data with ng-bootstrap, but at the end of the post you say everything is working except the data keeps refreshing. I suggest to update your title to represent the problem you are having. Commented Jan 25, 2024 at 0:17
  • Your stackbiz is not working. It throws 404 Commented Jan 25, 2024 at 17:19
  • I just tried from another not-logged-in machine and it works fine, I honestly don't know what's happening ! Commented Jan 25, 2024 at 17:45
  • I got what the problem is with stackblitz, stackoverlfow is change the character '~' in the URL to '%7E' causing the 404, not sure how to fix it though :/ Commented Jan 25, 2024 at 18:07
  • I added the link, copy paste should work. Commented Jan 25, 2024 at 18:11

1 Answer 1

1

In the _search method in AdvancedService, you are calling the totalRecords and endIndex setters (lines 118 and 122 in StackBlitz). These setters call the _set method, which nexts the _search$ Subject, which then causes the _search$ stream subscription inside the service constructor to run.

I do not think you should be nexting the _search$ Subject when setting startIndex, endIndex and totalRecords. Something like this should work:

  set startIndex(startIndex: number) {
    this._set({ startIndex }, false);
  }

  set endIndex(endIndex: number) {
    this._set({ endIndex }, false);
  }

  set totalRecords(totalRecords: number) {
    this._set({ totalRecords }, false);
  }

  private _set(patch: Partial<State>, triggerSearch = true) {
    Object.assign(this._state, patch);

    if (triggerSearch) {
      this._search$.next();
    }
  }
Sign up to request clarification or add additional context in comments.

3 Comments

you are right, when I remove this._search$.next(); call from _set method the refresh of the data stops, but then when I change the pagesize from UI it doesn't refresh because the call is not there :/ . any idea how to keep those ?
Check my updated answer for one possible solution
Even with this, it still triggers the API non-stop if I change one of those parameters. Thank you, this gives me a starting point to fix it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.