realistic, high-quality Angular service example from scratch, based on common real-world needs.

 

๐ŸŽฏ Use Case: UserService

Goal: Create a service that:

  • Fetches user profile from an API

  • Updates the user’s profile

  • Calculates the user's age

  • Caches the user data in a BehaviorSubject for shared use

  • Handles errors cleanly

  • Is testable and easy to maintain


๐Ÿ“ฆ 1. Define the User Model

// models/user.model.ts export interface User { id: string; name: string; email: string; dateOfBirth: string; // ISO date string phone?: string; }

๐Ÿ› ️ 2. Create the UserService

// services/user.service.ts import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { BehaviorSubject, Observable, catchError, map, of, throwError } from 'rxjs'; import { User } from '../models/user.model'; @Injectable({ providedIn: 'root' }) export class UserService { private userSubject = new BehaviorSubject<User | null>(null); public user$ = this.userSubject.asObservable(); private readonly API_URL = '/api/users'; constructor(private http: HttpClient) {} /** * Loads user from backend and updates local state */ loadUser(userId: string): Observable<User> { return this.http.get<User>(`${this.API_URL}/${userId}`).pipe( map(user => { this.userSubject.next(user); return user; }), catchError(error => { console.error('Error loading user', error); return throwError(() => new Error('User load failed')); }) ); } /** * Returns current user snapshot (not observable) */ getCurrentUser(): User | null { return this.userSubject.value; } /** * Updates user and refreshes local cache */ updateUser(user: Partial<User>): Observable<User> { const currentUser = this.getCurrentUser(); if (!currentUser) return throwError(() => new Error('No user loaded')); return this.http.put<User>(`${this.API_URL}/${currentUser.id}`, user).pipe( map(updatedUser => { this.userSubject.next(updatedUser); return updatedUser; }), catchError(error => { console.error('Error updating user', error); return throwError(() => new Error('User update failed')); }) ); } /** * Calculates age from dateOfBirth */ calculateAge(dateOfBirth: string): number { const dob = new Date(dateOfBirth); const today = new Date(); let age = today.getFullYear() - dob.getFullYear(); const m = today.getMonth() - dob.getMonth(); if (m < 0 || (m === 0 && today.getDate() < dob.getDate())) { age--; } return age; } }

๐Ÿ“˜ 3. How a Component Would Use It

// profile.component.ts export class ProfileComponent implements OnInit { user$ = this.userService.user$; age: number | null = null; constructor(private userService: UserService) {} ngOnInit() { this.userService.loadUser('12345').subscribe(user => { this.age = this.userService.calculateAge(user.dateOfBirth); }); } updatePhoneNumber(newPhone: string) { this.userService.updateUser({ phone: newPhone }).subscribe(updated => { console.log('Updated:', updated); }); } }

✅ Why This Is High-Level and Clean

FeatureBenefit
BehaviorSubject cachingState is shareable and reactive across components
✅ Separation of logicAPI, business logic (age calc), and UI are well-separated
✅ Clear, meaningful methodsEasy to understand and self-documenting
✅ Centralized error handlingErrors managed at the service layer, reducing repetition
✅ TestablePure functions (calculateAge) and DI-based logic are easy to unit test

๐Ÿงช 4. Test Example (for calculateAge)

