Построение компонентов с выпадающими блоками с помощью Angular и Material CDK

от автора

Каждое приложение использует компоненты с выпадающими блоками. Такие панели используются в выпадающем списке, Autocomplete, Tooltip и т.д. В Material CDK есть инструмент Overlay для создания такого функционала.

Зачем это нужно? Z-index и вперед!

С точки зрения простого html и css, мы можем использовать Z-index. Например, для dropdownmenu есть следующий рецепт. Но как писал Александр Инкин в своей статье, мы получим войну миров z-index. У Taiga UI есть свой способ решить эту проблему. Рассмотрим альтернативный инструмент от команды Material — CDK Overlay.

Кто применяет

Может показаться, что Material CDK обязывает использовать нас библиотеку компонентов Angular Material, но это не так. CDK и его базовые стили можно подключить отдельно. Благодаря этому команда создателей CDK дала возможность использовать их наработки в своих проектах. Более того, популярные и крупные проекты тоже активно используют этот CDK. 

Библиотеки компонентов, которые используют Material CDK:

  • Material

  • Ant Design (ng-zorro)

Какие компоненты можно сделать на базе CDK overlay:

  • Tooltip 

  • Dialog

  • Select

  • Autocomplete 

  • DropDawnMenu 

  • TreeSelect

  • Calendar

Почему бы не попробовать использовать его в своей работе, ведь он облегчает решение задач. Ниже мы рассмотрим примеры, как его использовать, и создадим компонент.

Начало работы, подключим CDK

Установить пакет

npm i @angular/cdk

Можно работать без основных материал компонентов, для этого нужно подключить стиль CDK

По ходу создания компонентов будем описывать Api CDK Overlay. Итак, приступим к созданию Tooltip!

Требования к Tooltip

Перед началом реализации подумаем, как мы его хотим использовать и из чего он будет состоять. Для этого зададим к нему небольшие требования.

  • Не использует Z-index!

  • Может позиционироваться как над, так и под элементом, к которому применяется подсказка и зависит от пространства сверху/снизу. Если нет места сверху на экране, он должен отображаться снизу.

  • Работает в 2-х режимах. Первый скрывает подсказку, если исчезает фокус с элемента (так работает Material). Второй не исчезает (так работает ng-zorro), чтобы можно было скопировать текст или совершить какие-либо действия.

  • Может отображать простой текст или шаблон.

  • Должен иметь легкий API для использования.

Архитектура Tooltip

  • Директива — используется в компоненте, к которому применяется Tooltip

  • Компонент — это отображение самого Tooltip.

Принцип работы

  1. Директива добавляет функциональность к компоненту. При событии Mouseenter будет создаваться компонент. В работе будут использованы следующие API Angular:

    ComponentFactoryResolver и ViewContainerRef — с их помощью будет создаваться компонент TooltipComponent.

    ElementRef — для получения ссылки на host элемент, она потребуется для OverlayCdk.

    Renderer2 — с его помощью него можно подписаться на события (Mouseenter)

  2. После создания инстанса компонента ему устанавливаются параметры:

    title — простой текст;

    view — ссылка на шаблон;

    origin — ссылка на host компонент.

  3. Теперь самое интересное, работа с СDK Overlay:

    cdkConnectedOverlay — директива для декларативного создания всплывающих элементов;

    cdkConnectedOverlayOrigin — сюда передается наш host, элемент относительно которого будет создаваться всплывающая область;

    cdkConnectedOverlayPositions — список возможного позиционирования всплывающей области. В ней можно настроить всплывающую область сверху, снизу, слева, справа и задать дополнительный класс css. В примере настройка для всплытия снизу и сверху.

Код директивы

