Lançado · em melhoria
Guia de Angular · 4/6
Por enquanto, este capítulo está disponível apenas em inglês.
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 comentários
Fazer login · Faça login para deixar um comentário.
Seja o primeiro a comentar.