Web API  

Paging, Sorting & Filtering Across API + UI (Perfect Beginner Project)

Introduction

When building web applications, working with large datasets is common. Loading all data at once is inefficient. Users often need to:

  • See data in pages (Paging)

  • Sort data by columns (Sorting)

  • Filter data based on criteria (Filtering)

In this tutorial, we will build a complete beginner-friendly project implementing paging, sorting, and filtering across ASP.NET Core API and Angular UI. You will learn how to pass parameters, query data efficiently, and display it interactively.

Application Architecture Overview

Angular UI  →  API Calls (Paging/Sorting/Filtering) → ASP.NET Core → SQL Server

Responsibilities:

  • UI: Capture user input, display paged data, allow sorting and filtering

  • API: Receive parameters (page, size, sort, filter) and return only required data

  • Database: Query efficiently with skip/take and where/order clauses

1. Setting Up the Database

Sample Table: Employees

CREATE TABLE Employees (
    Id INT IDENTITY(1,1) PRIMARY KEY,
    Name NVARCHAR(100),
    Email NVARCHAR(100),
    Department NVARCHAR(50)
);

Seed with some test data:

INSERT INTO Employees (Name, Email, Department)
VALUES 
('Alice', '[email protected]', 'HR'),
('Bob', '[email protected]', 'IT'),
('Charlie', '[email protected]', 'Finance'),
-- repeat for 50+ records for paging

2. Creating the ASP.NET Core API

Create a New Project

dotnet new webapi -n PagingApi
cd PagingApi

Install EF Core packages:

dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer

Create Employee Model

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public string Department { get; set; }
}

Create DbContext

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
    public DbSet<Employee> Employees { get; set; }
}

Configure connection string and register DbContext in Program.cs.

3. Implementing Paging, Sorting & Filtering in API

Create EmployeesController:

[ApiController]
[Route("api/[controller]")]
public class EmployeesController : ControllerBase
{
    private readonly AppDbContext _context;
    public EmployeesController(AppDbContext context)
    {
        _context = context;
    }

    [HttpGet]
    public async Task<IActionResult> Get(
        int page = 1, 
        int pageSize = 10, 
        string sortColumn = "Name", 
        string sortDirection = "asc", 
        string search = "")
    {
        var query = _context.Employees.AsQueryable();

        // Filtering
        if(!string.IsNullOrEmpty(search))
        {
            query = query.Where(e => e.Name.Contains(search) || e.Email.Contains(search) || e.Department.Contains(search));
        }

        // Sorting
        query = sortDirection.ToLower() == "asc" 
            ? query.OrderByDynamic(sortColumn, true) 
            : query.OrderByDynamic(sortColumn, false);

        // Total count for paging
        var total = await query.CountAsync();

        // Paging
        var data = await query
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync();

        return Ok(new { total, data });
    }
}

Note: Implement OrderByDynamic helper for dynamic sorting.

public static class IQueryableExtensions
{
    public static IQueryable<T> OrderByDynamic<T>(this IQueryable<T> source, string column, bool ascending)
    {
        if(string.IsNullOrEmpty(column))
            return source;

        var parameter = Expression.Parameter(typeof(T), "p");
        var property = Expression.Property(parameter, column);
        var lambda = Expression.Lambda(property, parameter);

        string methodName = ascending ? "OrderBy" : "OrderByDescending";

        var result = typeof(Queryable).GetMethods()
            .Single(
                method => method.Name == methodName 
                          && method.GetParameters().Length == 2
            )
            .MakeGenericMethod(typeof(T), property.Type)
            .Invoke(null, new object[] { source, lambda });

        return (IQueryable<T>)result;
    }
}

4. Angular Frontend Setup

Create Angular Project

ng new paging-ui
cd paging-ui
ng add @angular/material

Employee Model

employee.model.ts:

export interface Employee {
  id: number;
  name: string;
  email: string;
  department: string;
}

Employee Service

employee.service.ts:

@Injectable({
  providedIn: 'root'
})
export class EmployeeService {

  apiUrl = 'https://localhost:5001/api/employees';

  constructor(private http: HttpClient) {}

  getEmployees(page: number, pageSize: number, sortColumn: string, sortDir: string, search: string) {
    let params = new HttpParams()
      .set('page', page)
      .set('pageSize', pageSize)
      .set('sortColumn', sortColumn)
      .set('sortDirection', sortDir)
      .set('search', search);

    return this.http.get<{ total: number, data: Employee[] }>(this.apiUrl, { params });
  }
}

5. Employee List Component

employee-list.component.ts:

export class EmployeeListComponent implements OnInit {

  employees: Employee[] = [];
  totalRecords = 0;

  page = 1;
  pageSize = 10;
  sortColumn = 'name';
  sortDirection = 'asc';
  search = '';

  constructor(private service: EmployeeService) {}

  ngOnInit(): void {
    this.loadEmployees();
  }

  loadEmployees() {
    this.service.getEmployees(this.page, this.pageSize, this.sortColumn, this.sortDirection, this.search)
      .subscribe(res => {
        this.employees = res.data;
        this.totalRecords = res.total;
      });
  }

  onPageChange(newPage: number) {
    this.page = newPage;
    this.loadEmployees();
  }

  onSort(column: string) {
    this.sortColumn = column;
    this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
    this.loadEmployees();
  }

  onSearchChange(searchValue: string) {
    this.search = searchValue;
    this.page = 1;
    this.loadEmployees();
  }
}

Employee List HTML

<input type="text" placeholder="Search..." (input)="onSearchChange($event.target.value)" />

<table>
  <thead>
    <tr>
      <th (click)="onSort('name')">Name</th>
      <th (click)="onSort('email')">Email</th>
      <th (click)="onSort('department')">Department</th>
    </tr>
  </thead>
  <tbody>
    <tr *ngFor="let emp of employees">
      <td>{{ emp.name }}</td>
      <td>{{ emp.email }}</td>
      <td>{{ emp.department }}</td>
    </tr>
  </tbody>
</table>

<div>
  <button (click)="onPageChange(page - 1)" [disabled]="page === 1">Prev</button>
  <span>{{ page }}</span>
  <button (click)="onPageChange(page + 1)" [disabled]="page * pageSize >= totalRecords">Next</button>
</div>

6. How It Works

  1. Paging: Skip and Take on API, buttons on UI

  2. Sorting: Dynamic OrderBy on API, click column headers on UI

  3. Filtering: Where clause on API, input box in UI

Everything is connected via Angular HTTP requests and reactive state updates.

7. Best Practices

  1. Limit pageSize to prevent overloading API

  2. Use async pipes in Angular to avoid manual subscriptions

  3. Debounce search input for better performance

  4. Always validate query parameters on API

  5. Use indexes in SQL for faster filtering/sorting

  6. Return totalRecords to let frontend calculate pages

Conclusion

Paging, sorting, and filtering are essential features for any data-driven web app. By integrating ASP.NET Core API with Angular UI, you can build interactive, performant, and scalable applications. This project is perfect for beginners because it demonstrates:

  • API parameter handling

  • Querying the database efficiently

  • Passing data between frontend and backend

  • Dynamic UI updates

This foundation will prepare you for building complex admin dashboards, reporting apps, and enterprise-level web projects.