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.
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 startTestando o projeto recém-criado no navegador:

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.

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:

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

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.

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

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.

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

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:

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—signal,computed,input, etc. However, applications often need to deal with data that is available asynchronously. AResourcegives 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:

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:

Links interessantes:

