Estado e Serviços
O Angular usa Injeção de Dependência para compartilhar serviços entre componentes. O React usa Context API e hooks. O conceito é similar — compartilhar estado e lógica — mas a implementação é diferente.
Estado local: propriedade da classe vs. useState
Angularcounter.component.ts
// Estado local no Angular
@Component({
template: `
<p>Contagem: {{ count }}</p>
<button (click)="increment()">+</button>
`
})
export class CounterComponent {
count = 0; // propriedade da classe
increment() {
this.count++;
}
}Next.jsCounter.tsx
"use client";
import { useState } from "react";
export default function Counter() {
// useState — equivale à propriedade da classe
const [count, setCount] = useState(0);
function increment() {
setCount((prev) => prev + 1);
}
return (
<div>
<p>Contagem: {count}</p>
<button onClick={increment}>+</button>
</div>
);
}Signals vs. useState / useMemo
Os Signals do Angular têm equivalentes diretos nos hooks do React:
Angularcomponent.ts (Angular 17+)
// Angular Signals (Angular 17+)
@Component({ ... })
export class Component {
count = signal(0);
doubled = computed(() => this.count() * 2);
increment() {
this.count.update((v) => v + 1);
// ou: this.count.set(this.count() + 1);
}
}Next.jsComponent.tsx
"use client";
import { useState, useMemo } from "react";
export default function Component() {
// useState ≈ signal()
const [count, setCount] = useState(0);
// useMemo ≈ computed()
const doubled = useMemo(() => count * 2, [count]);
function increment() {
setCount((v) => v + 1);
}
return <div>{count} (dobro: {doubled})</div>;
}Serviços compartilhados vs. React Context
Um @Injectable({ providedIn: 'root' }) no Angular cria um singleton acessível em toda a aplicação. No React, você replica esse padrão com createContext, um Provider e um hook customizado:
Angularcart.service.ts + cart.component.ts
// Serviço Angular com Signals (Angular 17+)
@Injectable({ providedIn: 'root' })
export class CartService {
// Estado interno com Signal
private _items = signal<CartItem[]>([]);
// Estado público (readonly)
readonly items = this._items.asReadonly();
readonly total = computed(() =>
this._items().reduce((sum, i) => sum + i.price, 0)
);
add(item: CartItem) {
this._items.update((prev) => [...prev, item]);
}
remove(id: string) {
this._items.update((prev) =>
prev.filter((i) => i.id !== id)
);
}
}
// Uso no componente (injeção de dependência)
@Component({ ... })
export class CartComponent {
cart = inject(CartService);
}Next.jsCartContext.tsx
// Context + Provider — equivalente ao @Injectable service
"use client";
import { createContext, useContext, useState } from "react";
type CartItem = { id: string; name: string; price: number };
type CartCtx = {
items: CartItem[];
total: number;
add: (item: CartItem) => void;
remove: (id: string) => void;
};
const CartContext = createContext<CartCtx | null>(null);
// Provider — equivale ao providedIn: 'root'
export function CartProvider({ children }: { children: React.ReactNode }) {
const [items, setItems] = useState<CartItem[]>([]);
const total = items.reduce((sum, i) => sum + i.price, 0);
function add(item: CartItem) {
setItems((prev) => [...prev, item]);
}
function remove(id: string) {
setItems((prev) => prev.filter((i) => i.id !== id));
}
return (
<CartContext.Provider value={{ items, total, add, remove }}>
{children}
</CartContext.Provider>
);
}
// Hook — equivale a inject(CartService)
export function useCart() {
const ctx = useContext(CartContext);
if (!ctx) throw new Error("useCart fora do CartProvider");
return ctx;
}
// Uso no componente
export default function CartButton() {
const { items, add } = useCart();
return <button onClick={() => add(newItem)}>{items.length}</button>;
}ℹ
No Angular, o DI framework instancia e gerencia os serviços automaticamente. No React, você gerencia a inicialização e o escopo do estado manualmente via Provider. É mais verboso, mas também mais explícito.
Estado global com Zustand
Para estados globais complexos (equivalente ao NgRx), a biblioteca mais popular no ecossistema React/Next.js é o Zustand. É muito mais simples que o NgRx:
store/cart.ts
// Zustand — para estado global mais complexo
// npm install zustand
import { create } from "zustand";
type CartStore = {
items: CartItem[];
add: (item: CartItem) => void;
remove: (id: string) => void;
};
export const useCartStore = create<CartStore>((set) => ({
items: [],
add: (item) =>
set((state) => ({ items: [...state.items, item] })),
remove: (id) =>
set((state) => ({
items: state.items.filter((i) => i.id !== id),
})),
}));
// Uso — sem Provider necessário
export default function Cart() {
const { items, remove } = useCartStore();
return (
<ul>
{items.map((i) => (
<li key={i.id}>
{i.name}
<button onClick={() => remove(i.id)}>X</button>
</li>
))}
</ul>
);
}Tabela de equivalências
| Angular | React / Next.js | Uso |
|---|---|---|
| signal(value) | useState(value) | Estado local reativo |
| computed(() => expr) | useMemo(() => expr, [deps]) | Valor derivado |
| effect(() => {...}) | useEffect(() => {...}, [deps]) | Efeitos colaterais |
| @Injectable service | React Context + Provider | Estado compartilhado |
| BehaviorSubject (RxJS) | useState ou useReducer | Estado com histórico |
| NgRx Store | Redux Toolkit / Zustand | Estado global complexo |
| AsyncPipe (RxJS) | await em Server Component | Dados assíncronos |
✓
Dica de arquitetura: Em Next.js, prefira manter o estado no servidor sempre que possível. Use Server Components para buscar e exibir dados. Use estado client-side apenas para interatividade genuína (UI state: modais abertos, tabs ativas, formulários em progresso).
Referências: Managing State — React Docs ↗ · Signals — Angular Docs ↗