Au cours de l'évolution de notre bibliothèque de composants Taiga UI, nous avons commencé à remarquer que certains composants plus complexes ont @Input juste pour transmettre sa valeur dans @Input de notre autre composant de base en eux-mêmes. Parfois, il y a une telle nidification même en trois couches.
Nous l'avons fait avec des directives délicates appelées contrôleurs. Ils ont complètement résolu le problème de l'imbrication et réduit le poids de la bibliothèque.
Dans cet article, je vais vous montrer comment nous avons organisé un système commun de paramètres pour tous les champs de saisie grâce à ce concept et aux capacités DI dans Angular.
Textfield dans l'ancien "Taiga": un bon cas où vous pouvez utiliser des contrôleurs
Nous avons un composant d'entrée de base appelé Primitive Textfield.
Ce composant est une entrée native de style comme notre thème avec un wrapper pour cela. Il ne fonctionne pas avec les formulaires angulaires et est nécessaire pour créer des contrôles à part entière.
La toute première version de textfield était assez simple et a été utilisée dans plusieurs composants d'entrée composites. Mais bientôt, cela a commencé à devenir plus compliqué: de nouvelles fonctionnalités ont été ajoutées et le nombre de @Inputs pour le composant a augmenté de plus en plus.
«» 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.