No curso da evolução de nossa biblioteca de componentes de IU da Taiga, começamos a notar que alguns componentes mais complexos tĂȘm @Input apenas para passar seu valor para @Input de nosso outro componente bĂĄsico dentro deles. Ăs vezes, hĂĄ esse aninhamento mesmo em trĂȘs camadas.
Fizemos isso com algumas diretivas complicadas chamadas controladores. Eles resolveram completamente o problema de aninhamento e reduziram o peso da biblioteca.
Neste artigo, vou mostrar como organizamos um sistema comum de configuraçÔes para todos os campos de entrada, graças a esse conceito e aos recursos de DI no Angular.
Campo de texto no antigo "Taiga": um bom caso quando vocĂȘ pode usar controladores
Temos um componente bĂĄsico de entrada chamado Campo de Texto Primitivo.
Este componente Ă© uma entrada nativa com o estilo do nosso tema com um invĂłlucro para ele. Ele nĂŁo funciona com formulĂĄrios angulares e Ă© necessĂĄrio para construir controles completos.
A primeira versĂŁo do textfield era bastante simples e foi usada em vĂĄrios componentes de entrada compostos. Mas logo começou a ficar mais complicado: novos recursos foram adicionados e o nĂșmero de @Inputs para o componente cresceu cada vez mais.
«» Textfield 17 . :
@Inputâ , , . , textfield - 17 .
@Inputâ , . : @Inputs â . 10 , . .
, .
@Inputâ , . , , : ( ).
@Inputâ , . , . - :
@Directive({
selector: '[tuiHintContent]'
})
export class TuiHintControllerDirective {
@Input('tuiHintContent')
content: PolymorpheusContent = ââ;
@Input('tuiHintDirection')
direction: TuiDirection = 'bottom-left';
@Input('tuiHintMode')
mode: TuiHintMode | null = null;
}
â @Inputâ , . âtuiHintContentâ, .
. DI . , .
@Inputâ OnPush-, @Inputâ. , , @Input . Controller, :
export abstract class Controller implements OnChanges {
readonly change$ = new Subject<void>();
ngOnChanges() {
this.change$.next();
}
}
ngOnChanges, . :
@Directive({
selector: '[tuiHintContent]'
})
export class TuiHintControllerDirective extends Controller {
// ...
}
, change$ . â ChangeDetectorRef, markForCheck change$. , :
constructor(
@Inject(ChangeDetectorRef) private readonly changeDetectorRef: ChangeDetectorRef,
@Optional()
@Inject(TuiHintControllerDirective)
readonly hintController: TuiHintControllerDirective | null,
) {
if (!hintController) {
return;
}
hintController.change$.pipe(takeUntil(this.destroy$)).subscribe(() => {
changeDetectorRef.markForCheck();
});
}
. â .
, âtuiHintContentâ textfield .
: - @Inputâ . .
, : , .
, null, DI- Angular:
constructor(
@Inject(TUI_HINT_WATCHED_CONTROLLER)
readonly hintController: TuiHintControllerDirective,
) {}
. TUI_HINT_WATCHED_CONTROLLER :
export const TUI_HINT_WATCHED_CONTROLLER = new InjectionToken('watched hint controller');
export const HINT_CONTROLLER_PROVIDER: Provider = [
TuiDestroyService,
{
provide: TUI_HINT_WATCHED_CONTROLLER,
deps: [[new Optional(), TuiHintControllerDirective], ChangeDetectorRef, TuiDestroyService],
useFactory: hintWatchedControllerFactory,
},
];
export function hintWatchedControllerFactory(
controller: TuiHintControllerDirective | null,
changeDetectorRef: ChangeDetectorRef,
destroy$: Observable<void>,
): Controller {
if (!controller) {
return new TuiHintControllerDirective();
}
controller.change$.pipe(takeUntil(destroy$)).subscribe(() => {
changeDetectorRef.markForCheck();
});
return controller;
}
, HINT_CONTROLLER_PROVIDER. âprovidersâ , deps ChangeDetectorRef TuiDestroyService. , ngOnDestroy , ( , ).
:
@Component({
//...
providers: [HINT_CONTROLLER_PROVIDER],
})
export class TuiPrimitiveTextfieldComponent {
constructor(
//...
@Inject(TUI_HINT_WATCHED_CONTROLLER)
readonly hintController: TuiHintControllerDirective,
) {}
}
, . @Inputâ .
: DI , .
: hintWatchedControllerFactory , . , .
?
. @Inputâ , . : , â , . , . DI , .
-, , . â .
DI , , , . , , DI, API.