it('should calculate correct age', () => { const service = new UserService({} as any); // mock HttpClient const age = service.calculateAge('2000-01-01'); const thisYear = new Date().getFullYear(); expect(age).toBe(thisYear - 2000); });

๐Ÿ”š Final Thoughts

This service is:

  • Modular

  • Testable

  • Scalable

  • Follows Angular and SOLID best practices 



Advanced UserService with Role-Based Access and Token Auth

We'll build on the original UserService to include:

✅ Features:

  1. JWT token integration (e.g., get token from AuthService)

  2. Role-based logic (isAdmin(), hasRole())

  3. Guard-ready helper methods

  4. Secure user update with token header


๐Ÿ“ Project Structure Update

src/ ├── models/ │ └── user.model.ts ├── services/ │ ├── auth.service.ts ✅ New │ └── user.service.ts

๐Ÿ‘ค Updated User Model with Roles

// models/user.model.ts export interface User { id: string; name: string; email: string; dateOfBirth: string; roles: string[]; // ['user', 'admin', ...] token?: string; // Optional JWT token }

๐Ÿ” AuthService – Manages JWT Token

// services/auth.service.ts import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class AuthService { private readonly TOKEN_KEY = 'auth_token'; setToken(token: string) { localStorage.setItem(this.TOKEN_KEY, token); } getToken(): string | null { return localStorage.getItem(this.TOKEN_KEY); } clearToken() { localStorage.removeItem(this.TOKEN_KEY); } isAuthenticated(): boolean { return !!this.getToken(); } }

๐Ÿš€ Updated Advanced UserService

// services/user.service.ts import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { BehaviorSubject, Observable, catchError, map, throwError } from 'rxjs'; import { User } from '../models/user.model'; import { AuthService } from './auth.service'; @Injectable({ providedIn: 'root' }) export class UserService { private userSubject = new BehaviorSubject<User | null>(null); user$ = this.userSubject.asObservable(); private readonly API_URL = '/api/users'; constructor(private http: HttpClient, private auth: AuthService) {} /** * Load user and set token */ loadUser(userId: string): Observable<User> { const headers = this.getAuthHeaders(); return this.http.get<User>(`${this.API_URL}/${userId}`, { headers }).pipe( map(user => { this.userSubject.next(user); if (user.token) this.auth.setToken(user.token); // Store token if returned return user; }), catchError(error => { console.error('Error loading user', error); return throwError(() => new Error('Failed to load user')); }) ); } getCurrentUser(): User | null { return this.userSubject.value; } updateUser(data: Partial<User>): Observable<User> { const currentUser = this.getCurrentUser(); if (!currentUser) return throwError(() => new Error('No user loaded')); return this.http.put<User>(`${this.API_URL}/${currentUser.id}`, data, { headers: this.getAuthHeaders() }).pipe( map(user => { this.userSubject.next(user); return user; }), catchError(error => { console.error('Error updating user', error); return throwError(() => new Error('Update failed')); }) ); } calculateAge(dob: string): number { const birth = new Date(dob); const today = new Date(); let age = today.getFullYear() - birth.getFullYear(); const m = today.getMonth() - birth.getMonth(); if (m < 0 || (m === 0 && today.getDate() < birth.getDate())) { age--; } return age; } /** * Checks if current user has a specific role */ hasRole(role: string): boolean { const user = this.getCurrentUser(); return !!user?.roles.includes(role); } /** * Shortcut for admin check */ isAdmin(): boolean { return this.hasRole('admin'); } /** * Prepares auth headers using token */ private getAuthHeaders(): HttpHeaders { const token = this.auth.getToken(); return new HttpHeaders({ 'Authorization': `Bearer ${token ?? ''}` }); } }

๐Ÿ”’ Bonus: Can Be Used in Guards or Route Conditions

// auth.guard.ts canActivate(): boolean { return this.userService.isAdmin(); // Only admins allowed }

๐Ÿงช Optional Unit Test Example (hasRole)

it('should return true if user has role', () => { const service = new UserService({} as any, { getToken: () => 'token123' } as any); (service as any).userSubject.next({ id: '1', name: 'Test', email: '', dateOfBirth: '', roles: ['admin', 'user'] }); expect(service.hasRole('admin')).toBeTrue(); expect(service.hasRole('editor')).toBeFalse(); });

✅ Summary: What You Now Have

FeatureBenefit
✅ JWT integrationSecure authenticated requests
✅ Role-based logicCentralized access control helpers
BehaviorSubject stateReactive and shared user state
✅ Error handlingSafer and more maintainable
✅ Easy to test and reuseServices are self-contained, pure, and injectable

⏭️ Want to Go Further?

Here are some next steps if you want:

  1. ✅ Add refresh token and token expiration logic

  2. ✅ Protect routes using CanActivate guards with role checks

  3. ✅ Store state in NgRx or SignalStore (for larger apps)

  4. ✅ Add interceptors to automatically inject the token into all HTTP calls

  5. ✅ Implement logout flow


Let me know if you want help with any of those next steps — I can create examples too.


Great — let’s continue by adding the next advanced features one by one. Since you said yes, here’s what we’ll build next:


๐Ÿ”„ Next Advanced Features:

  1. Token Auto-Injection using HTTP Interceptor

  2. Role-Based Route Protection using AuthGuard with CanActivate

  3. Logout and Session Expiry Handling

  4. ✅ (Optional) Token Refresh Flow (JWT refresh tokens)

Let’s tackle each part cleanly and step-by-step.


๐Ÿ” 1. HTTP Interceptor – Auto-Add JWT to All Requests

✅ What It Does:

  • Automatically attaches Authorization: Bearer <token> header to every HTTP request

  • Centralizes token logic instead of adding headers manually in each service


๐Ÿ“ฆ Create AuthInterceptor

// interceptors/auth.interceptor.ts import { Injectable } from '@angular/core'; import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http'; import { Observable } from 'rxjs'; import { AuthService } from '../services/auth.service'; @Injectable() export class AuthInterceptor implements HttpInterceptor { constructor(private auth: AuthService) {} intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { const token = this.auth.getToken(); if (token) { const cloned = req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }); return next.handle(cloned); } return next.handle(req); } }

๐Ÿ”— Register Interceptor in App Module

// app.module.ts import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { AuthInterceptor } from './interceptors/auth.interceptor'; @NgModule({ providers: [ { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true } ] }) export class AppModule {}

✅ Now all services (e.g., UserService) no longer need to manually set headers.


๐Ÿ›ก️ 2. AuthGuard – Protect Routes by Role

✅ Goal:

Allow/deny access based on user role (like 'admin', 'editor', etc.)


๐Ÿ“ฆ Create AuthGuard

// guards/auth.guard.ts import { Injectable } from '@angular/core'; import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router'; import { UserService } from '../services/user.service'; @Injectable({ providedIn: 'root' }) export class AuthGuard implements CanActivate { constructor(private userService: UserService, private router: Router) {} canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { const requiredRole = route.data['role'] as string; if (!this.userService.getCurrentUser()) { this.router.navigate(['/login']); return false; } if (requiredRole && !this.userService.hasRole(requiredRole)) { this.router.navigate(['/unauthorized']); return false; } return true; } }

๐Ÿงญ Usage in Routes

// app-routing.module.ts const routes: Routes = [ { path: 'admin', component: AdminPanelComponent, canActivate: [AuthGuard], data: { role: 'admin' } // only admin can access }, { path: 'profile', component: ProfileComponent, canActivate: [AuthGuard] } ];

๐Ÿšช 3. Logout and Session Expiry Handling

✅ AuthService: Add logout logic

logout() { this.clearToken(); // Optionally clear other data like BehaviorSubjects }

✅ UserService: Add logout and cleanup

logout() { this.auth.clearToken(); this.userSubject.next(null); }

⛔ Handle Token Expiry (Basic Way)

Inside AuthInterceptor, detect expired token (e.g., 401 error) and redirect:

import { catchError } from 'rxjs/operators'; import { of, throwError } from 'rxjs'; intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { const token = this.auth.getToken(); const cloned = token ? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }) : req; return next.handle(cloned).pipe( catchError(err => { if (err.status === 401) { this.auth.clearToken(); this.router.navigate(['/login']); } return throwError(() => err); }) ); }

๐Ÿ” 4. (Optional) Refresh Token Flow

If your backend returns:

  • an access token (short-lived)

  • and a refresh token (longer-lived),

Then you can:

  • store the refresh token securely (e.g., HttpOnly cookie)

  • and use a token refresh endpoint (/auth/refresh) when access token expires

Let me know if you want me to implement that too — it’s more advanced and uses token queuing logic to prevent race conditions.


✅ Summary of What You Now Have

FeatureDescription
✅ Auth InterceptorAdds token to all HTTP requests automatically
✅ Auth GuardProtects routes based on login + roles
✅ Role-based accessEasy to add .hasRole('admin') logic
✅ Logout and cleanupCentralized logout + session cleanup
✅ Token Expiry handlingDetect 401s, auto-redirect to login

⏭️ Want to Add Next?

Here are options you can choose:

  1. ๐Ÿ”„ Implement refresh token flow

  2. ๐Ÿงช Write unit tests for the guards/interceptor

  3. ๐Ÿ’ก Show Signal-based global state instead of BehaviorSubject

  4. ๐Ÿงฐ Build a TokenService to manage both access + refresh cleanly

Let me know what you'd like next — or if you'd prefer a GitHub project template version of all this.


Perfect — since you've said yes, let's now implement a full Refresh Token Flow (step 4), which is a more advanced and realistic solution for production-level Angular apps that use JWT authentication.


๐Ÿ”„ What Is the Refresh Token Flow?

In modern JWT-based authentication:

  • ๐Ÿ” Access Token: short-lived (e.g. 5–15 mins)

  • ♻️ Refresh Token: long-lived (e.g. 1–7 days), used to get a new access token when it expires

✅ Goal:

Automatically refresh the access token when it expires, without logging the user out or requiring a manual login.


๐Ÿงฑ What We'll Build:

  1. TokenService: Stores and manages access/refresh tokens

  2. ✅ Updated AuthService: Handles login, logout, and token refresh

  3. AuthInterceptor:

    • Adds access token to all requests

    • Detects 401 errors

    • Refreshes token and retries request

  4. ✅ Backend expected endpoints:

    • POST /auth/login

    • POST /auth/refresh with refresh token

    • POST /auth/logout


1. ๐Ÿงฐ TokenService

// services/token.service.ts import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class TokenService { private ACCESS = 'access_token'; private REFRESH = 'refresh_token'; setTokens(accessToken: string, refreshToken: string) { localStorage.setItem(this.ACCESS, accessToken); localStorage.setItem(this.REFRESH, refreshToken); } getAccessToken(): string | null { return localStorage.getItem(this.ACCESS); } getRefreshToken(): string | null { return localStorage.getItem(this.REFRESH); } clearTokens() { localStorage.removeItem(this.ACCESS); localStorage.removeItem(this.REFRESH); } }

2. ๐Ÿ” AuthService

// services/auth.service.ts import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { TokenService } from './token.service'; import { Observable, tap } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class AuthService { private readonly API = '/api/auth'; constructor(private http: HttpClient, private tokenService: TokenService) {} login(email: string, password: string): Observable<any> { return this.http.post<any>(`${this.API}/login`, { email, password }).pipe( tap(tokens => { this.tokenService.setTokens(tokens.accessToken, tokens.refreshToken); }) ); } logout(): Observable<any> { return this.http.post(`${this.API}/logout`, {}).pipe( tap(() => this.tokenService.clearTokens()) ); } refreshToken(): Observable<any> { const refreshToken = this.tokenService.getRefreshToken(); return this.http.post<any>(`${this.API}/refresh`, { refreshToken }).pipe( tap(tokens => { this.tokenService.setTokens(tokens.accessToken, tokens.refreshToken); }) ); } isLoggedIn(): boolean { return !!this.tokenService.getAccessToken(); } }

3. ๐Ÿš€ AuthInterceptor with Token Refresh Logic

// interceptors/auth.interceptor.ts import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable, catchError, filter, switchMap, take, throwError, BehaviorSubject } from 'rxjs'; import { AuthService } from '../services/auth.service'; import { TokenService } from '../services/token.service'; @Injectable() export class AuthInterceptor implements HttpInterceptor { private isRefreshing = false; private refreshTokenSubject = new BehaviorSubject<string | null>(null); constructor(private auth: AuthService, private tokenService: TokenService) {} intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { const token = this.tokenService.getAccessToken(); const cloned = token ? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }) : req; return next.handle(cloned).pipe( catchError((error: HttpErrorResponse) => { if (error.status === 401 && !cloned.url.endsWith('/auth/refresh')) { return this.handle401Error(cloned, next); } return throwError(() => error); }) ); } private handle401Error(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { if (!this.isRefreshing) { this.isRefreshing = true; this.refreshTokenSubject.next(null); return this.auth.refreshToken().pipe( switchMap((tokens) => { this.isRefreshing = false; this.refreshTokenSubject.next(tokens.accessToken); return next.handle(this.addToken(req, tokens.accessToken)); }), catchError((err) => { this.isRefreshing = false; this.tokenService.clearTokens(); return throwError(() => err); }) ); } else { return this.refreshTokenSubject.pipe( filter(token => token != null), take(1), switchMap(token => next.handle(this.addToken(req, token!))) ); } } private addToken(req: HttpRequest<any>, token: string): HttpRequest<any> { return req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }); } }

4. ✅ Register Interceptor in AppModule

// app.module.ts import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { AuthInterceptor } from './interceptors/auth.interceptor'; @NgModule({ providers: [ { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true } ] }) export class AppModule {}

๐Ÿ’ก What Backend Should Support

Your API should expose:

EndpointPurpose
POST /auth/loginAuthenticates user, returns tokens
POST /auth/refreshAccepts refresh token, returns new access token
POST /auth/logoutInvalidates refresh token (optional)

✅ Final Result

  • ๐Ÿ’ก Access tokens are auto-added to all requests

  • ♻️ Expired tokens are auto-refreshed behind the scenes

  • ๐Ÿ›ก️ User stays logged in until refresh token expires

  • ๐Ÿง  No need to handle token logic in components or services


K

Angular app is taking time to load a list of data — for example, from an API call — you can enhance the user experience by showing a loading spinner Example: Show Loading Spinner While Fetching Data

 

Component HTML (e.g. list.component.html)

<!-- Loading Spinner --> <div class="text-center my-4" *ngIf="isLoading"> <div class="spinner-border text-primary" role="status"> <span class="visually-hidden">Loading...</span> </div> </div> <!-- Data List --> <ul class="list-group" *ngIf="!isLoading"> <li class="list-group-item" *ngFor="let item of items"> {{ item.name }} </li> </ul>


Explanation:

*ngIf="isLoading": Only shows the spinner when isLoading is true.

text-center: Centers the spinner.

my-4: Adds vertical margin.

Make sure in your component:

isLoading = true; // Set to false after

SQL Server auto generate UNIQUEIDENTIFIER

 Insert data into a table

✅ Have SQL Server auto-generate both:

  • Id (as INT IDENTITY)

  • RowGuid (as UNIQUEIDENTIFIER, using NEWID() — not Guid.NewGuid() from C#)
    ✅ Insert via stored procedure
    ✅ Return the newly inserted row (including Id and RowGuid) to your .NET Core API


✅ Full SQL Server Setup

๐Ÿ”น 1. Create Table (with auto-generated RowGuid)

CREATE TABLE Items ( Id INT IDENTITY(1,1) PRIMARY KEY, RowGuid UNIQUEIDENTIFIER NOT NULL DEFAULT NEWID(), Name NVARCHAR(100), CreatedAt DATETIME NOT NULL DEFAULT GETUTCDATE() );

๐Ÿง  Explanation:

ColumnDescription
IdAuto-increment primary key
RowGuidAuto-generated using SQL Server's NEWID()
CreatedAtSet automatically to current UTC time

๐Ÿ”น 2. Create Stored Procedure (only passes @Name)

CREATE PROCEDURE InsertItem @Name NVARCHAR(100) AS BEGIN SET NOCOUNT ON; INSERT INTO Items (Name) VALUES (@Name); -- Return the inserted row using SCOPE_IDENTITY() SELECT * FROM Items WHERE Id = SCOPE_IDENTITY(); END

๐Ÿง  Why SCOPE_IDENTITY()?

It returns the Id of the row just inserted, safely and accurately.


✅ .NET Core API Integration

๐Ÿ”น 1. DTO (for request)

public class ItemDto { public string Name { get; set; } }

๐Ÿ”น 2. Entity (for mapping result)

public class Item { public int Id { get; set; } public Guid RowGuid { get; set; } public string Name { get; set; } public DateTime CreatedAt { get; set; } }

๐Ÿ”น 3. API Controller Method

[HttpPost("create")] public IActionResult CreateItem([FromBody] ItemDto dto) { var result = _context.Items .FromSqlRaw("EXEC InsertItem @p0", dto.Name) .AsEnumerable() .FirstOrDefault(); return Ok(result); }

This uses FromSqlRaw() to call the stored procedure and fetch the inserted row.


✅ Example Request

POST /api/items/create Content-Type: application/json { "name": "My First Item" }

✅ Example Response

{ "id": 42, "rowGuid": "ebf679a1-9dc5-4f0e-b0a2-45c97a2a7c77", "name": "My First Item", "createdAt": "2025-09-26T16:00:00Z" }

✅ Benefits of This Approach

FeatureWhy it's good
No Guid.NewGuid() in C#You let SQL Server handle it (DEFAULT NEWID())
Clean APIClient just sends the name
Secure and consistentRowGuid is unguessable, great for future updates
Lightweight SPOnly handles what it needs: name and insert logic

Let me know if you'd also like to:

  • Use NEWSEQUENTIALID() for better index performance

  • Add update/delete stored procedures

  • Generate SQL script from EF Core (.HasDefaultValueSql("NEWID()"))

JavaScript + Angular-compatible version of loan amortization calculator that you can integrate into an Angular component or service

  JavaScript Version of Loan Amortization 1. Loan Calculator Function (Pure JS/TS) export function calculateLoanSchedule ( principal:...

Best for you