Working with Lists

ListService is a utility service to provide easy pagination, sorting, and search implementation.

Getting Started

ListService is not provided in root. The reason is, this way, it will clear any subscriptions on component destroy. You may use the optional LIST_QUERY_DEBOUNCE_TIME token to adjust the debounce behavior.

import { ListService } from '@abp/ng.core';
import { BookDto } from '../models';
import { BookService } from '../services';

@Component({
  /* class metadata here */
  providers: [
    // [Required]
    ListService,

    // [Optional]
    // Provide this token if you want a different debounce time.
    // Default is 300. Cannot be 0. Any value below 100 is not recommended.
    { provide: LIST_QUERY_DEBOUNCE_TIME, useValue: 500 },
  ],
  template: `
    
  `,
})
class BookComponent {
  items: BookDto[] = [];
  count = 0;

  constructor(
    public readonly list: ListService,
    private bookService: BookService,
  ) {
    // change ListService defaults here
    this.list.maxResultCount = 20;
  }

  ngOnInit() {
    // A function that gets query and returns an observable
    const bookStreamCreator = query => this.bookService.getList(query);

    this.list.hookToQuery(bookStreamCreator).subscribe(
      response => {
        this.items = response.items;
        this.count = response.count;
        // If you use OnPush change detection strategy,
        // call detectChanges method of ChangeDetectorRef here.
      }
    ); // Subscription is auto-cleared on destroy.
  }
}
JavaScript

Noticed list is public and readonly? That is because we will use ListService directly in the component's template. That may be considered as an anti-pattern, but it is much quicker to implement. You can always use public component members to expose the ListService instance instead.

Bind ListService to ngx-datatable like this:

<ngx-datatable
  [rows]="items"
  [count]="count"
  [list]="list"
  default
>
  <!-- column templates here -->
</ngx-datatable>
HTML

Extending query with custom variables

You can extend the query parameter of the ListService's hookToQuery method.

Firstly, you should pass your own type to ListService as shown below:

constructor(public readonly list: ListService<BooksSearchParamsDto>) { }
TypeScript

Then update the bookStreamCreator constant like following:

const bookStreamCreator = (query) => this.bookService.getList({...query, name: 'name here'});
TypeScript

You can also create your params object.

Define a variable like this:

booksSearchParams = {} as BooksSearchParamsDto;
TypeScript

Update the bookStreamCreator constant:

const bookStreamCreator = (query) => this.bookService.getList({...query, ...this.booksSearchParams});
TypeScript

Then you can place inputs to the HTML:

<div class="form-group">
  <input
    class="form control"
    placeholder="Name"
    (keyup.enter)="list.get()"
    [(ngModel)]="booksSearchParams.name"
  />
</div>
HTML

ListService emits the hookToQuery stream when you call the this.list.get() method.

Usage with Observables

You may use observables in combination with AsyncPipe of Angular instead. Here are some possibilities:

  book$ = this.list.hookToQuery(query => this.bookService.getListByInput(query));
JavaScript
<!-- simplified representation of the template -->

<ngx-datatable
  [rows]="(book$ | async)?.items || []"
  [count]="(book$ | async)?.totalCount || 0"
  [list]="list"
  default
>
  <!-- column templates here -->
</ngx-datatable>

<!-- DO NOT WORRY, ONLY ONE REQUEST WILL BE MADE -->
HTML

Handle request status

To handle the request status ListService provides a requestStatus$ observable. This observable emits the current status of a request, which can be one of the following values: idle, loading, success or error. These statuses allow you to easily manage the UI flow based on the request's state.

RequestStatus

import { ListService } from '@abp/ng.core';
import { AsyncPipe } from '@angular/common';
import { Component, inject } from '@angular/core';
import { BookDto, BooksService } from './books.service';

@Component({
  standalone: true,
  selector: 'app-books',
  templateUrl: './books.component.html',
  providers: [ListService, BooksService],
  imports: [AsyncPipe],
})
export class BooksComponent {
  list = inject(ListService);
  booksService = inject(BooksService);

  items = new Array<BookDto>();
  count = 0;

  //It's an observable variable
  requestStatus$ = this.list.requestStatus$;

  ngOnInit(): void {
    this.list
      .hookToQuery(() => this.booksService.getList())
      .subscribe(response => {
        this.items = response.items;
        this.count = response.totalCount;
      });
  }
}
JavaScript
<div class="card">
  <div class="card-header">
    @if (requestStatus$ | async; as status) {
      @switch (status) {
        @case ('loading') {
          <div style="height: 62px">
            <div class="spinner-border" role="status" id="loading">
              <span class="visually-hidden">Loading...</span>
            </div>
          </div>
        }
        @case ('error') {
          <h4>Error occured</h4>
        }
        @default {
          <h4>Books</h4>
        }
      }
    }
  </div>
  <table class="table">
    <thead>
      <tr>
        <th>Id</th>
        <th>Name</th>
      </tr>
    </thead>
    <tbody>
      @for (book of items; track book.id) {
        <tr>
          <td>{{ book.id }}</td>
          <td>{{ book.name }}</td>
        </tr>
      }
    </tbody>
  </table>
</div>
HTML

How to Refresh Table on Create/Update/Delete

ListService exposes a get method to trigger a request with the current query. So, basically, whenever a create, update, or delete action resolves, you can call this.list.get(); and it will call hooked stream creator again.

  this.bookService.createByInput(form.value)
    .subscribe(() => {
      this.list.get();

      // Other subscription logic here
    });
JavaScript

How to Implement Server-Side Search in a Table

ListService exposes a filter property that will trigger a request with the current query and the given search string. All you need to do is to bind it to an input element with two-way binding.

<!-- simplified representation -->

<input type="text" name="search" [(ngModel)]="list.filter">
HTML

ABP doesn't have a built-in filtering mechanism. You need to implement it yourself and handle the filter property in the backend.

Contributors


Last updated: October 24, 2024 Edit this page on GitHub

Was this page helpful?

Please make a selection.

To help us improve, please share your reason for the negative feedback in the field below.

Please enter a note.

Thank you for your valuable feedback!

Please note that although we cannot respond to feedback, our team will use your comments to improve the experience.

Community Talks

Real World Problems and Solutions with AI

27 Feb, 17:00
Online
Watch the Event
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
Do you need assistance from an ABP expert?
Schedule a Meeting
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
×