Novidades do Angular 18
Conheça as principais novidades da versão 18 do framework Angular: zoneless, eventos no formGroup, fallback no ng-content, entre outras novidades.
![Novidades do Angular 18](/content/images/size/w1200/2024/06/angular-18.png)
Após aproximadamente 6 meses desde o lançamento da versão 17 do framework Angular, foi lançada recentemente a versão 18. Essa versão apresenta algumas novidades como:
- O controle de fluxo passar a ser stable
- Tratamento de erro (fallback) para o ng-content
- Possibilidade de utilizar funções na propriedade redirect do Route
- Eventos no FormGroup, FormControl e FormArray
- Suporte para "zoneless change detection", ainda em modo experimental
Neste texto abordamos um pouco sobre essas novidades e também como atualizar o CLI para a versão 18.
Como atualizar o CLI do Angular
Para atualizar o CLI do Angular:
npm install -g @angular/cli@18
# Após a instalação é possível verificar
# a versão com o seguinte comando:
ng version
# Exemplo da saída do comando acima:
Angular CLI: 18.0.2
Node: 20.12.0
Package Manager: npm 10.8.1
OS: darwin arm64
Angular:
...
Package Version
------------------------------------------------------
@angular-devkit/architect 0.1800.2 (cli-only)
@angular-devkit/core 18.0.2 (cli-only)
@angular-devkit/schematics 18.0.2 (cli-only)
@schematics/angular 18.0.2 (cli-only)
Controle de fluxo
Há pouco tempo foi apresentada uma nova sintaxe para controle de fluxo, permitindo escrever estruturas como if/else, loop for e switch de uma maneira diferente. A partir desta versão, 18, essa sintaxe passa a ser estável (stable) e não mais experimental.
Exemplo utilizando diretivas:
<button (click)="exibirDetalhes.set(!exibirDetalhes())">
<ng-container *ngIf="exibirDetalhes()">
Esconder detalhes
</ng-container>
<ng-container *ngIf="!exibirDetalhes()">
Mostrar detalhes
</ng-container>
</button>
<div *ngIf="exibirDetalhes()">
<ng-container *ngFor="let item of itens()">
<div>{{ item }}</div>
</ng-container>
<ng-container *ngIf="itens().length">
<div>Sem itens</div>
</ng-container>
</div>
Exemplo utilizando a nova sintaxe:
<button (click)="exibirDetalhes.set(!exibirDetalhes())">
@if(exibirDetalhes()) {
Esconder detalhes
}
@else {
Mostrar detalhes
}
</button>
@if(exibirDetalhes()) {
<div>
@for (item of itens(); track $index) {
<div>{{ item }}</div>
} @empty {
<div>Sem itens</div>
}
</div>
}
Valor default para o ng-content
Antes da versão 18, era um pouco mais complicado atribuir um valor padrão (default) para o <ng-content>
. Considere um componente bem simples, um CardComponent que tenha o seguinte template:
<div>
<h1>{{titulo()}}</h1>
<div>
<ng-content></ng-content>
</div>
</div>
Ao utilizar este componente, é possível projetarmos algo a mais devido à presença do <ng-content>
, por exemplo:
<app-card titulo="Card com ng-content">
<!-- Conteúdo projetado no ng-content -->
<p>Lorem, ipsum dolor.</p>
<em>Lorem ipsum dolor sit amet.</em>
</app-card>
Eventualmente, ao utilizar o componente você não irá projetar nada, e então podemos atribuir um valor padrão caso quem esteja utilizando o componente não projete nenhum conteúdo, por exemplo:
<app-card titulo="Card com ng-content">
</app-card>
Definindo um valor padrão para o ng-content
Antes da versão 18 do Angular:
import { CommonModule } from '@angular/common';
import { Component, input } from '@angular/core';
@Component({
selector: 'app-card-antigo',
standalone: true,
imports: [CommonModule],
templateUrl: './card-antigo.component.html'
})
export class CardAntigoComponent {
titulo = input<string>('');
}
card-antigo.component.ts
<div>
<h1>{{titulo()}}</h1>
<div>
<div #container>
<ng-content></ng-content>
</div>
<div *ngIf="!container.children.length">
NGContent não informado!
</div>
</div>
</div>
card-antigo.component.html
A partir da versão 18 ficou bem mais simples, veja a seguir:
import { Component, input } from '@angular/core';
@Component({
selector: 'app-card',
standalone: true,
templateUrl: './card.component.html',
})
export class CardComponent {
titulo = input<string>('');
}
card.component.ts
<div>
<h1>{{titulo()}}</h1>
<div>
<ng-content>NGContent não informado!</ng-content>
</div>
</div>
card.component.html
Para ambos os casos acima, caso nenhum conteúdo seja projetado, o Angular assumirá o valor default NGContent não informado!
:
<app-card titulo="Card com ng-content">
<div>Conteúdo dentro do card</div>
</app-card>
<app-card titulo="Card sem o ng-content">
</app-card>
Resultado do HTML gerado:
<app-card titulo="Card com ng-content">
<div>
<h1>Card com ng-content</h1>
<div>
<div>Conteúdo dentro do card</div>
</div>
</div>
</app-card>
<app-card titulo="Card sem o ng-content">
<div>
<h1>Card sem o ng-content</h1>
<div>NGContent não informado!</div>
</div>
</app-card>
Funções na propriedade redirect do Route
Outra novidade é a possibilidade de utilizarmos uma função na propriedade redirect
na configuração das rotas. Isto dá mais liberdade para resolver alguns cenários.
Por exemplo, antes pesquisávamos um produto usando o endereço /produtos?id=123
, mas agora o formato mudou para /produtos/123
. Para manter a compatibilidade, podemos criar uma função na rota /produtos
que fará o tratamento necessário e redirecionará para a nova rota. Veja o exemplo abaixo:
export const routes: Routes = [{
path: 'produtos',
redirectTo: (redirectData) => {
const produtoId = redirectData.queryParams['id'];
if (produtoId === undefined) {
return 'not-found';
}
const router: Router = inject(Router);
return router.createUrlTree(['produtos', produtoId]);
},
}, {
path: 'produtos/:id',
component: ProdutoComponent
}, {
path: 'not-found',
component: NotFoundComponent
}, {
path: '**',
redirectTo: 'not-found'
}];
Eventos no FormGroup, FormControl e FormArray
As classes FormControl, FormGroup e FormArray agora possuem uma propriedade chamada events. Você pode se inscrever (subscribe) neste Observable para receber informações sobre diversos eventos. Os eventos disponíveis são:
- FormResetEvent
Emitido quando o formulário é resetado. - FormSubmittedEvent
Emitido quando o formulário é submetido. - PristineChangeEvent
Emitido quando o valor do formulário é modificado a partir do seu estado inicial. - StatusChangeEvent
Emitido sempre que há uma mudança de status. - TouchedChangeEvent
Emitido quando há alguma interação com o formulário, como clicar em um campo e depois fora dele. - ValueChangeEvent
Emitido sempre que houver uma alteração no valor do formulário.
Exemplo:
// Emite o status do formulário quando houver uma
// alteração em relação ao último valor emitido
// Valores possíveis:
// 'VALID' | 'INVALID' | 'PENDING' | 'DISABLED'
this.formGroup.events
.pipe(
filter(event => event instanceof StatusChangeEvent),
map((event) => (event as StatusChangeEvent).status),
distinctUntilChanged()
)
.subscribe((status) => {
console.log('Novo status do formulário: ', status);
});
Suporte para "zoneless change detection", ainda em modo experimental
Anteriormente, comentamos sobre o funcionamento do sistema de detecção de mudanças (change detection) do Angular: como funciona o change detection do Angular. Nesta versão (18), em modo experimental, o framework possibilita deixar de lado a lib Zone.js, por isso o "zoneless".
Dentre as vantagens desta nova feature, podemos citar:
- Melhor interoperabilidade com outros frameworks e utilização de micro-frontends.
- Renderização inicial e execução mais rápidas.
- Bundle menor.
- Stack trace mais legível.
Ao que parece, a equipe do Angular está focada em tornar o framework independente da lib Zone.js.
We’ve been working for several years towards a way of using Angular that doesn’t rely on zone.js, and we’re incredibly excited to share the first experimental APIs for zoneless!
fonte: https://blog.angular.dev/angular-v18-is-now-available-e79d5ac0affe
Como utilizar o zoneless
Para utilizar este modo é bem simples. Primeiro modifique o app.config.ts
adicionando o conteúdo abaixo:
// (imports)...
export const appConfig: ApplicationConfig = {
providers: [
provideExperimentalZonelessChangeDetection(),
// ...
],
};
app.config.ts
Depois remova a referência zone.js
no arquivo angular.json:
"polyfills": [
// Remover a linha abaixo:
"zone.js"
],
angular.json
Considerações
Para concluir, cobrimos apenas uma fração das novidades do Angular 18, mas neste link você pode ver a lista completa de novidades.
Links interessantes: