Angular: recuperando os dados de uma página após um recarregamento

Ao desenvolver aplicações Angular, é comum precisar preservar o estado da página após um refresh, garantindo que os dados que o usuário forneceu sejam mantidos, como em um formulário. Isso pode ser feito de algumas formas, dentre elas, salvar os dados no localStorage do navegador.

Introdução sobre o ciclo de vida do JavaScript

Quando acessamos uma página no navegador, diversos elementos são carregados, como o conteúdo HTML, arquivos CSS, scripts JavaScript, imagens, fontes, entre outros. Após o navegador interpretar os scripts, é criado um contexto de execução. Esse contexto representa o ambiente onde o JavaScript da página opera, permitindo a manipulação de elementos da DOM, execução de lógica de negócios e interação com APIs externas.

Por exemplo, o contexto de execução pode corresponder a um projeto Angular em funcionamento ou até mesmo a uma página estática, como esta que você está lendo agora. No entanto, ao pressionar F5 ou clicar no botão de atualizar do seu navegador, o contexto de execução atual é descartado, e um novo é criado. Isso significa que todas as variáveis, classes, instâncias de objetos e outros dados mantidos em memória pelo JavaScript são perdidos e reiniciados.

De forma prática, imagine que você preenche um formulário em uma página e, por algum motivo, decide recarregar essa página. Após o refresh, os dados do formulário não estarão mais presentes, porque o contexto anterior foi substituído. Esse comportamento é padrão no ciclo de vida do JavaScript em uma página web, ou seja, sempre que uma página é recarregada, é como se você formatasse seu computador e recomeçasse do zero.

É exatamente nesse ponto que surge a necessidade de salvar o estado da página. Preservar o estado ajuda a melhorar a experiência do usuário, evitando que informações sejam perdidas e permitindo que a interação continue de forma contínua, mesmo após um recarregamento. Técnicas como o uso de localStoragesessionStorage, query params, cookies ou persistência de dados no servidor são algumas das abordagens mais comuns para solucionar esse problema.

Neste pequeno texto será apresentado o uso do localStorage para gravar e resgatar valores, preservando assim o estado de um formulário.

Como utilizar e quais as diferenças entre sessionStorage e localStorage
Aprenda um pouco mais sobre estes dois recursos, localStorage e sessionStorage. Veja as diferenças e os cenários onde é melhor usar um ou outro.

Gravando e resgatando o estado da página de um projeto Angular

O código abaixo foi desenvolvido utilizando Angular 19, mas a ideia e boa parte do código funcionará com versões mais antigas do framework.

A classe EstadoService fornece métodos bem diretos para salvar qualquer valor no localStorage. O motivo da escolha do localStorage, é porque ao contrário do sessionStorage, ele mantém os dados armazenados mesmo após o fechamento da aba ou do navegador. Também poderíamos salvar os valores nos cookies, contudo, os cookies são trafegados em todas as requisições ao backend, aumentando assim o tamanho do cabeçalho de requisição.

import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class EstadoService {
  salvar(chave: string, valor: string): void {
    localStorage.setItem(chave, valor);
  }

  obter(chave: string): string | null {
    return localStorage.getItem(chave);
  }

  limpar(chave: string): void {
    localStorage.removeItem(chave);
  }
}

estado.service.ts

Já no componente que contém o formulário, a lógica é bem simples. Deixei alguns comentários para facilitar o entendimento:

import { CommonModule } from '@angular/common';
import {
  Component,
  inject,
  OnDestroy,
  OnInit,
} from '@angular/core';
import {
  FormControl,
  FormGroup,
  ReactiveFormsModule,
} from '@angular/forms';
import { debounceTime, Subscription } from 'rxjs';
import { EstadoService } from '../estado.service';

const CHAVE_FORM_EXEMPLO = 'teste';

