Novidades do Angular 19

Confira os destaques da versão 19 do Angular, lançada em novembro de 2024. Entre as principais novidades estão o linkedSignal, autoCsp, resource e o uso de standalone como padrão, além de outras novidades.

Novidades do Angular 19
Angular 19 - Conheça as principais novidades dessa versão

Lançada no dia 19 de novembro de 2024, a versão 19 do framework Angular traz novidades interessantes.

Atualizando o CLI

Antes de falar sobre as novidades do Angular 19, o script abaixo mostra como instalar a nova versão do CLI e também cria um novo projeto para explorarmos as novidades:

# Instalando o CLI da nova versão:
npm i @angular/cli@19 -g 

# Conferindo a versão:
ng version

Angular CLI: 19.0.0
Node: 22.11.0
Package Manager: npm 10.9.0
OS: darwin arm64

# Criando um novo projeto:
ng new versao19 --skip-git --skip-tests

# Executando o novo projeto:
cd versao19/
npm start
💡
Para quem já tem um projeto e precisa atualizar a versão do Angular, vale dar uma olhada neste link.

Testando o projeto recém-criado no navegador:

Navegador mostrando o conteúdo de localhost:4200
Testando o projeto recém-criado

Standalone agora é default

A partir dessa versão (Angular 19), não é mais necessário declarar explicitamente standalone: true para componentes, pipes e diretivas. Essa configuração, padrão a partir dessa versão e introduzida no Angular 14, permite que esses elementos sejam utilizados de forma independente, sem a necessidade de serem declarados em um módulo.

Entendendo o pure e standalone do Angular Pipe
Veja as diferenças de valores nos parâmetros pure e standalone de um Angular Pipe. Aprenda a usá-los para melhorar o desempenho e a reutilização.

Neste texto foi abordado sobre o que é o standalone e o que motivou sua criação

Observe no código abaixo gerado pelo CLI, que não é necessário declarar explicitamente o standalone: true. Quando omitido, o padrão já é true:

VS Code mostrando dois códigos gerados pelo CLI: app.component.ts e main.ts
Exemplo do código gerado pelo CLI

Strict standalone

Ainda falando sobre o standalone, uma forma de garantir que todos os componentes, pipes e diretivas do seu projeto sejam standalone, é utilizando a opção strictStandalone: true no arquivo tsconfig.json. Ao configurar essa opção, você está informando ao compilador que todos os componentes, pipes e diretivas do seu projeto devem ser declarados como standalone. Isso implica que, caso o compilador identifique algum componente, pipe ou diretiva que não seja standalone, um erro será gerado durante o processo de build.

{
  "angularCompilerOptions": {
    "strictStandalone": true
  }
}

trecho do tsconfig.json

VS Code mostrando o erro de compilação devido a presença de componentes que não usam o Standalone
Exemplo de erro ao utilizar um componente não standalone com a configuração strictStandalone ativada

autoCsp - hash-based - Content Security Policy

Essa nova funcionalidade, ainda em developer preview, adiciona um hash no arquivo index.html gerado durante o processo de build. O objetivo é simples: aumentar a segurança da aplicação nos navegadores. Esse recurso, conhecido como Content Security Policy (CSP), oferece um maior controle sobre os recursos que o navegador pode carregar. Diferentemente da abordagem tradicional, que lista apenas as origens permitidas, a CSP hash-based exige que os recursos sejam identificados por meio de um hash criptográfico único, garantindo uma camada adicional de proteção.

Content-Security-Policy (CSP): como melhorar a segurança do seu site
A CSP (Content Security Policy) é uma camada na defesa para seu site. Neste texto exploramos uma visão detalhada do CSP, destacando sua importância e métodos para combater vulnerabilidades como XSS e clickjacking.
Using hash-based CSP, the browser will add the hash of every inline script to the CSP. Each script will have a unique hash associated with it. That will prevent an attacker from running a malicious script on your page because for the browser to execute the script, its hash needs to be present in the CSP.

https://blog.angular.dev/meet-angular-v19-7b29dfd05b84
VS Code mostrando o hash gerado no arquivo index.html e a opção autoCsp no arquivo angular.json
Exemplo de uso do autoCsp

A imagem abaixo mostra mostra um trecho do arquivo index.html que foi gerado durante o processo de build e recebeu um hash. Esse hash é como uma impressão digital única do conteúdo dos scripts incluídos nesse arquivo. Ao adicionar ou modificar um bloco de script, o hash deixa de ser válido, pois ele representa uma nova "impressão digital" do arquivo. Consequentemente, o navegador, ao verificar a diretiva Content Security Policy (CSP), não encontra uma correspondência entre o hash especificado na diretiva e o hash calculado localmente para o script. Por essa razão, o script não é executado.

À esquerda o VS Code mostra um script que foi adicionado no arquivo index.html. À direita o navegador mostra um erro de CSP.
Exemplo de erro do CSP ao tentar incluir um novo bloco de script

inputs, outputs, e view queries passam a ser estáveis

Os recursos input, output, viewChild, viewChildren, contentChild e contentChildren passam a ser estáveis.

Angular
The web development framework for building modern apps.

Link da documentação oficial mostrando exemplos

linkedSignal

Em modo experimental, o linkedSignal é essencialmente um signal que reage às mudanças de outro signal. No exemplo abaixo, é possível observar que, ao alterar o valor de opcoes, o valor de opcaoSelecionada é automaticamente atualizado.

import { CommonModule } from '@angular/common';
import {
  Component,
  linkedSignal,
  signal
} from '@angular/core';
import { RouterOutlet } from '@angular/router';

@Component({
  selector: 'app-root',
  imports: [RouterOutlet, CommonModule],
  templateUrl: './app.component.html',
  styleUrl: './app.component.css',
})
export class AppComponent {
  opcoes = signal<Array<{ id: number; label: string }>>([
    { id: 1, label: 'Item 1' },
    { id: 2, label: 'Item 2' },
    { id: 3, label: 'Item 3' },
  ]);

  opcaoSelecionada = linkedSignal(() => this.opcoes()[0]);

  selecionarItem(id: number) {
    const itemSelecionado = this.opcoes()
      .find((a) => a.id === id);

    if (itemSelecionado === undefined) {
      return;
    }

    this.opcaoSelecionada.set(itemSelecionado);
  }

  gerarOutrasOpcoes() {
    this.opcoes.set([
      { id: 4, label: 'Item 4' },
      { id: 5, label: 'Item 5' },
      { id: 6, label: 'Item 6' },
    ]);
  }
}
<div>
  Opções:
  <pre>{{ opcoes() | json }}</pre>
</div>

<div>
  Selecionada:
  <pre>{{ opcaoSelecionada() | json }}</pre>
</div>

<hr>
<div>
  @for (opcao of opcoes(); track opcao.id) {
    <button (click)="selecionarItem(opcao.id)">
      Selecionar: {{ opcao.label }}
    </button>
  }
</div>
<hr>
<div>
  <button (click)="gerarOutrasOpcoes()">
    Gerar outras opções
  </button>
</div>

Resultado:

Navegador mostrando um JSON e alguns botões. Conforme o usuário clica nos botões, o valor do JSON é alterado na tela
Exemplo do linkedSignal

resource

Também em modo experimental, o resource() tem o objetivo de trabalhar de forma similar ao signal, porém para operações assíncronas, como por exemplo, requisições HTTP.

Most signal APIs are synchronous— signalcomputedinput, etc. However, applications often need to deal with data that is available asynchronously. A Resource gives you a way to incorporate async data into your application's signal-based code.

https://angular.dev/guide/signals/resource

Abaixo há um exemplo de uso onde inicialmente uma lista de itens é carregada e renderizada em uma tabela. Então, ao clicar no botão "detalhes" na tabela, preenchemos o valor do signal idItemSelecionado e automaticamente o código dentro do resource itemSelecionadoResource efetua uma requisição à API. O resultado é computado em itemSelecionado, que por sua vez, é apresentado na tela.

import { CommonModule } from '@angular/common';
import {
  Component,
  computed,
  resource,
  signal
} from '@angular/core';

@Component({
  selector: 'app-root',
  imports: [CommonModule],
  templateUrl: './app.component.html',
  styleUrl: './app.component.css',
})
export class AppComponent {
  // Carrega uma lista de itens
  itensResource = resource({
    loader: async () => {
      const url = new URL('/', 'http://localhost:3000/');
      const fetchResponse = await fetch(url);
      return await fetchResponse.json();
    },
  });
  itens = computed(() => this.itensResource.value());

  // Trata os detalhes de um item selecionado
  idItemSelecionado = signal<number | null>(null);
  itemSelecionadoResource = resource({
    request: () => ({ id: this.idItemSelecionado() }),
    loader: async ({ request }) => {
      if (request.id === null) {
        return null;
      }

      const baseUrl = 'http://localhost:3000/';
      const path = request.id !== null ? `/${request.id}` : '/';
      const url = new URL(path, baseUrl);
      const fetchResponse = await fetch(url);
      return await fetchResponse.json();
    },
  });

  itemSelecionado = computed(() => 
    this.itemSelecionadoResource.value()
  );

  selecionarItem(id: number) {
    this.idItemSelecionado.set(id);
  }
}
<table>
  <thead>
    <tr>
      <td>Id</td>
      <td>Label</td>
      <td></td>
    </tr>
  </thead>
  <tbody>
    @for (item of itens(); track item.id) {
      <tr>
        <td>{{ item.id }}</td>
        <td>{{ item.label }}</td>
        <td>
          <button
            (click)="selecionarItem(item.id)">
            Detalhes
          </button>
        </td>
      </tr>
      @if (item.id === idItemSelecionado()) {
        <tr>
          <td colspan="3">
            <pre>
              @if (itemSelecionadoResource.isLoading()) {
                Carregando...
              } @else {
                {{ itemSelecionado() | json }}
              }
            </pre>
          </td>
        </tr>
      }
    }
  </tbody>
</table>

Resultado:

Navegador mostrando uma tabela de dados. Ao clicar no botão "detalhes", são carregadas mais informações
Exemplo de uso do resource

Considerações

Aqui, destacamos apenas algumas das novidades, neste link você encontra a lista completa da versão 19. Além disso, abaixo apresentamos uma tabela de compatibilidade entre as versões do Angular, Node.js, TypeScript e RxJS:

Tabela de compatibilidade entre versões do Angular, Node.js, TypeScript e Rxjs
Tabela de compatibilidade entre versões do Angular, Node.js, TypeScript e Rxjs

Links interessantes: