O futuro do Angular sem NgModules: soluções leves em cima de componentes independentes

Andrew Rosário
6 min readDec 22, 2021

--

Atenção: Este artigo foi originalmente escrito por Manfred Steyer. Veja o link original aqui.

Componentes independentes tornam o futuro das aplicações Angular mais leves. Não precisamos mais de NgModules. Em vez disso, usamos apenas módulos EcmaScript.

Eles sempre foram um dos pontos mais controversos: Angular Modules ou, abreviadamente, NgModules. Alguns gostam dos módulos porque permitem que classes relacionadas, como componentes, pipes ou diretivas sejam agrupadas. Outros querem um futuro sem módulos e, em vez disso, apenas optam pelo sistema de módulos oferecido pela EcmaScript.

A equipe do Angular está agora assumindo essa tarefa e trabalhando para tornar os NgModules opcionais. Um documento para Standalone Components e um RFC (Request for Comments) associado já existe há algum tempo. Além disso, a equipe do Angular também oferece uma demo que permite que você experimente as ideias planejadas até então. Embora esta demo não seja para uso em produção, ele nos mostra como será um mundo sem NgModules.

Eu atualizei uma de nossas apps com base nesta demo. A implementação resultante mostra como a eliminação de NgModules pode afetar nossas futuras arquiteturas Angular. O seu código pode ser encontrado na forma de um CLI Workspace e como um Nx Workspace que usa bibliotecas como um substituto para os NgModules.

NgModules — indesejado, mas necessário

Na verdade, os Angular Modules que já foram usados ​​no AngularJS 1.x não devem ser implementados. Em vez disso, a equipe ficou feliz em anunciar sua descontinuação em 2014, quando o Angular 2 foi anunciado em Paris. Em uma apresentação que provavelmente entrará para a história do framework, uma lápide foi mostrada para cada conceito do AngularJS 1.x que não era mais necessário. A verdade era que todos esses conceitos haviam se tornado obsoletos: em parte por causa da nova arquitetura do Angular, em parte porque certos conceitos já estavam planejados para a próxima iteração do EcmaScript.

Este último foi o caso com os NgModules, que parecia obsoleto devido ao sistema de módulos planejado para o EcmaScript 2015. A comunidade ficou ainda mais surpresa quando um dos últimos release candidates apareceu com os NgModules. A principal razão para isso era pragmática: precisávamos de uma maneira de agrupar as classes que são usadas ​​juntas. Não apenas para aumentar a conveniência para os desenvolvedores, mas também para o Compilador do Angular, cujo desenvolvimento ficou um pouco para trás. Neste último caso, estamos falando sobre o contexto de compilação. A partir desse contexto, o compilador aprende onde o código do programa tem permissão para chamar quais componentes.

No entanto, a comunidade nunca ficou muito feliz com essa decisão. Ter outro sistema de módulos além do EcmaScript não parecia certo. Além disso, aumentou a barreira de entrada para novos desenvolvedores Angular. É por isso que a equipe projetou o novo compilador Ivy para que a aplicação compilada funcione sem módulos em tempo de execução. Cada componente compilado com Ivy tem seu próprio contexto de compilação. Mesmo que isso pareça grandioso, esse contexto é representado apenas por dois arrays que se referem a componentes, diretivas e pipes.

Como o compilador antigo e o ambiente de execução associado foram removidos permanentemente do Angular a partir da versão 13, era hora de ancorar essa opção na API pública do Angular. Há algum tempo existe um documento e um RFC associado. Ambos descrevem um mundo onde os módulos Angular são opcionais. A palavra opcional é importante aqui: o código existente que depende de módulos ainda é suportado.

O Modelo Mental

O modelo mental por trás dos Componentes Independentes é realmente simples. Imagine um componente que tem seu próprio NgModule:

[...]
import {Component} from './standalone-shim';
import {NavbarComponent, SidebarComponent} from './shell';

@Component ({
standalone: ​​true,
selector: 'app-root',
imports: [
NavbarComponent,
SidebarComponent,
HomeComponent,
AboutComponent,
HttpClientModule,
RouterModule.forRoot ([...])
],
template: `[...]`
})
export class AppComponent {
}

Isso é semelhante ao padrão SCAM de Lars Nielsen . No entanto, embora o SCAM use um módulo explícito, aqui falamos apenas de um módulo pensado.

O sinalizador standalone: ​​true indica que este é um componente independente. A nova propriedade imports define o contexto de compilação, ou seja, o número de todos os building blocks que o componente pode usar. Isso pode ser usado para importar outros componentes independentes, mas também NgModules existentes.

A lista exaustiva de todos esses building blocks torna o componente sobrevivente por conta própria e, portanto, aumenta sua capacidade de reutilização. Também nos força a pensar sobre as dependências do componente. Infelizmente, essa tarefa acaba sendo extremamente monótona e demorada.

Portanto, há considerações para implementar uma espécie de auto-import no Angular Language Service utilizado pelas IDEs. De forma análoga à importação automática para módulos TypeScript, a IDE escolhida também pode sugerir colocar a entrada correspondente no array de imports na primeira vez que um componente, pipe ou diretiva é usado no template.

Compatibilidade com Código Existente

A decisão de tratar componentes independentes como módulos, entre outras coisas, também é a chave para a compatibilidade com o ecossistema existente. Isso significa que os módulos existentes podem ser integrados em componentes independentes. O componente na lista anterior importa, por exemplo, oRouterModule incluindo sua configuração e o HttpClientModuledesta forma.

Mas também do ponto de vista das seções de código existentes que usam NgModules, o modelo mental garante compatibilidade, especialmente porque os módulos Angular também podem importar componentes independentes:

@NgModule ({
imports: [SomeStandaloneComponent],
exports: [...],
declarations: […],
providers: […],
})
export class SomeModule {}

É interessante aqui que componentes independentes são importados como módulos e não declarados como componentes clássicos. Isso pode ser confuso, mas se encaixa no modelo mental que vê um componente independente tanto como um componente quanto como um NgModule reutilizável.

Componentes independentes de inicialização

Até agora, os módulos também eram necessários para o bootstrapping, especialmente porque as funções fornecidas pelo Angular para esse fim previam um módulo com um componente de bootstrap. A aplicação, portanto, forneceu o componente principal e seu contexto de compilação. No futuro, será possível inicializar um único componente. A demo fornece um método bootstrapComponentpara essa finalidade, que pode ser usado em main.ts:

bootstrapComponent ( AppComponent );

Componentes independentes e Lazy Loading

O modelo mental discutido também garante que o lazy loading de componentes independentes funcione com o router. O router atualmente é capaz de obter módulos por meio do lazy loading. Uma vez que um componente independente também é visto como um módulo, ele já funciona desta forma:

{
path: 'flight-booking',
loadChildren: () =>
import ('./ booking / flight-booking.component')
.then (m => m.FlightBookingComponent ['module'])
// module property: solução alternativa ^^
}

A propriedade do módulo mostrada aqui é apenas uma construção auxiliar da demo usada, que na verdade cria explicitamente um módulo por building block autônomo por debaixo dos panos. No entanto, a implementação real dispensará essa prática. Além disso, já existem ideias para dar ao router alguns ajustes para que ele possa interagir melhor com componentes autônomos.

Desde a introdução do Ivy, os componentes individuais também podem ser obtidos por meio de lazy loading:

@Component ({
standalone: ​​true,
selector: 'app-about',
template: `
<h1> About </h1>
<ng-container #container> </ng-container>
`
})
export class AboutComponent {

@ViewChild ('container', {read: ViewContainerRef})
viewContainer!: ViewContainerRef;

async ngOnInit () {
const esm = await import ('./ lazy / lazy.component');
const ref = this.viewContainer.createComponent (esm.LazyComponent)
ref.instance.title = `I'm so lazy today !!`;
}

}

O exemplo carrega o componente com um import dinâmico e instancia-o dentro de ViewContainerRef. Antes do Angular 13, a aplicação precisava obter uma factory para o componente ser carregado. Agora, createComponentaceita a classe do componente diretamente.

Essa abordagem é possível desde o lançamento do Ivy. No entanto, o contexto de compilação do componente é perdido se o módulo associado não foi armazenado no mesmo arquivo. Agora como um componente independente define seu próprio contexto, essa solução alternativa não é mais necessária.

Pipes, Diretivas e Serviços

Análogos aos componentes independentes, pipes independentes e diretivas independentes também podem ser usadas. Para esse propósito, os Decorators pipee directivetambém terão uma propriedade standalone. É assim que um pipe independente será:

@Pipe ({
standalone: ​​true,
name: 'city',
pure: true
})
export class CityPipe implements PipeTransform {

transform (value: string, format: string): string {[…]}

}

E aqui está um exemplo de uma diretiva independente:

@Directive ({
standalone: ​​true,
selector: 'input [appCity]',
providers: […]
})
export class CityValidator implements Validator {

[...]

}

Graças aos tree-shakable providers, os serviços têm funcionado sem módulos há muito tempo. Para este efeito, a propriedade providedIndeve ser utilizada:

@Injectable ({
providedIn: 'root'
})
export class FlightService {[…]}

Além disso, sempre foi possível configurar um provider para um componente. O serviço se aplica a este componente e a todos aqueles na árvore de componentes abaixo. A propriedade providers do Decorator Component é usada para isso:

@Component ({
standalone: ​​true,
imports: [...],

// Define um provider para o componente:
providers: [FlightService],

selector: 'flight-search',
template: `[...]`
})
export class FlightSearchComponent {[…]}

Conclusão provisória: componentes independentes — e agora?

Até agora, vimos como usar componentes independentes para tornar nossas aplicações Angular mais leves. Também vimos que o modelo mental subjacente garante compatibilidade com o código existente.

No entanto, agora surge a questão de como tudo isso influenciará na nossa estrutura e arquitetura de aplicações. A próxima parte desta curta série lançará alguma luz sobre isso.

Próxima parte: O futuro do Angular sem NgModules: como eles afetam nossas arquiteturas

--

--

Andrew Rosário

Desenvolvedor Front-end, mentor e palestrante. Apaixonado por tecnologia e por compartilhar conhecimento.