Publicado em

- 7 minutos de leitura

Angular 20: Conheça as novidades!

img of Angular 20: Conheça as novidades!

Angular v20: Novidades e Aprimoramentos

A versão 20 do Angular introduz um conjunto de novas funcionalidades, aprimoramentos na experiência de desenvolvimento e a estabilização de recursos previamente introduzidos.

Esta release visa consolidar a plataforma e otimizar o fluxo de trabalho para desenvolvedores.

Estabilização de APIs Reativas e de Renderização

A v20 marca a promoção à estabilidade de diversas APIs fundamentais:

  • Reactivity Primitives: As APIs effect, linkedSignal e toSignal alcançaram o status de estável, oferecendo mecanismos robustos para a gestão de estado reativo.

       import { signal, effect } from '@angular/core'
    
    const counter = signal(0)
    effect(() => {
    	console.log(`Current count: ${counter()}`)
    })
    
    counter.update((n) => n + 1)
  • Incremental Hydration: A estratégia de hidratação progressiva do client-side agora é considerada estável, configurável via provideClientHydration(withIncrementalHydration()). A diretiva @defer nos templates permite o carregamento e hidratação sob demanda.

       import { provideClientHydration, withIncrementalHydration } from '@angular/platform-browser'
    // ...
    provideClientHydration(withIncrementalHydration())

    Nos templates você pode usar a diretiva @defer para hidratar partes do template sob demanda.

       @defer (hydrate on viewport) {
    <shopping-cart />
    }
  • SSR Route-Level Configuration: A capacidade de definir a estratégia de renderização no servidor por rota (RenderMode) é agora uma API estável, configurada através da propriedade data.renderMode nas definições de rota.

       export const routeConfig: ServerRoute = [
    	{ path: '/login', mode: RenderMode.Server },
    	{ path: '/dashboard', mode: RenderMode.Client },
    	{
    		path: '/product/:id',
    		mode: RenderMode.Prerender,
    		async getPrerenderParams() {
    			const dataService = inject(ProductService)
    			const ids = await dataService.getIds() // ["1", "2", "3"]
    			// `id` is used in place of `:id` in the route path.
    			return ids.map((id) => ({ id }))
    		}
    	}
    ]

Novo Recurso experimental

Agora, vamos supor que estamos recebendo dados de uma conexão WebSocket.

Para este propósito, podemos usar um recurso de streaming:

   @Component({
	template: `{{ dataStream.value() }}`
})
export class App {
	// Lógica de inicialização do WebSocket irá aqui...
	// ...
	// Inicialização do recurso de streaming
	dataStream = resource({
		stream: () => {
			return new Promise<Signal<ResourceStreamItem<string[]>>>((resolve) => {
				const resourceResult = signal<{ value: string[] }>({
					value: []
				})

				this.socket.onmessage = (event) => {
					resourceResult.update((current) => ({
						value: [...current.value, event.data]
					}))
				}

				resolve(resourceResult)
			})
		}
	})
}

Nesse exemplo minimalista, declaramos um novo recurso de streaming, que retorna uma Promise de um Signal.

O Signal tem um tipo de valor ResourceStreamItem<string[]>, que significa que o Signal pode ter o valor { value: string[] } ou {error: … } em caso de erro.

Emitimos os valores recebidos sobre a conexão WebSocket via o Signal resourceResult.

Agora, vamos criar um exemplo mais interessante, usando o recurso httpResource experimental:

   @Component({
	template: `{{ userResource.value() | json }}`
})
class UserProfile {
	userId = signal(1)
	userResource = httpResource<User>(() => `https://example.com/v1/users/${this.userId()}`)
}

O snippet acima irá enviar uma solicitação HTTP GET para a URL especificada sempre que o userId mudar.

O httpResource retorna um HttpResourceRef que tem uma propriedade value do tipo Signal que podemos acessar diretamente no template.

O userResource tem outros valores, como isLoading, headers e outros, como se vê abaixo.

Por baixo dos panos, o httpResource usa o HttpClient, portanto você pode especificar interceptores no provedor de HttpClient:

   bootstrapApplication(AppComponent, {
	providers: [provideHttpClient(withInterceptors([loggingInterceptor, cachingInterceptor]))]
})
Imagem com o texto: Curso testes automatizado com Angular e um Robo azul com uma logo do Angular atrás. Logo abaixo existe um botão com o texto 'Eu quero!'

