diff --git a/docs/en/UI/Angular/List-Service.md b/docs/en/UI/Angular/List-Service.md
new file mode 100644
index 0000000000..b6a4dadf3e
--- /dev/null
+++ b/docs/en/UI/Angular/List-Service.md
@@ -0,0 +1,155 @@
+# Querying Lists Easily with ListService
+
+`ListService` is a utility service to provide an 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.
+
+```js
+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,
+ ) {}
+
+ 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.
+ }
+}
+```
+
+> Noticed `list` is `public` and `readonly`? That is because we will use `ListService` properties 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 properties instead.
+
+Place `ListService` properties into the template like this:
+
+```html
+
+
+
+
+
+ |
+ {%{{{ '::Name' | abpLocalization }}}%}
+
+ |
+
+
+
+
+
+ | {%{{{ data.name }}}%} |
+
+
+```
+
+## Usage with Observables
+
+You may use observables in combination with [AsyncPipe](https://angular.io/guide/observables-in-angular#async-pipe) of Angular instead. Here are some possibilities:
+
+```ts
+ book$ = this.list.hookToQuery(query => this.bookService.getListByInput(query));
+```
+
+```html
+
+
+
+
+
+
+```
+
+...or...
+
+
+```ts
+ @Select(BookState.getBooks)
+ books$: Observable;
+
+ @Select(BookState.getBookCount)
+ bookCount$: Observable;
+
+ ngOnInit() {
+ this.list.hookToQuery((query) => this.store.dispatch(new GetBooks(query))).subscribe();
+ }
+```
+
+```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.
+
+```ts
+this.store.dispatch(new DeleteBook(id)).subscribe(this.list.get);
+```
+
+...or...
+
+```ts
+this.bookService.createByInput(form.value)
+ .subscribe(() => {
+ this.list.get();
+
+ // Other subscription logic here
+ })
+```