リリース・改善中
Angular ガイド · 4/6
この章は現在、英語でのみ提供しています。
Most Angular apps load data over HTTP, keep some state on the client and collect input through forms. This chapter covers HttpClient, signal-based state, RxJS basics and reactive forms.
Register HttpClient once in app.config.ts. withFetch() makes it use the browser's Fetch API, which is recommended when you use server-side rendering:
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient, withFetch } from '@angular/common/http';
export const appConfig: ApplicationConfig = {
providers: [provideHttpClient(withFetch())],
};Keep HTTP calls in a service. HttpClient methods return Observables that send the request on subscribe and emit the parsed JSON:
import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs';
export interface Todo {
id: number;
title: string;
completed: boolean;
}
@Injectable({ providedIn: 'root' })
export class TodoApi {
private readonly http = inject(HttpClient);
private readonly baseUrl = 'https://jsonplaceholder.typicode.com/todos';
list(limit = 10): Observable<Todo[]> {
const params = new HttpParams().set('_limit', limit);
return this.http.get<Todo[]>(this.baseUrl, { params });
}
create(title: string): Observable<Todo> {
return this.http.post<Todo>(this.baseUrl, { title, completed: false });
}
}The type argument in get<Todo[]> informs TypeScript only; the response is not validated at runtime.
toSignal() subscribes to an Observable, exposes the latest value as a signal and unsubscribes when the component is destroyed:
import { Component, inject } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { TodoApi } from './todo-api';
@Component({
selector: 'app-todo-list',
template: `
@for (todo of todos(); track todo.id) {
<p>{{ todo.title }}</p>
} @empty {
<p>Loading...</p>
}
`,
})
export class TodoList {
private readonly api = inject(TodoApi);
protected readonly todos = toSignal(this.api.list(), { initialValue: [] });
}For state that several components share, such as a cart, a service holding signals is often enough. Components read the exposed signals; only the service changes them:
import { Injectable, computed, signal } from '@angular/core';
export interface CartItem { id: number; price: number; qty: number; }
@Injectable({ providedIn: 'root' })
export class CartStore {
private readonly items = signal<CartItem[]>([]);
readonly all = this.items.asReadonly();
readonly total = computed(() => this.items().reduce((sum, item) => sum + item.price * item.qty, 0));
add(item: CartItem) {
this.items.update((list) => [...list, item]);
}
}Larger apps sometimes adopt a library such as NgRx, but signal-based services go a long way first.
RxJS is still central to HttpClient, the router and forms. debounceTime waits for a pause, distinctUntilChanged skips repeats, switchMap cancels the previous request when a new value arrives, and catchError turns failures into a fallback. takeUntilDestroyed() ends a manual subscription when the component is destroyed:
import { Component, inject, signal } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { HttpClient } from '@angular/common/http';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { catchError, debounceTime, distinctUntilChanged, of, switchMap } from 'rxjs';
@Component({
selector: 'app-search',
imports: [ReactiveFormsModule],
template: `
<input [formControl]="query" placeholder="Search" />
@for (name of results(); track name) {
<p>{{ name }}</p>
}
`,
})
export class Search {
private readonly http = inject(HttpClient);
protected readonly query = new FormControl('', { nonNullable: true });
protected readonly results = signal<string[]>([]);
constructor() {
this.query.valueChanges.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap((q) => this.http.get<string[]>('/api/search', { params: { q } }).pipe(catchError(() => of([])))),
takeUntilDestroyed(),
).subscribe((list) => this.results.set(list));
}
}Reactive forms define the form model in TypeScript, with typed controls and validators. The template binds to it with formGroup and formControlName:
import { Component, inject } from '@angular/core';
import { NonNullableFormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
@Component({
selector: 'app-signup',
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="form" (ngSubmit)="submit()">
<input type="email" formControlName="email" />
@if (form.controls.email.invalid && form.controls.email.touched) {
<small>Enter a valid email address.</small>
}
<input type="password" formControlName="password" />
<button type="submit" [disabled]="form.invalid">Sign up</button>
</form>
`,
})
export class Signup {
private readonly fb = inject(NonNullableFormBuilder);
protected readonly form = this.fb.group({
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required, Validators.minLength(8)]],
});
submit() {
if (this.form.valid) console.log(this.form.getRawValue());
}
}provideHttpClient() once and call APIs from services that return Observables.toSignal() turns an Observable into a signal for the template.computed() values make simple, shared stores.switchMap, debounceTime, catchError and takeUntilDestroyed() cover common async cases.
コメント 0件
ログイン · ログインするとコメントできます。
最初のコメントを書いてみましょう。