ngnext

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

AngularReact / Next.jsUso
signal(value)useState(value)Estado local reativo
computed(() => expr)useMemo(() => expr, [deps])Valor derivado
effect(() => {...})useEffect(() => {...}, [deps])Efeitos colaterais
@Injectable serviceReact Context + ProviderEstado compartilhado
BehaviorSubject (RxJS)useState ou useReducerEstado com histórico
NgRx StoreRedux Toolkit / ZustandEstado global complexo
AsyncPipe (RxJS)await em Server ComponentDados 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).