Zoneless Change Detection em Developer Preview

O modelo de detecção de mudanças sem a dependência de Zone.js avança para o status de developer preview, habilitado através de provideZonelessChangeDetection().

   import { bootstrapApplication } from '@angular/platform-browser'
import { AppComponent } from './app/app.component'
import { provideZonelessChangeDetection, provideBrowserGlobalErrorListeners } from '@angular/core'

bootstrapApplication(AppComponent, {
	providers: [provideZonelessChangeDetection(), provideBrowserGlobalErrorListeners()]
})

Além disso, é possível adicionar o zoneless na criação de um novo projeto com o comando
ng new:

Adicionando Zoneless Change Detection

Adições à Sintaxe de Templates

A linguagem de templates do Angular foi expandida com novos operadores e funcionalidades:

  • Suporte a untagged template literals para interpolação de strings.

       <img [src]="`/assets/images/user-${userId()}.png`" />
  • Implementação dos operadores aritmético de exponenciação (**) e lógico in.

       <div>{{ base ** exponent }}</div>
    <div *ngIf="'propertyName' in object">Property exists</div>
  • Introdução do operador void para descarte explícito de valores de retorno de event listeners.

       import { Directive, HostListener } from '@angular/core'
    
    @Directive({
    	host: {
    		'(click)': 'void handleClick()'
    	}
    })
    export class NoPreventDefaultDirective {
    	handleClick(): boolean {
    		console.log('Handling click.')
    		return false // Without void, this would call preventDefault
    	}
    }

Aprimoramentos na Experiência de Desenvolvimento

  • Implementação de type checking para host bindings, configurável via typeCheckHostBindings no angularCompilerOptions do tsconfig.json.

       {
    	"angularCompilerOptions": {
    		"typeCheckHostBindings": true
    	}
    }

    No gif abaixo, vemos um exemplo de uso da validação de typeCheckHostBindings.

    Exemplo de uso da validação de typeCheckHostBindings

  • Refinamento da API do componente NgComponentOutlet através da introdução de inputs declarativos.

       import { Component, Input, Type } from '@angular/core'
    import { NgComponentOutlet } from '@angular/common'
    
    interface DynamicData {
    	message: string
    }
    
    @Component({
    	selector: 'app-dynamic-component',
    	template: `<p>{{ data.message }}</p>`
    })
    export class DynamicComponent {
    	@Input() data!: DynamicData
    }
    
    @Component({
    	selector: 'app-root',
    	template: `
    		<ng-container
    			[ngComponentOutlet]="dynamicComponentType"
    			[ngComponentOutletInputs]="{ data: dynamicInput }"
    		></ng-container>
    	`
    })
    export class AppComponent {
    	dynamicComponentType: Type<DynamicComponent> = DynamicComponent
    	dynamicInput: DynamicData = { message: 'Data from parent component' }
    }
  • Para criar dinamicamente um componente Angular, você pode usar a função createComponent.

    Na versão 20, foi introduzido novos recursos que permitem aplicar diretivas e especificar associações a componentes criados dinamicamente:

       import { createComponent, signal, inputBinding, outputBinding } from '@angular/core'
    
    const canClose = signal(false)
    const title = signal('Título do meu diálogo')
    
    // Criar MyDialog
    createComponent(MyDialog, {
    	bindings: [
    		// Vincular um signal ao input `canClose`.
    		inputBinding('canClose', canClose),
    
    		// Ouvir especificamente o evento `onClose` no diálogo.
    		outputBinding<Result>('onClose', (result) => console.log(result)),
    
    		// Cria vinculação bidirecional com a propriedade title
    		twoWayBinding('title', title)
    	],
    	directives: [
    		// Aplicar a diretiva `FocusTrap` ao `MyDialog` sem quaisquer associações.
    		FocusTrap,
    
    		// Aplicar a diretiva `HasColor` ao `MyDialog` e vincular o valor `red` ao seu input `color`.
    		// O callback para `inputBinding` é invocado em cada detecção de mudança.
    		{
    			type: HasColor,
    			bindings: [inputBinding('color', () => 'red')]
    		}
    	]
    })

    Acima, criamos um componente de Dialog e especificamos:

    • Vinculamos um signal ao input canClose da Dialog.
    • Escutamos o evento onClose usnado um callback que registra o resultado emitido.
    • Criamos two-data binding entre a input title da Dialog e o signal title usando a função twoWayBinding.

    Além disso, adicionamos as diretivas FocusTrap e HasColor ao componente.

    Observe que também podemos vincular valores para inputs da diretiva HasColor que aplicamos ao MyDialog.

