How We Make Basic Taiga UI Components More Flexible: The Concept of Component Controllers in Angular

In the course of the evolution of our Taiga UI component library, we began to notice that some more complex components have @Input just in order to pass its value into the @Input of our other base component within themselves. Sometimes there is such nesting even in three layers.





We did it with some tricky directives called controllers. They completely solved the problem of nesting and reduced the weight of the library.





In this article, I will show you how we have organized a common system of settings for all input fields thanks to this concept and the DI capabilities in Angular.





Textfield in the former "Taiga": a good case when you can use controllers

We have a basic input component called Primitive Textfield.





This component is a native input styled like our theme with a wrapper for it. It does not work with Angular forms and is needed to build full-fledged controls. 





The very first version of textfield was quite simple and was used in several composite input components. But soon it began to become more complicated: new features were added, and the number of @Inputs for the component grew more and more. 





«» 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.








All Articles