Below are interview-focused answers for an Angular + ASP.NET Core Web API application.
1. How would you share logged-in user information across components?
Use a singleton authentication/user service to maintain the logged-in user's state. In modern Angular, Signals are a simple choice for application-wide user state.
Angular model
export interface User {
id: number;
name: string;
email: string;
role: string;
}
auth.service.ts
import { Injectable, computed, signal } from '@angular/core';
import { User } from './user';
@Injectable({
providedIn: 'root'
})
export class AuthService {
private currentUserSignal = signal<User | null>(null);
currentUser = this.currentUserSignal.asReadonly();
isLoggedIn = computed(() => this.currentUserSignal() !== null);
setUser(user: User) {
this.currentUserSignal.set(user);
}
logout() {
this.currentUserSignal.set(null);
}
}
Because the service uses:
providedIn: 'root'
the same service instance is shared across the application.
Header component
import { Component, inject } from '@angular/core';
import { AuthService } from './auth.service';
@Component({
selector: 'app-header',
standalone: true,
template: `
@if (auth.currentUser(); as user) {
<span>Welcome {{ user.name }}</span>
}
`
})
export class Header {
auth = inject(AuthService);
}
Profile component
export class Profile {
auth = inject(AuthService);
}
@if (auth.currentUser(); as user) {
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
}
Both components automatically see changes to the same user state.
Important architecture
Login Component
|
| Login
↓
ASP.NET Core API
|
| JWT / User Information
↓
AuthService
|
| Signal<User>
↓
-----------------------------
| | |
Header Profile Admin
For security, Angular's user state is not authoritative. ASP.NET Core must still authenticate the JWT and enforce authorization on protected API endpoints.
Interview answer:
I normally maintain logged-in user state in a root-level authentication service. Components consume the shared state using Signals or Observables. However, actual authentication and authorization are always enforced by ASP.NET Core.
2. How would you implement pagination using Angular and ASP.NET Core?
Pagination should normally be server-side for large datasets.
Instead of retrieving 100,000 records and paginating them in Angular:
GET /api/articles
send pagination parameters:
GET /api/articles?page=2&pageSize=10
ASP.NET Core
[HttpGet]
public async Task<IActionResult> GetArticles(
int page = 1,
int pageSize = 10)
{
var query = _context.Articles
.AsNoTracking()
.OrderByDescending(x => x.CreatedOn);
var totalCount = await query.CountAsync();
var articles = await query
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return Ok(new
{
items = articles,
totalCount,
page,
pageSize
});
}
The key calculation is:
Skip((page - 1) * pageSize)
Take(pageSize)
For page 3 with 10 records per page:
Skip = (3 - 1) × 10
= 20
Take = 10
So SQL effectively returns records 21–30.
Angular service
getArticles(page: number, pageSize: number) {
return this.http.get<any>(
`${this.apiUrl}?page=${page}&pageSize=${pageSize}`
);
}
Component
articles: Article[] = [];
page = 1;
pageSize = 10;
totalCount = 0;
loadArticles() {
this.articleService
.getArticles(this.page, this.pageSize)
.subscribe(response => {
this.articles = response.items;
this.totalCount = response.totalCount;
});
}
nextPage() {
this.page++;
this.loadArticles();
}
previousPage() {
if (this.page > 1) {
this.page--;
this.loadArticles();
}
}
Flow
Angular
|
| GET ?page=2&pageSize=10
↓
ASP.NET Core
|
| Count()
| Skip(10)
| Take(10)
↓
SQL Server
|
↓
{
items: [...],
totalCount: 125,
page: 2,
pageSize: 10
}
|
↓
Angular Pagination UI
Interview answer:
I prefer server-side pagination. Angular sends
pageandpageSize, and ASP.NET Core usesSkip()andTake()to retrieve only the required records. The API also returns the total count so Angular can calculate the number of pages.
3. How would you implement search and filtering?
Search and filtering should also normally be performed server-side when dealing with database data.
For example:
GET /api/articles?search=angular&categoryId=5&page=1&pageSize=10
ASP.NET Core
[HttpGet]
public async Task<IActionResult> GetArticles(
string? search,
int? categoryId,
int page = 1,
int pageSize = 10)
{
var query = _context.Articles
.AsNoTracking()
.AsQueryable();
if (!string.IsNullOrWhiteSpace(search))
{
query = query.Where(x =>
x.Title.Contains(search));
}
if (categoryId.HasValue)
{
query = query.Where(x =>
x.CategoryId == categoryId);
}
var totalCount = await query.CountAsync();
var articles = await query
.OrderByDescending(x => x.CreatedOn)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return Ok(new
{
items = articles,
totalCount
});
}
Angular
search = '';
categoryId: number | null = null;
loadArticles() {
let params: any = {
page: this.page,
pageSize: this.pageSize
};
if (this.search) {
params.search = this.search;
}
if (this.categoryId) {
params.categoryId = this.categoryId;
}
this.http.get<any>(
'/api/articles',
{ params }
)
.subscribe(response => {
this.articles = response.items;
this.totalCount = response.totalCount;
});
}
When the filter changes, reset pagination:
onSearch() {
this.page = 1;
this.loadArticles();
}
A common improvement for text search is debouncing:
searchControl.valueChanges
.pipe(
debounceTime(300),
distinctUntilChanged()
)
.subscribe(search => {
this.search = search ?? '';
this.page = 1;
this.loadArticles();
});
This prevents an API call for every keystroke.
User types:
A
An
Ang
Angu
Angular
Without debounce
↓
5 API requests
With debounce
↓
wait ~300ms
↓
1 API request
Interview answer:
I send search and filter values as query-string parameters to ASP.NET Core. The backend dynamically builds an EF Core
IQueryable, applies filters, then pagination. For text boxes, I normally use RxJSdebounceTime()anddistinctUntilChanged()to avoid unnecessary API calls.
4. How would you implement infinite scrolling?
Infinite scrolling is similar to pagination, but instead of displaying Next/Previous buttons, Angular automatically loads the next batch when the user approaches the bottom of the page.
For example:
Initial request
GET /api/articles?page=1&pageSize=10
User scrolls down
GET /api/articles?page=2&pageSize=10
Scrolls again
GET /api/articles?page=3&pageSize=10
ASP.NET Core API
The same pagination endpoint can be reused:
[HttpGet]
public async Task<IActionResult> GetArticles(
int page = 1,
int pageSize = 10)
{
var query = _context.Articles
.AsNoTracking()
.OrderByDescending(x => x.CreatedOn);
var totalCount = await query.CountAsync();
var articles = await query
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return Ok(new
{
items = articles,
totalCount
});
}
The main difference is in Angular.
Angular component
articles: Article[] = [];
page = 1;
pageSize = 10;
loading = false;
hasMore = true;
loadMore() {
if (this.loading || !this.hasMore)
return;
this.loading = true;
this.articleService
.getArticles(this.page, this.pageSize)
.subscribe(response => {
this.articles = [
...this.articles,
...response.items
];
if (response.items.length < this.pageSize) {
this.hasMore = false;
} else {
this.page++;
}
this.loading = false;
});
}
The important difference from normal pagination is this:
Normal pagination
this.articles = response.items;
Existing records are replaced.
Infinite scrolling
this.articles = [
...this.articles,
...response.items
];
New records are appended.
So:
Page 1
[A B C D]
↓ Scroll
Page 2
[E F G H]
Angular array
[A B C D E F G H]
↓ Scroll
Page 3
[I J K L]
Angular array
[A B C D E F G H I J K L]
For the browser-side trigger, IntersectionObserver is preferable to continuously handling raw scroll events.
For example, place a sentinel at the bottom:
@for (article of articles; track article.id) {
<app-article [article]="article" />
}
<div #loadMoreTrigger></div>
When that element enters the viewport:
observer = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) {
this.loadMore();
}
});
Search + filter + infinite scroll
When the user changes the search/filter criteria, reset the accumulated state:
applyFilter() {
this.page = 1;
this.articles = [];
this.hasMore = true;
this.loadMore();
}
Otherwise, results from the old filter can be mixed with the new results.
How all four concepts work together
A production Angular + ASP.NET Core application could use:
Angular
│
├── AuthService
│ └── Signal<User>
│
├── Article Component
│
├── Search
│ └── debounceTime()
│
├── Filters
│ ├── Category
│ └── Tag
│
└── Pagination / Infinite Scroll
│
↓
ASP.NET Core Web API
│
├── Authentication
├── Authorization
├── Search
├── Filtering
├── Sorting
└── Pagination
│
↓
EF Core
│
↓
SQL Server
Key interview points
| Requirement | Recommended approach |
|---|---|
| Share logged-in user | Root service + Signal/Observable |
| Security | Enforce authentication/authorization in ASP.NET Core |
| Pagination | Server-side Skip() + Take() |
| Search | Server-side query |
| Filters | Query-string parameters |
| Search typing | debounceTime() |
| Multiple filters | Build an IQueryable dynamically |
| Infinite scrolling | Reuse paginated API |
| Detect bottom | IntersectionObserver |
| Infinite-scroll results | Append instead of replace |
| New search/filter | Reset page and accumulated results |
| Large datasets | Don't load everything into Angular |
Interview summary
Logged-in user: Store shared client-side user state in a root-level Angular service using Signals or Observables.
Pagination: Send
pageandpageSizeto ASP.NET Core and use EF CoreSkip()andTake().Search/filtering: Pass criteria as query parameters and perform filtering server-side before pagination. Debounce text search in Angular.
Infinite scrolling: Reuse the paginated API, detect when the user reaches the bottom using
IntersectionObserver, request the next page, and append the returned records to the existing collection.