Каждое приложение использует компоненты с выпадающими блоками. Такие панели используются в выпадающем списке, 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.
Принцип работы
-
Директива добавляет функциональность к компоненту. При событии Mouseenter будет создаваться компонент. В работе будут использованы следующие API Angular:
ComponentFactoryResolver и ViewContainerRef — с их помощью будет создаваться компонент TooltipComponent.
ElementRef — для получения ссылки на host элемент, она потребуется для OverlayCdk.
Renderer2 — с его помощью него можно подписаться на события (Mouseenter)
-
После создания инстанса компонента ему устанавливаются параметры:
title — простой текст;
view — ссылка на шаблон;
origin — ссылка на host компонент.
-
Теперь самое интересное, работа с С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) { } }
Результат работы

Заключение
Мы изучили Material CDK Overlay. Сделали Tooltip и познакомились с некоторыми важными API Angular. Эта статья поможет вам создавать свои прекрасные компоненты на базе CDK. А в следующей части мы рассмотрим создание других компонентов на базе CDK.
К примеру нужно относиться как к заготовке и ознакомлением с CDK Overlay. Для того чтобы использовать его в проекте, нужно будет доработать следующие моменты:
-
отписаться от событий;
-
покрыть тестом;
-
прикрутить палитру из ваших переменных темы.
ссылка на оригинал статьи https://habr.com/ru/company/europlan/blog/561494/
Добавить комментарий