@Directive({   selector: '[ui-tooltip]',   exportAs: 'uiTooltip' }) export class TooltipDirective implements OnInit, OnDestroy, AfterViewInit {   @Input()   content: string;   @Input()   view: TemplateRef<any>;   component: TooltipComponent;   componentFactory: ComponentFactory<     TooltipComponent   > = this.resolver.resolveComponentFactory(TooltipComponent);   protected readonly disposables: Array<() => void> = [];    constructor(     private elementRef: ElementRef,     private hostView: ViewContainerRef,     private renderer: Renderer2,     private resolver: ComponentFactoryResolver   ) {}    ngOnInit(): void {}    ngAfterViewInit(): void {     this.registerTriggers();   }    createComponent(): void {     const componentRef = this.hostView.createComponent(this.componentFactory);     this.component = componentRef.instance as TooltipComponent;     this.component.setTitle(this.content);     this.component.setView(this.view);     this.component.setOverlayOrigin({ elementRef: this.elementRef });     this.component.mouseleave.subscribe(x => {       this.component.hide();     });   }    registerTriggers(): void {     const el = this.elementRef.nativeElement;     const listnerMouseenter = this.renderer.listen(el, 'mouseenter', () => {       this.createComponent();     });     this.disposables.push(listnerMouseenter);     const listnerMouseleave = this.renderer.listen(el, 'mouseleave', () => {       console.log(el);       setTimeout(x => {         if (!this.component.isActive) {           this.component.hide();         }       }, 50);     });     this.disposables.push(listnerMouseleave);   }    ngOnDestroy(): void {     this.disposables.forEach(dispose => dispose());   } }

Код Tooltip

@Component({   selector: 'uikit-tooltip',   exportAs: 'uikitTooltip',   changeDetection: ChangeDetectionStrategy.OnPush,   encapsulation: ViewEncapsulation.None,   template: `     <ng-template       #overlay="cdkConnectedOverlay"       cdkConnectedOverlay       [cdkConnectedOverlayOrigin]="origin"       [cdkConnectedOverlayOpen]="visible"       [cdkConnectedOverlayPositions]="positions"       [cdkConnectedOverlayPush]="true"       ,       (detach)="hide()"       (positionChange)="positionChange($event)"     >       <div #tooltip class="tooltip-container">         <div class="tooltip-body">           <div *ngIf="title">{{ title }}</div>           <div *ngIf="view">             <ng-template [ngTemplateOutlet]="$any(view)"></ng-template>           </div>         </div>       </div>     </ng-template>   `,   styleUrls: ['./tooltip.component.scss'] }) export class TooltipComponent implements OnInit, AfterViewInit {   title: string;   view: TemplateRef<any>;   isActive = false;    @ViewChild('overlay', { static: false }) overlay!: CdkConnectedOverlay;   @ViewChild('tooltip', { static: false }) tooltip!: ElementRef;   @Output() mouseleave = new EventEmitter();   origin!: CdkOverlayOrigin;   positions = [     new ConnectionPositionPair(       { originX: 'center', originY: 'top' },       { overlayX: 'center', overlayY: 'bottom' },       0,       0,       'tooltip-body-top'     ),     new ConnectionPositionPair(       { originX: 'center', originY: 'bottom' },       { overlayX: 'center', overlayY: 'top' },       0,       0,       'tooltip-body-bottom'     )   ];   visible = false;   constructor(public cdr: ChangeDetectorRef) {}   overlayRef: any;    ngOnInit(): void {}    ngAfterViewInit() {     fromEvent<any>(this.tooltip.nativeElement, 'mouseenter').subscribe(       (event: any) => {         this.isActive = true;         this.cdr.markForCheck();       }     );     fromEvent<any>(this.tooltip.nativeElement, 'mouseleave').subscribe(       (event: any) => {         this.isActive = false;         this.cdr.markForCheck();         this.mouseleave.emit();       }     );   }    setOverlayOrigin(origin: CdkOverlayOrigin): void {     this.origin = origin;     this.show();   }    hide(): void {     this.visible = false;     this.cdr.markForCheck();   }    show(): void {     this.visible = true;   }    setTitle(title: string): void {     this.title = title;   }    setView(view): void {     this.view = view;   }    positionChange($event) {   } }

Результат работы

Angular Tooltip CDK
Angular Tooltip CDK

Заключение

Мы изучили Material CDK Overlay. Сделали Tooltip и познакомились с некоторыми важными API Angular. Эта статья поможет вам создавать свои прекрасные компоненты на базе CDK. А в следующей части мы рассмотрим создание других компонентов на базе CDK.

К примеру нужно относиться как к заготовке и ознакомлением с CDK Overlay. Для того чтобы использовать его в проекте, нужно будет доработать следующие моменты:

  • отписаться от событий;

  • покрыть тестом;

  • прикрутить палитру из ваших переменных темы.

ссылка на оригинал статьи https://habr.com/ru/company/europlan/blog/561494/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *