Angular v15 já está disponível!

Tradução livre do artigo original: blog.angular.io/angular-v15-is-now-availabl..

No ano passado, foi removido o compilador legado e o pipeline de renderização do Angular, que permitiu o desenvolvimento de uma série de melhorias na experiência do desenvolvedor nos últimos meses. Angular v15 é o resultado disso com dezenas de refinamentos que levam a uma melhor experiência e desempenho do desenvolvedor.

Standalone API agora está estável e pronta para produção!

Na v14, foram introduzidas novas standalone APIs que permitem aos desenvolvedores criarem aplicativos sem usar NgModules. A equipe do Angular está feliz em compartilhar que essas APIs saíram da fase de preview e agora fazem parte da API estável do framework. A partir de agora ela será evoluída gradualmente seguindo o versionamento semântico.

Como parte de garantir que as standalone APIs estivessem prontas para serem utilizadas em produção, foram garantidos que os componentes standalone funcionem em todo ecosistema Angular e agora funcionem completamente com as APIs HttpClient, Angular Elements, router e mais.

As APIs standalone permitem que você inicialize um aplicativo usando um único componente:

import {bootstrapApplication} from '@angular/platform-browser';
import {ImageGridComponent} from'./image-grid';

@Component({
  standalone: true,
  selector: 'photo-gallery',
  imports: [ImageGridComponent],
  template: `
    … <image-grid [images]="imageList"></image-grid>
  `,
})
export class PhotoGalleryComponent {
  // component logic
}
bootstrapApplication(PhotoAppComponent);

Router and HttpClient tree-shakable standalone APIs

Você pode criar um aplicativo de multi-rotas usando as novas APIs standalone do router! Para declarar a rota raiz, você pode usar o seguinte exemplo:

export const appRoutes: Routes = [{
  path: 'lazy',
  loadChildren: () => import('./lazy/lazy.routes')
    .then(routes => routes.lazyRoutes)
}];

Onde lazyRoutes são declarados em:

import {Routes} from '@angular/router';

import {LazyComponent} from './lazy.component';

export const lazyRoutes: Routes = [{path: '', component: LazyComponent}];

e finalmente, registre o appRoutes na chamada bootstrapApplication:

bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(appRoutes)
  ]
});

Outro benefício da API ProvideRouter é que ela pode ser tree-shakable! Os Bundlers podem remover recursos não utilizados do roteador em tempo de compilação. Em testes com a nova API, foi descoberto que a remoção desses recursos não utilizados do pacote resultou em uma redução de 11% no tamanho do código do router no bundle do aplicativo.

API de composição de diretivas

A API de composição de diretivas traz a reutilização de código para outro nível! Esse recurso foi inspirado pela solicitação de recurso mais popular no GitHub, solicitando a funcionalidade de adicionar diretivas a um elemento host.

A API de composição de diretivas permite que os desenvolvedores aprimorem os elementos do host com diretivas e adiciona ao Angular com uma poderosa estratégia de reutilização de código, que só é possível graças ao compilador do Angular. A API de composição de diretivas funciona apenas com diretivas independentes.

Vejamos um exemplo rápido:

@Component({
  selector: 'mat-menu',
  hostDirectives: [HasColor, {
    directive: CdkMenu,
    inputs: ['cdkMenuDisabled: disabled'],
    outputs: ['cdkMenuClosed: closed']
  }]
})
class MatMenu {}

No trecho de código acima, aprimoramos MatMenu com duas diretivas: HasColor e CdkMenu. MatMenu reutiliza todas as entradas, saídas e lógica associada com HasColors e apenas a lógica e as entradas selecionadas do CdkMenu.

Esta técnica pode lembrá-lo de herança múltipla ou traços em algumas linguagens de programação, com a diferença de que temos um mecanismo para resolução de conflitos de nomes e é aplicável a primitivas de interface de usuário.

A diretiva de imagem agora está estável!

Foi anunciada a preview da diretiva de imagem Angular que foi desenvolvida em colaboração com o Chrome Aurora na v14.2.

image.png

Agora ela está estável! A Land's End experimentou esse recurso e observou 75% de melhoria no LCP em um teste no lighthouse.

