🔥 Your ₹599 Course Awaits — Grab It Before It’s Gone!

Join thousands of learners building their careers with Prime Inspire. Your exclusive ₹599 coupon is just one click away.

Subscription Form

How to Call API in Angular 20+

How to Call API in Angular 20+

Learning how to call api in angular is simpler than it looks—especially in Angular 20, where new projects default to the standalone style and the root component lives in app.ts, not app.component.ts. In this guide, we’ll wire up HTTP the Angular‑20 way: configure providers in app.config.ts, build a typed data service, add interceptors, handle errors, and write proper tests. Every step reflects the current Angular docs and file layout.

Angular 20 favors a lean setup: no NgModule is required, you bootstrap the app with app.ts as the root component and attach framework services via application providers. That includes HTTP—now added with provideHttpClient() in your app.config.ts, instead of importing HttpClientModule in a module. This keeps your app light, explicit, and easy to evolve as features grow. (Angular)

We’ll proceed in small, solid steps. You’ll see how to organize code, keep responses strongly typed, display loading/empty/error states, and centralize headers/tokens via interceptors. By the end, you’ll have a pattern you can drop into any v20 app, aligned with Angular’s official guidance on the new app.ts root and HTTP configuration. (Angular)

1) Project shape in Angular 20: app.ts, not app.component.ts

In Angular v20 tutorials and starters, the root component class is defined in src/app/app.ts, using the selector app-root. That’s the top-level component your app bootstraps—think of it as the shell where you’ll nest routes, headers, and feature areas. This naming shift is intentional in v20 learning materials and examples. (Angular)

If you’ve used older guides, you’ll recall app.component.ts. Functionally it’s the same concept—a root component—but the current docs and samples show app.ts for clarity and brevity. When you open v20 tutorials, you’ll frequently see instructions like “Open app.ts and…”, confirming that’s the canonical filename now. (Angular)

Your app still has an application configuration file—app.config.ts—where you register framework‑level providers (router, HTTP, etc.). We’ll use that to enable HTTP globally with one line, which is cleaner than importing a module all over the place. (Angular)

2) Enable HttpClient the v20 way (application providers)

Angular 20 provides HttpClient via provideHttpClient(), added to the providers array in app.config.ts. This is the recommended path for standalone apps; it replaces the older HttpClientModule import most of us memorized. Here’s the minimal setup (additions emphasized): (Angular)

// src/app/app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient, withFetch } from '@angular/common/http';
// (Optionally: provideRouter(...) goes here)

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(withFetch()), // enable HttpClient + fetch backend (good for SSR)
  ],
};

Code language: JavaScript (javascript)

This single change makes HttpClient injectable anywhere in your app. You can also chain features here, like withInterceptors(...) for global auth headers or logging. Angular’s API page explicitly documents provideHttpClient() and its feature helpers—including withFetch() and interceptors—so you’re following the official pattern. (Angular)

If you forget this provider, you’ll hit a NullInjectorError: No provider for HttpClient. The fix is exactly what you see above—add provideHttpClient() to app.config.ts. This is also the accepted solution discussed across community Q&As for standalone apps. (Stack Overflow)

3) Create a typed data service (clean separation)

Make a dedicated service so components stay simple. Generate a feature folder and service (file names may be suffix‑less in v20 scaffolds; that’s fine). The service owns your endpoints and types, while components just consume Observables.

// src/app/core/services/todos.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

export interface Todo {
  id: number;
  title: string;
  completed: boolean;
}

@Injectable({ providedIn: 'root' })
export class TodosService {
  private baseUrl = 'https://jsonplaceholder.typicode.com';

  constructor(private http: HttpClient) {}

  getTodos(): Observable<Todo[]> {
    return this.http.get<Todo[]>(`${this.baseUrl}/todos`);
  }
}

Code language: JavaScript (javascript)

Strong typing is more than editor sugar: it prevents shape mismatches at compile time and documents your API contract for future contributors. Angular’s HttpClient returns Observables for all verbs (GET/POST/PUT/PATCH/DELETE), which play nicely with the template async pipe and RxJS operators you’ll use for loading and error states. (Angular)

Keep models in a models/ folder if multiple services share them. For larger backends, consider generating types from an OpenAPI spec, reducing drift between client and server. This is optional, but it scales.

4) Display data in a component (standalone, async pipe)

Generate a feature component (e.g., features/todo-list). In Angular 20, components are standalone by default; you declare imports on the component itself. Expose an observable property; let the template use async to subscribe and clean up automatically.

// src/app/features/todo-list/todo-list.ts
import { Component } from '@angular/core';
import { NgIf, NgFor } from '@angular/common';
import { Observable } from 'rxjs';
import { TodosService, Todo } from '../../core/services/todos.service';

@Component({
  selector: 'app-todo-list',
  standalone: true,
  imports: [NgIf, NgFor],
  template: `
    <section class="container">
      <h2>Todos</h2>
      <ul *ngIf="todos$ | async as todos; else loading">
        <li *ngFor="let t of todos">
          <strong>#{{ t.id }}</strong> — {{ t.title }}
          <span *ngIf="t.completed"></span>
          <span *ngIf="!t.completed"></span>
        </li>
      </ul>
      <ng-template #loading><p>Loading todos…</p></ng-template>
    </section>
  `,
})
export class TodoList {
  todos$!: Observable<Todo[]>;
  constructor(private todos: TodosService) {
    this.todos$ = this.todos.getTodos();
  }
}

Code language: HTML, XML (xml)

Mount the feature in your root app.ts so it appears on screen. The official v20 tutorials repeatedly reference editing app.ts to compose your UI—follow that pattern and keep app.ts a thin shell. (Angular)

// src/app/app.ts
import { Component } from '@angular/core';
import { TodoList } from './features/todo-list/todo-list';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TodoList],
  template: `
    <main class="container">
      <h1>HTTP Demo</h1>
      <app-todo-list />
    </main>
  `,
})
export class App {}

Code language: JavaScript (javascript)

5) Real‑world UX: loading, empty, and error states

Users need feedback while data loads and helpful messages if something fails. Enhance the component with catchError, finalize, and a friendly empty state. These operators come from RxJS and work naturally with HttpClient’s Observables. (Angular)

// src/app/features/todo-list/todo-list.ts (excerpt)
import { catchError, finalize, of, tap } from 'rxjs';

export class TodoList {
  todos$ = of<Todo[]>([]);
  loading = true;
  error: string | null = null;

  constructor(private todos: TodosService) {
    this.todos$ = this.todos.getTodos().pipe(
      tap(() => (this.error = null)),
      catchError(() => {
        this.error = 'Failed to load todos. Please retry.';
        return of([] as Todo[]);
      }),
      finalize(() => (this.loading = false))
    );
  }
}

Code language: JavaScript (javascript)

Keep all four states obvious: loading, success, empty, and error. In templates, gate each block with small *ngIfs. This small investment prevents “blank screen” confusion and reduces support pings.

For larger lists, consider skeleton loaders (CSS only) and @defer for progressive templating. These are additive and don’t change your HTTP code.

6) CRUD: POST, PATCH, DELETE with types

Most apps need to create, update, and delete. Extend the service with typed methods. Keep request bodies and responses strictly typed, so templates and calling code get autocomplete and compile‑time checks.

// src/app/core/services/todos.service.ts (more endpoints)
createTodo(payload: Omit<Todo, 'id'>) {
  return this.http.post<Todo>(`${this.baseUrl}/todos`, payload);
}

updateTodo(id: number, changes: Partial<Todo>) {
  return this.http.patch<Todo>(`${this.baseUrl}/todos/${id}`, changes);
}

deleteTodo(id: number) {
  return this.http.delete<void>(`${this.baseUrl}/todos/${id}`);
}

Code language: JavaScript (javascript)

Wire these into form submit handlers or button clicks in your component. For optimistic UI, update the list immediately and roll back on failure; for pessimistic UI, wait for the server response before updating. Choose based on your consistency requirements.

If your backend uses PUT, adjust accordingly. Angular’s HttpClient offers methods for all standard verbs—your ergonomics stay the same across endpoints. (Angular)

7) Environment‑based base URLs (dev/stage/prod)

Don’t hardcode URLs. Use Angular’s environment configuration files so each build points to the right backend. While some docs still show examples with app.component.ts, the concept is identical for app.ts apps—you import from environments/environment and read values at runtime. (Angular, Angular)

Create or edit environment files:

// src/environments/environment.ts
export const environment = {
  production: false,
  apiUrl: 'http://localhost:3000/api'
};

// src/environments/environment.prod.ts
export const environment = {
  production: true,
  apiUrl: 'https://api.yourapp.com'
};

Code language: JavaScript (javascript)

Use it in your service:

import { environment } from '../../../environments/environment';
private baseUrl = environment.apiUrl;

Code language: JavaScript (javascript)

This keeps configuration out of code and lets CI/CD swap endpoints per target. For secrets, prefer server‑side storage or runtime injection, not checked‑in files.

8) Interceptors (tokens, headers, global behavior) with provideHttpClient

With standalone HTTP, you attach interceptors via features on provideHttpClient(). This centralizes auth headers, request logging, or error mapping—no component code changes needed. The API docs show withInterceptors(...) and related helpers. (Angular)

// src/app/app.config.ts (excerpt)
import { provideHttpClient, withInterceptors } from '@angular/common/http';

function authInterceptor(req: Request, next: any) {
  const token = localStorage.getItem('access_token');
  const headers = token ? { Authorization: `Bearer ${token}` } : {};
  return next(req.clone({ headers: new Headers(headers) }));
}

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(
      withInterceptors([authInterceptor])
    ),
  ],
};

Code language: JavaScript (javascript)

Functional interceptors are lightweight and tree‑shakeable. You can also add withFetch() (shown earlier) to improve SSR performance and standards alignment. Keep cross‑cutting concerns here so features stay focused on their domain. (Angular)

9) Resilience: timeouts, retry/backoff

Networks fail. Add resilience with RxJS operators: a conservative timeout, a couple of retries, and a short delay. Apply per‑call or wrap inside your service for reuse.

// inside TodosService
import { timeout, retryWhen, scan, delay } from 'rxjs/operators';

getTodos() {
  return this.http.get<Todo[]>(`${this.baseUrl}/todos`).pipe(
    timeout(8000),
    retryWhen(errors =>
      errors.pipe(
        scan((count, err) => {
          if (count >= 2) throw err;
          return count + 1;
        }, 0),
        delay(1000)
      )
    )
  );
}

Code language: JavaScript (javascript)

Use retries carefully for idempotent operations like GET; avoid blindly retrying POST/PUT without server idempotency guarantees. Log failures so you can spot flaky endpoints early in development and production.

If your app supports offline usage, integrate caching (e.g., IndexedDB) or queue mutations for later sync. That’s out of scope here but fits neatly on top of these patterns.

10) CORS + dev proxy (when you don’t control the server)

If you hit CORS errors, the best fix is server‑side allowlists. When you can’t change the server, route calls through Angular’s dev proxy during local development. Map a local path like /api to the remote host; your browser now calls same‑origin, and the dev server forwards the request. (This doesn’t change production—you’ll handle that at your backend or gateway.) (Angular)

Create proxy.conf.json:

{
  "/api": {
    "target": "https://thirdparty.example.com",
    "secure": true,
    "changeOrigin": true,
    "pathRewrite": { "^/api": "" }
  }
}

Code language: JSON / JSON with Comments (json)

Then run the dev server with the proxy file:

// package.json (scripts)
"start": "ng serve --proxy-config proxy.conf.json"

Code language: JavaScript (javascript)

Point your service base URL at /api. You get a clean dev experience without changing your production networking story.

11) Testing HttpClient in standalone apps

Testing is first‑class: use provideHttpClient() and provideHttpClientTesting() in your TestBed. This replaces the older HttpClientTestingModule pattern and gives you HttpTestingController to assert requests and flush mock responses. (Angular)

// src/app/core/services/todos.service.spec.ts
import { TestBed } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting, HttpTestingController } from '@angular/common/http/testing';
import { TodosService, Todo } from './todos.service';

describe('TodosService', () => {
  let service: TodosService;
  let httpMock: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [TodosService, provideHttpClient(), provideHttpClientTesting()],
    });
    service = TestBed.inject(TodosService);
    httpMock = TestBed.inject(HttpTestingController);
  });

  it('GETs todos', () => {
    const mock: Todo[] = [{ id: 1, title: 'Test', completed: false }];

    service.getTodos().subscribe(data => {
      expect(data[0].title).toBe('Test');
    });

    const req = httpMock.expectOne('https://jsonplaceholder.typicode.com/todos');
    expect(req.request.method).toBe('GET');
    req.flush(mock);
    httpMock.verify();
  });
});

Code language: JavaScript (javascript)

This keeps your tests pure and fast—no network is touched. Write one test per endpoint to lock in URLs, methods, and payload shapes, and you’ll catch regressions early.

12) Quick checklist (Angular 20, standalone)

Add provideHttpClient() in app.config.ts (optionally with withFetch() and withInterceptors(...)). Confirm your root lives in app.ts and keep it a thin shell that composes features. Build one service per resource, return typed Observables, and consume via async pipes in standalone components. (Angular)

Show proper UI states: loading, error, and empty. Move tokens/headers into a functional interceptor attached in app.config.ts. Use environment files for apiUrl per build target. Add a modest timeout and retry strategy for flaky networks, and test each endpoint with provideHttpClientTesting() and HttpTestingController. (Angular, Angular)

With this pattern, you can scale features without sprinkling HTTP details across components. The result is a maintainable, idiomatic Angular 20 codebase that’s easy to read, test, and change.

Sources you can trust (Angular 20 docs)

  • Root app.ts in tutorials: Angular’s v20 tutorial pages reference editing app.ts for the root component / routing. (Angular)
  • Enable HttpClient with providers: Official docs show provideHttpClient() in app.config.ts for standalone apps. (Angular)
  • provideHttpClient() API + features: Angular API reference (interceptors, withFetch, etc.). (Angular)
  • Make HTTP requests: Overview of verbs and Observables in HttpClient. (Angular)
  • Testing: provideHttpClientTesting() + HttpTestingController in standalone tests. (Angular)

Discover more from Prime Inspire

Subscribe to get the latest posts sent to your email.

We’d love to hear your thoughts! Share your ideas below 💡

Scroll to Top

Discover more from Prime Inspire

Subscribe now to keep reading and get access to the full archive.

Continue reading