Deps opcionais em Angular
Geralmente quando crio componentes em Angular, é preciso fornecer uma maneira de customizar como a lógica do mesmo vai funcionar. Uma boa maneira para fazer isso, é criar uma classe de configuração com algumas propriedades que vão ditar como a lógica funcionará. Assim, poderíamos usar essa classe de configuração da seguinte forma em um componente:
// Classe de configuração
export class Config {
show: boolean = true;
}
// Componente que receberá a classe
@Component({
selector: 'component',
template: '<div *ngIf="show">Can you see me?</div>'
})
export class Component {
@Input() config: Config = new Config(); // instancia a classe para evitar erros
show: boolean = this.config.show;
}
// usando ambos
<component [config]="config"><component>
Passando a configuração via Input é uma maneira muito boa de customizar o comportamento de um componente. Porém, há casos em que precisamos usar determinado componente várias vezes pela aplicação e replicar a mesma configuração para todos os lugares que o componente está sendo usado será ruim para a manutenção da aplicação. Uma boa solução para isso é registrar a classe de configuração como um Provider:
// Classe de configuração e token de injeção
export const CONFIG: InjectionToken<Config> = new InjectionToken('Config')
export class Config {
show: boolean = true;
}
// Componente que consumirá a configuração
@Component({
selector: 'component',
template: '<div *ngIf="show">Can you see me?</div>'
})
export class Component {
@Input() config: Config;
show: boolean = this.config.show;
constructor(@Optional() @Inject(CONFIG) private config: Config) {
this.config = this.config || new Config();
}
}
Note que usamos o decorator Optional para evitar que o Angular não dispare um erro de injeção, caso o Provider da CONFIG não exista.
Seguindo o exemplo acima, poderíamos registrar a configuração customizada como um Provider para toda a aplicação poder consumir, sem precisar replicar a mesma.
// Customiza a configuração padrão
export class MyCustomConfig extends Config {
show: boolean = false;
}
@NgModule({
// Registra a configuração customizada
providers: [
{ provider: CONFIG, useClass: MyCustomConfig }
]
})
export class AppModule { }
Agora que foi registrado o Provider da CONFIG no módulo root da aplicação, a configuração customizada existirá para todos os lugares em que o componente for usado, assim não há necessidade de replicar a mesma configuração! Simples não?!
Eis o problema
Apesar da solução a cima ser muito boa em relação ao reuso, caso o Provider da CONFIG precise ser usado em outro componente, service ou diretiva, teríamos que replicar o tratamento para quando o Provider da Config não existir:
constructor(@Optional() @Inject(CONFIG) private config: Config) {
this.config = this.config || new Config();
}
Eis a solução
A solução para melhorar ainda mais o reuso e evitar problemas com manutenção desse código, é criar um Factory Provider para quando o Provider da CONFIG não existir, a factory retornará uma instância padrão da configuração. Dito isso, vamos ver como fica a implementação:
// Classe de configuração e token de injeção
export const CONFIG: InjectionToken<Config> = new InjectionToken('Config');
export class Config {
show: boolean = true;
}
// Função factory que verificará se existe um provider
export function configFactory(parent: Config): Config {
return parent || new Config();
}
// Componente que consumirá a configuração
@Component({
selector: 'component',
template: '<div *ngIf="show">Can you see me?</div>',
providers: [
{
provide: CONFIG,
useFactory: configFactory,
deps: [
[new Optional(), new SkipSelf(), new Inject(Config)],
]
}
]
})
export class Component {
@Input() config: Config;
show: boolean = this.config.showSomething;
constructor(@Inject(CONFIG) private config: Config) { }
}
Note que a propriedade deps recebe um array com as instâncias dos decorators Optional, SkipSelf e Inject. Basicamente estamos dizendo ao Injector do Angular:
- Optional: o Provider da CONFIG pode não existir, então não dispare erros.
- SkipSelf: o Injector deve procurar Provider da CONFIG apenas nos injectors parents e não no mesmo Injector do componente.
- Inject: o Injector deve injetar o Provider da CONFIG.
Agora ficou bom!
Após todas essas alterações, agora podemos injetar o Provider da Config sem precisar fazer qualquer tratamento, pois a config factory estará cuidando disso para nós!
Se esse artigo ajudou você de alguma forma não deixe de compartilhar e recomendar, garanto que existem muitas pessoas com as mesmas necessidades!
Obrigado por ler e até a próxima! 😃