@Component({
  selector: 'app-formulario-exemplo',
  templateUrl: './formulario-exemplo.component.html',
  imports: [CommonModule, ReactiveFormsModule],
})
export class FormularioExemploComponent
  implements OnInit, OnDestroy {
  private readonly estadoService = inject(EstadoService);
  private formularioSubscription!: Subscription;
  formulario: FormGroup = new FormGroup({
    texto: new FormControl<string>('', {
      nonNullable: true,
    }),
    select: new FormControl<string>('', {
      nonNullable: true,
    }),
    radio: new FormControl<string>('', {
      nonNullable: true,
    }),
    checkbox: new FormControl<string>('', {
      nonNullable: true,
    }),
  });

  ngOnInit(): void {
    this.carregarEstadoPagina();
    this.monitorarSalvarEstadoPagina();
  }

  ngOnDestroy(): void {
    this.formularioSubscription.unsubscribe();
  }

  limparFormulario() {
    this.formulario.reset();
    this.estadoService.limpar(CHAVE_FORM_EXEMPLO);
  }

  /**
   * Verifica se existe um estado salvo anteriormente.
   * Caso exista, é efetuado o parse do valor e atribuido
   * ao `formulario`:
   */
  private carregarEstadoPagina(): void {
    const dadosFormulario = this.estadoService.obter(
      CHAVE_FORM_EXEMPLO,
    );

    if (dadosFormulario === null) {
      return;
    }

    try {
      const objDadosForm = JSON.parse(dadosFormulario);
      this.formulario.patchValue(objDadosForm);
    } catch {
      this.estadoService.limpar(CHAVE_FORM_EXEMPLO);
    }
  }

  /**
   * Sempre que o `formulario` sofre alguma alteração,
   * pegamos seu valor e salvamos.
   */
  private monitorarSalvarEstadoPagina(): void {
    this.formularioSubscription =
      this.formulario.valueChanges
        // Aguarda 500ms após a última alteração de valor
        // antes de emitir o evento no `subscribe`.
        .pipe(debounceTime(500))
        .subscribe(() => {
          // O método `getRawValue()` retorna todos os
          // valores do formulário, incluindo os campos
          // desabilitados.
          const formValue = this.formulario.getRawValue();
          this.estadoService.salvar(
            CHAVE_FORM_EXEMPLO,
            JSON.stringify(formValue),
          );
        });
  }
}

formulario-exemplo.component.ts

<h1>Formulário de Teste</h1>
<div>
  <form [formGroup]="formulario">
    <div>
      <input
        placeholder="campo 1..."
        formControlName="texto" />
    </div>
    <div>
      <select formControlName="select">
        <option value=""></option>
        <option value="1">Valor 1</option>
        <option value="2">Valor 2</option>
      </select>
    </div>
    <div>
      <input
        formControlName="radio"
        type="radio"
        value="radio1" />
      Radio 1
    </div>
    <div>
      <input
        formControlName="radio"
        type="radio"
        value="radio2" />
      Radio 2
    </div>
    <div>
      <input
        formControlName="radio"
        type="radio"
        value="radio3" />
      Radio 3
    </div>
    <div>
      <input type="checkbox" formControlName="checkbox" />
    </div>
    <div>
      <button type="submit">Enviar</button>
      <button (click)="limparFormulario()" type="reset">
        Limpar
      </button>
    </div>
  </form>
</div>
<div>
  <!-- 
    O JSONPipe funciona como o JSON.stringify(). Para
    utilizá-los é necessário importar o CommonModule.
  -->
  <pre>{{ formulario.value | json }}</pre>
</div>

formulario-exemplo.component.html

Observe no resultado abaixo que após preencher o formulário e efetuar um "reload", os valores do formulário são mantidos:

Resultado final

É possível visualizar os dados salvos no localStorage através do DevTools do navegador:

DevTool do navegador mostrando os dados do localStorage

Considerações

Mesmo sendo um projeto simples, envolve o entendimento sobre o que acontece no navegador ao requisitar uma página HTML, sobretudo de quando o JavaScript é interpretado e executado, que é um conceito fundamental em projetos Angular. Vale mencionar que o entendimento dos mecanismos de storage do navegador são importantes e já foram alvo de estudo por aqui, deixo o link a seguir:

Como utilizar e quais as diferenças entre sessionStorage e localStorage
Aprenda um pouco mais sobre estes dois recursos, localStorage e sessionStorage. Veja as diferenças e os cenários onde é melhor usar um ou outro.

Para finalizar, a ideia pode ser expandida e modificada, tudo dependerá dos requisitos do seu projeto.