O Que há De Novo No Angular 14 - Uma Mudança De Jogo
Descubra o que há de novo no Angular 14
Introdução
O Angular 14 foi lançado recentemente com vários recursos interessantes. E hoje, vou apresentá-los para você. São eles:
- Standalone Components
- Provedores de rota
- ENVIRONMENT_INITIALIZER
- Formulários tipados
- A Função inject()
- Configuração do título da página pela rota
- Autocomplete na CLI
- Injetores opcionais em Embedded Views
Então vamos começar!
Standalone components
Para a maioria das pessoas, a mudança mais significativa nesta versão é a possibilidade de criar componentes sem @NgModule
! Sim, Isso mesmo que você leu.
Antes do Angular 14
Se você está um pouco perdido, me deixe te mostrar uma estrutura de diretório para um componente clássico do angular:
home
|--home.component.html
|--home.component.css
|--home.component.ts
|--home.module.ts
bashhome
|--home.component.html
|--home.component.css
|--home.component.ts
|--home.module.ts
Nela, temos um arquivo .html
para o template, um arquivo .css
para os estilos, um arquivo .ts
para o componente e outro arquivo .ts
para o @NgModule
. Este último arquivo importa as dependências do nosso componente, declara o componente e também pode definir alguns provedores.
Uma nova possibilidade com Angular 14
No Angular 14, podemos fazer tudo isso diretamente no componente, sem precisar de um @NgModule
.
home
|--home.component.html
|--home.component.css
|--home.component.ts
bashhome
|--home.component.html
|--home.component.css
|--home.component.ts
Para fazer isso, basta definir a propriedade standalone
em nosso componente como true
.
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss'],
standalone: true,
})
TypeScript@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss'],
standalone: true,
})
Fique atento!
Fique atento:
- Standalone Component não é a nova maneira de declarar componentes! Ele é outra maneira de declarar componentes. A forma clássica de definir
@NgModules
não será obsoleta.
De fato. Ainda usarei @NgModules
em vez de standalone components porque gosto do isolamento que @NgModules
fornece.
- Não migre todo o seu aplicativo para standalone components ainda! Eles são muito recentes e levaremos um tempo para criar convenções e definir as melhores práticas. Recomendo esperar um pouco mais antes de pular de cabeça nessa nova possibilidade.
Provedores de rotas
Mas ei, se abandonarmos o @NgModule
e usarmos standalone components, como podemos definir o provedor por rota, como costumávamos fazer com @NgModule
s?
Para resolver isso, o Angular adicionou provedores de rotas. Então, basicamente, as definições de rota agora têm uma propriedade chamada providers
. Permitindo-nos fornecer valores apenas para os módulos e componentes renderizados por essa rota.
const NAME = new InjectionToken<string>('token');
export const routes: Routes = [
{
path: '',
component: HomeComponent,
providers: [
{
provide: NAME,
useValue: 'foo'
}
]
}
];
TypeScriptconst NAME = new InjectionToken<string>('token');
export const routes: Routes = [
{
path: '',
component: HomeComponent,
providers: [
{
provide: NAME,
useValue: 'foo'
}
]
}
];
ENVIRONMENT_INITIALIZER
(INICIALIZADOR DE AMBIENTE)
Outra coisa que costumávamos fazer com NgModules
era executar um script de configuração quando um módulo carregado com lazy load era inicializado. Em outras palavras, o constructor
de um módulo carregado com lazy load só seria executado quando o módulo fosse inicializado, e alguns desenvolvedores aproveitam isso para executar algum tipo de configuração.
export class HomeModule {
constructor() {
console.log('You can run something here');
}
}
TypeScriptexport class HomeModule {
constructor() {
console.log('You can run something here');
}
}
Como podemos fazer isso com standalone components? A solução é usar o token ENVIRONMENT_INITIALIZER
.
const routes: Routes = [
{
path: '',
pathMatch: 'full',
component: HomeComponent
providers:[
{
provide: ENVIRONMENT_INITIALIZER,
multi: true,
useValue: () => console.log("You can run something right here too")
}
]
}
]
TypeScriptconst routes: Routes = [
{
path: '',
pathMatch: 'full',
component: HomeComponent
providers:[
{
provide: ENVIRONMENT_INITIALIZER,
multi: true,
useValue: () => console.log("You can run something right here too")
}
]
}
]
Esse token nos permite fornecer uma função de configuração que será executada antes que o ambiente seja inicializado.
Se quisermos que ele seja executado quando navegarmos para uma rota, podemos fornecer esse token usando provedores de rota.
Isso tem o mesmo efeito que nossa solução anterior usando NgModule
constructor
s e tem o benefício adicional de ser mais explícito.
Formulários Tipados
Outro recurso muito aguardado do Angular 14 são os formulários tipados.
Antes da versão 14, os valores de formulário eram tipados como any
. Isso significa que perdiamos toda a incrível segurança de tipagem do TypeScript.
const control = new FormControl(""),
control.value
//=> control.value: any
TypeScript const control = new FormControl(""),
control.value
//=> control.value: any
A propósito, existe uma maneira segura de dizer que você não conhece o tipo de algo. Se você estiver interessado nisso, confira este vídeo de um minuto explicando as diferenças entre any
e unknown
.
De qualquer forma, não temos mais esse problema. Porque o Angular 14 tem tipos estritos para formulários. Portanto, se um FormControl
lidar com uma string
, o valor será tipado como uma string
em vez de any
.
const control = new FormControl(""),
control.value
//=> control.value: string | null
TypeScriptconst control = new FormControl(""),
control.value
//=> control.value: string | null
Funções injetáveis
Agora, esta é a característica mais interessante para mim.
Angular 14 introduziu as funções injetáveis. E se parecem muito com os React hooks. Isso nos dá um universo de possibilidades para reutilizar nosso código, a mais relevante é que agora podemos criar funções reutilizáveis que usam injeção de dependência internamente.
export function getPosts() {
const http = inject(HttpClient);
return http.get('/api/getPosts');
}
export class HomeComponent {
readonly posts = getPosts();
}
TypeScriptexport function getPosts() {
const http = inject(HttpClient);
return http.get('/api/getPosts');
}
export class HomeComponent {
readonly posts = getPosts();
}
Se você está tão interessado em programação funcional quanto eu, sabe que isso significa muito! Mas como o tio Ben disse uma vez:
Isso cria dependências implícitas em nossa função. No entanto, espero que minhas dependências de função sejam declaradas explicitamente nos argumentos da função. Então, se vejo uma função sem argumentos, imagino que ela não tenha dependências. Mas agora, podemos criar funções que parecem puras, porém injetam muitas dependências internamente, tornando-as mais difíceis de testar e mais acopladas ao framework Angular.
Uma boa solução é seguir a mesma convenção que temos no React, prefixar essas funções com “use
”. Dessa forma, você pode identificar facilmente funções que usam inject()
internamente.
export function useGetPosts() {
const http = inject(HttpClient);
return http.get('/api/getPosts');
}
export class HomeComponent {
readonly posts = useGetPosts();
}
TypeScriptexport function useGetPosts() {
const http = inject(HttpClient);
return http.get('/api/getPosts');
}
export class HomeComponent {
readonly posts = useGetPosts();
}
Definir o título da página pela rota.
Uma tarefa comum em aplicações Web é alterar o título da página em cada rota.
No Angular, isso costumava ser um processo muito manual, mas agora podemos simplesmente definir o título da página na definição da rota.
export const routes: Routes = [
{
path: '',
component: HomeComponent,
title: 'Home',
pathMatch: 'full'
}
];
TypeScriptexport const routes: Routes = [
{
path: '',
component: HomeComponent,
title: 'Home',
pathMatch: 'full'
}
];
Mas eu sei o que você está pensando:
Não se preocupe. Angular 14 pensou em você.
Se você precisar personalizar o título dinamicamente, poderá fazê-lo estendendo a classe TitleStrategy
do @angular/router
e fornecer sua nova estratégia de título personalizada em vez da padrão.
@Injectable({ providedIn: 'root' })
export class PageTitleStrategy extends TitleStrategy {
constructor(@Inject(Title) private title: Title) {
super();
}
override updateTitle(routerState: RouterStateSnapshot) {
const title = this.buildTitle(routerState);
if (title !== undefined) {
this.title.setTitle(`CompanyName | ${title}`);
} else {
this.title.setTitle(`CompanyName | Page without title`);
}
}
}
export const routes: Routes = [
{
path: '',
component: HomeComponent,
title: 'Home',
pathMatch: 'full'
}
];
bootstrapApplication(AppComponent, {
providers: [
importProvidersFrom(RouterModule.forRoot(routes)),
{ provide: TitleStrategy, useClass: PageTitleStrategy }
]
});
TypeScript@Injectable({ providedIn: 'root' })
export class PageTitleStrategy extends TitleStrategy {
constructor(@Inject(Title) private title: Title) {
super();
}
override updateTitle(routerState: RouterStateSnapshot) {
const title = this.buildTitle(routerState);
if (title !== undefined) {
this.title.setTitle(`CompanyName | ${title}`);
} else {
this.title.setTitle(`CompanyName | Page without title`);
}
}
}
export const routes: Routes = [
{
path: '',
component: HomeComponent,
title: 'Home',
pathMatch: 'full'
}
];
bootstrapApplication(AppComponent, {
providers: [
importProvidersFrom(RouterModule.forRoot(routes)),
{ provide: TitleStrategy, useClass: PageTitleStrategy }
]
});
Angular CLI Autocomplete
Outra coisinha legal é que agora temos um autocomplete na CLI do Angular.
Basta digitar ng
no seu terminal e pressionar TAB
para permitir que a CLI do Angular complete automaticamente seu comando ou forneça ajuda.
> ng se
//=> If you press TAB here, the CLI will complete
> ng serve
bash> ng se
//=> If you press TAB here, the CLI will complete
> ng serve
> ng
//=> If you press TAB here, the CLI will show a list of commands
> ng
add -- Adds support for an external library to your project.
analytics -- Configures the gathering of Angular CLI usage metrics.
build -- Compiles an Angular application or library into an outpu...
cache -- Configure persistent disk cache and retrieve cache sta...
...
bash> ng
//=> If you press TAB here, the CLI will show a list of commands
> ng
add -- Adds support for an external library to your project.
analytics -- Configures the gathering of Angular CLI usage metrics.
build -- Compiles an Angular application or library into an outpu...
cache -- Configure persistent disk cache and retrieve cache sta...
...
Ativando o autocomplete da CLI
Para ativá-lo, você deve executar o comando ng complete
e confirmar que deseja habilitar o autocomplete digitando Yes
.
> ng completion
? Would you like to enable autocompletion? This will set up your terminal so pressing
TAB while typing Angular CLI commands will show possible options and autocomplete arguments.
(Enabling autocompletion will modify configuration files in your home directory.)
> Yes
bash> ng completion
? Would you like to enable autocompletion? This will set up your terminal so pressing
TAB while typing Angular CLI commands will show possible options and autocomplete arguments.
(Enabling autocompletion will modify configuration files in your home directory.)
> Yes
Por fim, reinicie seu terminal para ativar o autocomplete.
Injetores opcionais em Embedded Views
Por último, mas não menos importante, agora podemos passar injetores opcionais em embedded views. No passado, tínhamos que usar outras APIs para fazer isso, como o ngComponentOutlet
.
@Directive({ selector: '[foo]' })
export class FooDirective implements OnInit {
constructor(
private vcr: ViewContainerRef,
private templateRef: TemplateRef<unknown>
) {}
ngOnInit(): void {
this.vcr.createEmbeddedView(
this.templateRef,
{}, // context
{
injector: Injector.create({
// pass an injector :)
providers: [
{
provide: 'foo',
useValue: 'bar'
}
]
})
}
);
}
}
TypeScript@Directive({ selector: '[foo]' })
export class FooDirective implements OnInit {
constructor(
private vcr: ViewContainerRef,
private templateRef: TemplateRef<unknown>
) {}
ngOnInit(): void {
this.vcr.createEmbeddedView(
this.templateRef,
{}, // context
{
injector: Injector.create({
// pass an injector :)
providers: [
{
provide: 'foo',
useValue: 'bar'
}
]
})
}
);
}
}
Não pare aqui
Essas foram as mudanças mais relevantes no Angular 14, na minha opinião. Se você quiser ver todas as alterações, você poderá vê-las verificando as referências.
Se você quiser se aprofundar no Angular,
E se sua empresa estiver procurando por desenvolvedores web remotos, considere entrar em contato comigo e minha equipe. Você pode fazer isso aqui.
Como sempre, as referências estão na descrição. Tenha um ótimo dia. E te vejo na próxima.
– Lucas
Referências
- Angular 14 - Academind on Youtube
- O que há de novo no revolucionário Angular 14 - Andrew Rosário on Medium
- Novidades no Angular v14 - Vida Fullstack
- What is new in Angular 14? - Nevzatopcu on Medium
- Angular v14 is now available! - Angular Blog on Medium
- What’s New in Angular v14 - Netanel Basal
- Unleash the Power of DI Functions in Angular - Netanel Basal
- Getting to know the ENVIRONMENT_INITIALIZER Injection Token in Angular - Netanel Basal
- Typed Reactive Forms in Angular — No Longer a Type Dream - Netanel Basal
- Handling Page Titles in Angular - Netanel Basal
- Angular Standalone Components: Welcome to a World Without NgModule - Netanel Basal
- Pass an Injector to Embedded Views - Netanel Basal
- Setting page title from the route module - Brandon Roberts
- Standalone components with Custom Title Strategy in Angular 14