Data Lists in Angular 15 – Add, Remove, Sort, Filter and Server Side Resolve

Data lists are used to list down items that can be of Array or Object type. In this article we will discuss how to list down items and also other operations related to them like: Optimize Data List using trackBy How to get an Index of each data list item? Using an alias with ‘as’…

By.

•

min read

Data lists are used to list down items that can be of Array or Object type. In this article we will discuss how to list down items and also other operations related to them like:

  • Optimize Data List using trackBy
  • How to get an Index of each data list item?
  • Using an alias with ‘as’
  • How to Filter/ Search Data List?
  • Lazy loading of Data list items with resolve
  • Ways to refresh the data list
  • Animating list items when added or removed

[lwptoc]

ngFor Directive

In Angular, we use the *ngFor directive to loop through data and help to generate a custom template for each item. Here is an example:

<div *ngFor="let item of items">
  {{item.name}}
</div>

This will loop through the items array and display the name property of each item.

 

Displaying Lists with *ngFor

Above we discussed on to iterate Array items, but here we will use an Object having multiple key-value pairs or in multiple hierarchies

Here is an example to display a list of products:

@Component({
  // component metadata
})
export class ProductListComponent {

  products = [
    {name: 'Apples', category: 'Fruit'},
    {name: 'Oranges', category: 'Fruit'},
    {name: 'Bread', category: 'Bakery'}
  ];

}
<div *ngFor="let product of products">
  <h3>{{product.name}}</h3>
  <p>{{product.category}}</p> 
</div>

This will loop through the products array and display the name and category of each product.

 

Optimizing with trackBy

When the items in a list become huge, it starts lagging to find the actionable items. By default, Angular checks each object instance for changes on every cycle.

Using trackBy, Angular will track changes by the result supplied by the trackBy function instead of the object instance.

On the first render, Angular stores the ID of each item in the list, thereafter Angular calls the trackByItemId() function for each item and compares the ID returned with the previously stored ID. If the ID is same, Angular reuse existing rendered binding in DOM else destroys the old DOM element and bonding to create new ones.

Here is how to use trackBy to optimize performance:

<div *ngFor="let product of products; trackBy: trackByFn">

</div>
trackByFn(index, item) {
  return item.id; // unique identifier
}

Now each item is tracked by a unique id instead of re-rendering the complete list when data changes.

 

Utilizing the index in ngFor

Sometimes, we may require to get the index id of each item from the list. This index can be used for displaying serial/ sequence numbers or to add unique identifiers etc.

The index can be used to display the index of each item:

<div *ngFor="let product of products; let i = index">
  {{i + 1}} - {{product.name}}
</div>

 

The index value starts from 0, so we can have index+1 for the first item.

 

Alias with as Syntax

We can use as to assign an alias to each item. Let’s have a look on some common use-cases of alias with examples below:

Cleaner Referencing in Template

Instead of:

*ngFor="let product of products"
{{product.name}}

We can alias the product to p for brevity:

*ngFor="let product of products as p" 
{{p.name}}

Avoid Name Conflicts

If our component has a property with the same name as the loop variable:

// class
product: any;

// template 
*ngFor="let product of products"

We can alias to avoid the name collision:

*ngFor="let product of products as p"

 

Loop Index

Alias index to i for conciseness:

*ngFor="let item of items; let i = index"
{{i}}

Instead of:

{{index}}

 

Self Reference

Alias the loop variable to reference itself:

*ngFor="let item of items as item"
{{item.children}}

 

Unconventional Names

Alias to more descriptive names like:

*ngFor="let product of products as prod"
{{prod.name}}

 

How to filter/search the data list?

We can easily implement the client-side pipe to implement the filter or search on the rendered data list items. Here we have added an input text field to pass the search term to the product data list via searchTerm model.

<input type="text" [(ngModel)]="searchTerm">

<div *ngFor="let product of products | filter:searchTerm">
  {{product.name}}
</div>
// pipe
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'filter'
})
export class FilterPipe implements PipeTransform {

  transform(items: any[], searchTerm: string) {
    return items.filter(item => {
      return item.name.toLowerCase().includes(searchTerm.toLowerCase());
    });
  }
}