A versão v15 também inclui alguns novos recursos para a diretiva de imagem:

  • Geração automática de srcset: a diretiva garante que uma imagem de tamanho apropriado seja solicitada gerando o atributo srcset para você. Isso pode reduzir o tempo de download de suas imagens.

  • Modo de preenchimento [experimental]: este modo faz com que a imagem preencha seu contêiner pai, eliminando a necessidade de declarar a largura e a altura da imagem. É uma ferramenta útil se você não souber os tamanhos de suas imagens ou se quiser migrar imagens de plano de fundo CSS para usar a diretiva.

Você pode usar a diretiva NgOptimizedImage diretamente em seu componente ou NgModule:

import { NgOptimizedImage } from '@angular/common';

// Include it into the necessary NgModule
@NgModule({
  imports: [NgOptimizedImage],
})
class AppModule {}

// ... or a standalone Component
@Component({
  standalone: true
  imports: [NgOptimizedImage],
})
class MyStandaloneComponent {}

Para usá-lo em um componente, basta substituir o atributo src da imagem por ngSrc e certifique-se de especificar o atributo de prioridade para suas imagens LCP.

Você pode encontrar mais informações na documentação.

Guarda de rotas funcionais

Junto com as APIs de router independentes e tree-shakable, foi trabalhado o boilerplate das guardas de rota. Vejamos um exemplo onde definimos uma guarda que verifica se o usuário está logado:

@Injectable({ providedIn: 'root' })
export class MyGuardWithDependency implements CanActivate {
  constructor(private loginService: LoginService) {}

  canActivate() {
    return this.loginService.isLoggedIn();
  }
}

const route = {
  path: 'somePath',
  canActivate: [MyGuardWithDependency]
};

LoginService implementa a maior parte da lógica e na guarda só invocamos isLoggedIn(). Mesmo que o guard seja bem simples, temos muito boilerplate.

Com as novas guarda de rotas funcionais, você pode refatorar esse código para:

const route = {
  path: 'admin',
  canActivate: [() => inject(LoginService).isLoggedIn()]
};

Expressamos toda a guarda dentro da sua declaração. As guardas funcionais também podem ser compostas — você pode criar funções do tipo factory que aceitam uma configuração e retornam uma função de guarda ou resolver. Você pode encontrar um exemplo para executar proteções de roteador em série no GitHub.

O roteador desempacota as importações padrão

Para tornar o roteador mais simples e reduzir ainda mais o boilerplate, o roteador agora desempacota automaticamente as exportações padrão durante o carregamento lazy.

Vamos supor que você tenha o seguinte LazyComponent:

@Component({
  standalone: true,
  template: '...'
})
export default class LazyComponent { ... }

Antes dessa alteração, para fazer o carregamento lazy de um componente independente , você precisava:

{
  path: 'lazy',
  loadComponent: () => import('./lazy-file').then(m => m.LazyComponent),
}

Agora o roteador irá procurar por uma exportação padrão e se encontrar, usará automaticamente, o que simplifica a declaração de rota para:

{
  path: 'lazy',
  loadComponent: () => import('./lazy-file'),
}

Melhores stack traces

A equipe do Angular recebe muitos insights das pesquisas anuais com desenvolvedores, por isso queremos agradecer a você por dedicar um tempo para compartilhar seus pensamentos! Entramos de cabeça nas dificuldades com a experiência de depuração que os desenvolvedores enfrentam, descobrimos que as mensagens de erro poderiam ser melhoradas.

image.png

Problemas de depuração para desenvolvedores Angular

Foi feita uma parceria com o Chrome DevTools para corrigir isso! Veja um exemplo de stack trace que você pode trabalhar em um aplicativo Angular:

ERROR Error: Uncaught (in promise): Error
Error
    at app.component.ts:18:11
    at Generator.next (<anonymous>)
    at asyncGeneratorStep (asyncToGenerator.js:3:1)
    at _next (asyncToGenerator.js:25:1)
    at _ZoneDelegate.invoke (zone.js:372:26)
    at Object.onInvoke (core.mjs:26378:33)
    at _ZoneDelegate.invoke (zone.js:371:52)
    at Zone.run (zone.js:134:43)
    at zone.js:1275:36
    at _ZoneDelegate.invokeTask (zone.js:406:31)
    at resolvePromise (zone.js:1211:31)
    at zone.js:1118:17
    at zone.js:1134:33

Este snippet sofre de dois problemas:

  • Há apenas uma linha correspondente ao código que o desenvolvedor criou. Todo o resto vem de dependências de terceiros (framework Angular, Zone.js, RxJS)
  • Não há informações sobre qual interação do usuário causou o erro

