Angular's Portal Pattern: Why You Need a Root Component in Taiga UI

My colleague Roman recently announced the release of our new component library for Angular Taiga UI. The Getting started instructions say that the application needs to be wrapped in a tui-root



. Let's figure out what it does, find out how and why we use portals and what they are in general.





What is a portal?

Imagine a component select



. It has a dropdown with options to choose from. If you store it in the same place in the DOM as the component itself, you can run into a number of problems. Downstream elements can pop out on top, and the container can cut off the content:





z-index



, Z . 100, 10000, 10001. , overflow: hidden



. ?





. z-index



. ยซยป. Root- Taiga UI . :





<tui-scroll-controls></tui-scroll-controls>
<tui-portal-host>
    <div class="content"><ng-content></ng-content></div>
    <tui-dialog-host></tui-dialog-host>
    <ng-content select="tuiOverDialogs"></ng-content>
    <tui-notifications-host></tui-notifications-host>
    <ng-content select="tuiOverNotifications"></ng-content>
</tui-portal-host>
<ng-content select="tuiOverPortals"></ng-content>
<tui-hints-host></tui-hints-host>
<ng-content select="tuiOverHints"></ng-content>
      
      



tui-dialog-host



, tui-portal-host



โ€” . -. . Taiga UI . . :





@Injectable({
   providedIn: 'root',
})
export class TuiPortalService {
   private host: TuiPortalHostComponent;

   add<C>(componentFactory: ComponentFactory<C>, injector: Injector): ComponentRef<C> {
       return this.host.addComponentChild(componentFactory, injector);
   }

   remove<C>({hostView}: ComponentRef<C>) {
       hostView.destroy();
   }

   addTemplate<C>(templateRef: TemplateRef<C>, context?: C): EmbeddedViewRef<C> {
       return this.host.addTemplateChild(templateRef, context);
   }

   removeTemplate<C>(viewRef: EmbeddedViewRef<C>) {
       viewRef.destroy();
   }
}
      
      



. , , โ€” . , , .





, - . , ยซยป, .





, . :





  1. . Material .





  2. , . .





  3. .





. , . , requestAnimationFrame



. โ€” , . . , . , . , . .









โ€” . Obscured. .





, . , .





:





<section
   *ngFor="let item of dialogs$ | async"
   polymorpheus-outlet
   tuiFocusTrap
   tuiOverscroll="all"
   class="dialog"
   role="dialog"
   aria-modal="true"
   [attr.aria-labelledby]="item.id"
   [content]="item.component"
   [context]="item"
   [@tuiParentAnimation]
></section>
<div class="overlay"></div>
      
      



, ngFor



, . , . , . , .





, . Taiga UI โ€” . - iOS Android. . , .





Observable



. , โ€” . . . , , DI- POLYMORPHEUS_CONTEXT



. content



observer



. complete



, , next



. , , . :





const DIALOG = new PolymorpheusComponent(MyDialogComponent);
const DEFAULT_OPTIONS: MyDialogOptions = {
   label: '',
   size: 's',
};

@Injectable({
   providedIn: 'root',
})
export class MyDialogService extends AbstractTuiDialogService<MyDialogOptions> {
   protected readonly component = DIALOG;
   protected readonly defaultOptions = DEFAULT_OPTIONS;
}
      
      



.





, Taiga UI, ng-polymorpheus . , , , .





tuiFocusTrap



. , DOM, , . - โ€” :





@HostListener('window:focusin.silent', ['$event.target'])
onFocusIn(node: Node) {
   if (containsOrAfter(this.elementRef.nativeElement, node)) {
       return;
   }

   const focusable = getClosestKeyboardFocusable(
       this.elementRef.nativeElement,
       false,
       this.elementRef.nativeElement,
   );

   if (focusable) {
       focusable.focus();
   }
}
      
      



root-. root , tuiOverscroll



. CSS- overscroll-behavior. . , , . , , .





: tui-root?

, . , root-. tui-scroll-controls



. , . <ng-content select="tuiOverDialogs"></ng-content>



. Taiga UI. , .





root event manager plugin` DI. โ€” . , TuiRootModule



BrowserModule



, . : โ€” .





, root-. Taiga UI open-source, ยซยป npm. StackBlitz-. , , !








All Articles