In our custom pipe, we can have our custom logic to compare the search term item with each data list value.

 

Lazy loading of Data list items with resolve

Sometimes, we may need to fetch the data list items from a remote server which needs to be resolved first before rending them to view.

In such a situation, we can set up a resolver service and plug it into the router module as shown below:

First, create a resolver service to fetch the remote response data:

// product-list-resolver.service.ts

@Injectable()
export class ProductListResolver implements Resolve<any> {

  constructor(private productService: ProductService) {}

  resolve(route: ActivatedRouteSnapshot) {
    return this.productService.getProducts();
  }

}

 

In the component class, we are fetching data inside the ngOnInit hook:

// product-list.component.ts

constructor(private route: ActivatedRoute) {}

ngOnInit() {
  this.products = this.route.snapshot.data['products'];
}

 

Then in the products router config path, we will add the resolve get the data and pass it into the products to provide the data in the component class’s ngOnInit hook

// app-routing.module.ts

{
  path: 'products',
  component: ProductListComponent,
  resolve: {
    products: ProductListResolver
  }
}

 

Refreshing Data

For refreshing the data list, you can use Rxjs Subject or BehaviourSubject to pass the value using next() and subscribe to the same instance to pick the value and rerender the list.

Use an Observable:

// product.service.ts

products$ = new BehaviorSubject<Product[]>([]);

getProducts() {
  // api call
  this.products$.next(data);
}

// product-list.component.ts

products$ = this.productService.products$;

ngOnInit() {
  this.products$.subscribe(items => {
    this.products = items;
  });
}

 

Animating List Changes

Let’s have a look at how to add Angular Animation when items are added or removed from the list.

// component
@Component({
  // ...
  animations: [
    trigger('itemAnim', [
      transition(':enter', [
        style({opacity: 0, transform: 'translateY(-10px)'}), 
        animate('500ms', style({opacity: 1, transform: 'translateY(0)'}))  
      ]),
      transition(':leave', [
        animate('500ms', style({opacity: 0})) 
      ])
    ])
  ]
})
export class ProductListComponent {

  // ...

}
// template
<div [@itemAnim] *ngFor="let product of products">
  {{product.name}} 
</div>

We have implemented the @itemAnim to our list template resulting in the fades in new items and fades out removed items.

 

How to Add/ Remove Items in the Data List?

Now we will learn how to add new items to the data list and also removing the selected item from the list by adding a delete action:

import { Component } from '@angular/core';

@Component({
  //...
})
export class MyComponent {

  items = [
    { id: 1, name: 'Item 1'},
    { id: 2, name: 'Item 2'}
  ];

  addItem() {
    this.items.push({
      id: this.items.length + 1,
      name: 'New Item' 
    });
  }

  removeItem(id: number) {
    this.items = this.items.filter(item => item.id !== id);
  }

}
<button (click)="addItem()">Add Item</button>

<div *ngFor="let item of items">
  {{item.name}}
  <button (click)="removeItem(item.id)">Remove</button>
</div>
  • addItem() pushes a new item onto the items array
  • removeItem() filters the array to remove the item with the matching id
  • The add button calls addItem() on click
  • The remove button calls removeItem(), passing the id of the item to remove

We can also animate the changes using Angular animations:

// component
@Component({
  //...
  animations: [
    trigger('listAnimation', [
      transition(':enter', [
        style({ opacity: 0, transform: 'translateY(-10px)' }),
        animate('500ms', style({ opacity: 1, transform: 'translateY(0)' })),
      ]),
      transition(':leave', [
        animate('500ms', style({ opacity: 0 })),
      ]),
    ]),
  ]
})
<!-- template -->
<div [@listAnimation] *ngFor="let item of items">
</div>

 

Conclusion

We discussed how to iterate a simple Array or Object into the template using *ngFor Angular build-in structure directive. We looked deep into various other features we may require in our application like optimising list rendering using trackBy getting an index of each item, using alias, filtering items using pipe and much more.

Hope this helps…

Leave a Reply

Your email address will not be published. Required fields are marked *