Não há informações sobre qual interação do usuário causou o erro

A equipe do Chrome DevTools criou um mecanismo para ignorar scripts provenientes de node_modules anotando source maps por meio da CLI do Angular. Também foi criada uma API de marcação de pilha assíncrona que nos permitiu concatenar tarefas assíncronas agendadas e independentes em um único stack trace. Jia Li integrou o Zone.js com a API de marcação de pilha assíncrona, o que permitiu fornecer stack traces vinculados.

Essas duas mudanças melhoram drasticamente o stack trace que os desenvolvedores veem no Chrome DevTools:

ERROR Error: Uncaught (in promise): Error
Error
    at app.component.ts:18:11
    at fetch (async)  
    at (anonymous) (app.component.ts:4)
    at request (app.component.ts:4)
    at (anonymous) (app.component.ts:17)
    at submit (app.component.ts:15)
    at AppComponent_click_3_listener (app.component.html:4)

Aqui você pode acompanhar a execução do botão pressionado no AppComponent até o erro. Você pode ler mais sobre as melhorias aqui.

Lançamento de componentes baseados em MDC para produção

Estamos felizes em anunciar que a refatoração dos componentes de material Angular com base no Material Design Components for Web (MDC) está concluída! Essa mudança permite que o Angular se alinhe ainda mais com a especificação do Material Design, reutilize código de primitivas desenvolvidas pela equipe do Material Design e nos permite adotar o Material 3 assim que finalizado os tokens de estilo.

Para muitos dos componentes, atualizamos os estilos e a estrutura DOM e outros reescrevemos do zero. Mantivemos a maioria das APIs TypeScript e seletores de componentes/diretivas para os novos componentes idênticos à implementação antiga.

Migramos milhares de projetos do Google, o que nos permitiu facilitar o caminho da migração externa e documentar uma lista abrangente das alterações em todos os componentes.

Devido ao novo DOM e CSS, você provavelmente descobrirá que alguns estilos em seu aplicativo precisam ser ajustados, principalmente se seu CSS estiver substituindo estilos em elementos internos em qualquer um dos componentes migrados.

A implementação antiga de cada novo componente agora está obsoleta, mas ainda está disponível a partir de uma importação “herdada”. Por exemplo, você pode importar a implementação do botão mat antigo importando o módulo de botão legado.

import {MatLegacyButtonModule} from '@angular/material/legacy-button';

Visite o guia de migração para obter mais informações.

Mudamos muitos dos componentes para usar tokens de design e variáveis CSS internamente, o que fornecerá um caminho mais tranquilo para os aplicativos adotarem os estilos de componentes do Material 3.

Mais melhorias nos componentes

Resolvemos o 4º problema mais votado - suporte à seleção de intervalo no controle slider.

Para obter uma entrada de intervalo, use:

<mat-slider>
  <input matSliderStartThumb>
  <input matSliderEndThumb>
</mat-slider>

Além disso, todos os componentes agora têm uma API para personalizar a densidade que resolveu outro problema popular do GitHub.

Agora você pode especificar a densidade padrão em todos os seus componentes personalizando seu tema:

@use '@angular/material' as mat;

$theme: mat.define-light-theme((
  color: (
    primary: mat.define-palette(mat.$red-palette),
    accent: mat.define-palette(mat.$blue-palette),
  ),
  typography: mat.define-typography-config(),
  density: -2,
));

@include mat.all-component-themes($theme);

As novas versões dos componentes incluem uma ampla gama de melhorias de acessibilidade, incluindo melhores taxas de contraste, tamanhos de alvo de toque aumentados e semântica ARIA refinada.

CDK Listbox

O Component Dev Kit (CDK) oferece um conjunto de primitivas de comportamento para a construção de componentes de interface do usuário. Na v15, introduzimos outra primitiva que você pode personalizar para seu caso de uso — a listbox CDK:

image.png

O módulo @angular/cdk/listbox fornece diretivas para ajudar a criar interações de caixa de listagem personalizadas com base no padrão de caixa de listagem WAI ARIA.

Ao usar @angular/cdk/listbox, você obtém todos os comportamentos esperados para uma experiência acessível, incluindo suporte a layout bidi, interação com teclado e gerenciamento de foco. Todas as diretivas aplicam suas funções ARIA associadas ao elemento host.

Melhorias no suporte de esbuild experimental

image.png

Na v14, anunciamos o suporte experimental para esbuild em ng build para permitir tempos de compilação mais rápidos e simplificar nosso pipeline.

Na v15, agora temos Sass experimental, modelo SVG, substituição de arquivo e ng build --watchsupport ! Por favor, tente esbuild atualizando os builder angular.json de:

"builder": "@angular-devkit/build-angular:browser"

para

"builder": "@angular-devkit/build-angular:browser-esbuild"

Se você encontrar algum problema com suas compilações de produção, informe-nos registrando um problema no GitHub.

Importações automáticas no language service

O language service agora pode importar automaticamente os componentes que você está usando em um template, mas não adicionou a um componente independente ou a um NgModule.

image.png

Melhorias na CLI

Na CLI Angular, introduzimos suporte para APIs estáveis independentes. Agora você pode gerar um novo componente independente via ng g component --standalone.

Também temos a missão de simplificar a saída de ng new. Como primeiro passo, reduzimos a configuração removendo test.ts, polyfills.ts e ambientes. Agora você pode especificar seus polyfills diretamente em angular.json na seção polyfills:

"polyfills": [
  "zone.js"
]

Para reduzir ainda mais os arquivos de configuração, agora usamos .browserlist para permitir que você defina a versão de destino do ECMAScript.

Destaques da contribuição da comunidade

Estamos gratos em compartilhar que, desde o lançamento da v14, recebemos contribuições de mais de 210 pessoas em framework, componentes e CLI! Nesta seção, gostaria de destacar dois deles.

Fornecida a capacidade de configurar opções padrão para DatePipe

Esse recurso permite que você altere globalmente a configuração de formatação padrão para DatePipe. Aqui está um exemplo com a nova API bootstrapApplication:

bootstrapApplication(AppComponent, {
  providers: [
    {
      provide: DATE_PIPE_DEFAULT_OPTIONS,
      useValue: { dateFormat: 'shortDate' }
    }
  ]
});

A configuração acima habilitará o formato shortDate para todos os locais em que você usa DatePipe em seu aplicativo.

Adicionada a tag de pré-carregamento para imagens prioritárias durante o SSR

Para garantir que as imagens prioritárias sejam carregadas o mais rápido possível, Jay Bell adicionou uma funcionalidade à diretiva de imagem para incluir uma tag <link rel="preload"> para elas ao usar o Angular Universal.

Não há necessidade de ação do seu lado se você já ativou a diretiva de imagem. Se você especificou uma imagem como prioridade, a diretiva irá pré-carregá-la automaticamente.

Descontinuações

Os lançamentos major nos permitem evoluir a estrutura em direção à simplicidade, melhor experiência do desenvolvedor e alinhamento com a plataforma da web.

Depois de analisar milhares de projetos no Google, encontramos alguns padrões raramente usados que, na maioria dos casos, são mal utilizados. Como resultado, estamos descontinuando o providedIn: 'any' é uma opção que tem uso muito limitado, além de um punhado de casos esotéricos internos ao framework.

Também estamos descontinuando o providedIn: NgModule . Ele não tem amplo uso e, na maioria dos casos, é usado incorretamente, em circunstâncias em que você deve preferir providedIn: 'root'. Se você deve realmente definir o escopo de provedores para um NgModule específico, use NgModule.providers.

Com o layout em evolução em CSS, a equipe deixará de publicar novos lançamentos de @angular/flex-layout. Continuaremos fornecendo correções de segurança e compatibilidade de navegador para o próximo ano. Você pode aprender mais sobre isso na primeira postagem do blog da nossa série “CSS moderno”.

Animado com o que vem por aí!

O lançamento do Ivy em 2020 permitiu muitas melhorias em geral que você pode encontrar já sendo lançadas. O NgModules opcional é um ótimo exemplo. Ele ajuda na redução dos conceitos com os quais os iniciantes precisam lidar como parte de sua jornada de aprendizado básica e também suporta recursos avançados, como API de composição de diretivas por meio de diretivas independentes.

Em seguida, estamos abordando melhorias em nosso pipeline de renderização do lado do servidor e reatividade, trazendo melhorias de qualidade de vida em geral!

Mal posso esperar para compartilhar com vocês o que está por vir!