Suporte experimental para vitest

Com a depreciação do Karma, o Angular CLI agora oferece suporte experimental para o vitest como um substituto para testes automatizados.

Para usar Vitest, execute:

   npm i vitest jsdom --save-dev

Atualize o arquivo angular.json:

   "test": {
    "builder": "@angular/build:unit-test",
    "options": {
        "tsConfig": "tsconfig.spec.json",
        "buildTarget": "::development",
        "runner": "vitest"
    }
}

Será atualizar também os testes do projeto para utilizar as funções do Vitest:

   ...
import { describe, beforeEach, it, expect } from 'vitest';
...

Atualizações no Guia de Estilo

O guia de estilo do Angular foi atualizado para modernizá-lo e simplificá-lo, com base no feedback da comunidade (RFC). As principais mudanças incluem:

  • Remoção de práticas de “code health” não específicas do Angular.
  • Movimentação de melhores práticas do Angular (não relacionadas a estilo) para a documentação.
  • Sufixos de nomes de arquivos e classes tornaram-se opcionais para reduzir código repetitivo e incentivar nomes mais intencionais.

A partir do Angular v20, o Angular CLI não gerará sufixos por padrão. Projetos existentes podem habilitá-los via ng update.

Para novos projetos, a geração de sufixos pode ser ativada com a seguinte configuração no angular.json:

     "projects": {
    "app": {
      // ...
      "schematics": {
        "@schematics/angular:component": { "type": "component" },
        "@schematics/angular:directive": { "type": "directive" },
        "@schematics/angular:service": { "type": "service" },
        "@schematics/angular:guard": { "typeSeparator": "." },
        "@schematics/angular:interceptor": { "typeSeparator": "." },
        "@schematics/angular:module": { "typeSeparator": "." },
        "@schematics/angular:pipe": { "typeSeparator": "." },
        "@schematics/angular:resolver": { "typeSeparator": "." }
      },
      // ...
    }
  }

Refletindo a evolução do Angular, a maior parte das orientações sobre NgModules foi removida.

O uso de @HostBinding e @HostListener foi revisto, favorecendo o objeto host nos metadados da diretiva, e lacunas no suporte a host binding foram corrigidas.

Breaking Changes e Deprecações

A atualização para a v20 implica em algumas alterações que exigem atenção:

  • Atualização das peer dependencies para Node.js, TypeScript e RxJS.

  • Remoção da emissão de atributos ng-reflect-* em modo de desenvolvimento.

  • Remoção da API InjectFlags.

       import { inject, Optional, Host } from '@angular/core'
    import { ParentComponent } from './parent.component'
    
    export class ChildComponent {
    	parent = inject(ParentComponent, { optional: true, host: true })
    }
  • Depreciação do suporte oficial ao HammerJS.

Conclusão

O Angular v20 representa um marco significativo na evolução contínua do framework, focando na estabilização de recursos poderosos, na modernização da experiência de desenvolvimento e na preparação para o futuro com inovações como a detecção de mudanças sem Zone.js.

As melhorias nas APIs reativas e de renderização, juntamente com as adições à sintaxe de templates e o suporte experimental ao Vitest, demonstram o compromisso da equipe do Angular em fornecer uma plataforma robusta, eficiente e agradável para os desenvolvedores.

Embora a atualização traga algumas breaking changes e deprecações, os benefícios em termos de performance, produtividade e alinhamento com as práticas modernas de desenvolvimento web certamente compensarão o esforço de migração.

O Angular v20 consolida sua posição como um framework de ponta, pronto para enfrentar os desafios dos projetos web mais exigentes.

Assine nossa Newsletter

Receba novos posts como esse na sua caixa de e-mail!

Sobre o autor

Author's photo
Henrique Custódia Arquiteto Frontend, entusiasta Angular, cat lover, criador de conteúdo e fundador da Code Dimension!