From 440e23fa0b7d41e17f1bf33b62d7e1b3dd2cc03d Mon Sep 17 00:00:00 2001 From: Hakan <112955732+InalHakan@users.noreply.github.com> Date: Thu, 16 Jul 2026 11:31:42 +0200 Subject: [PATCH] feat: implement directives exercise --- angular.json | 3 +- .../shared/ui-common/click-with-warning.ts | 35 ++++++++ src/app/domains/shared/ui-common/tooltip.ts | 79 +++++++++++++++++++ src/app/shell/about/about.html | 24 ++++++ src/app/shell/about/about.ts | 7 ++ 5 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 src/app/domains/shared/ui-common/click-with-warning.ts create mode 100644 src/app/domains/shared/ui-common/tooltip.ts diff --git a/angular.json b/angular.json index 83193047..bb2d4681 100644 --- a/angular.json +++ b/angular.json @@ -3,7 +3,8 @@ "version": 1, "cli": { "packageManager": "npm", - "schematicCollections": ["angular-eslint"] + "schematicCollections": ["angular-eslint"], + "analytics": "955b7c74-ae29-4025-8907-29a55529de8a" }, "newProjectRoot": "projects", "projects": { diff --git a/src/app/domains/shared/ui-common/click-with-warning.ts b/src/app/domains/shared/ui-common/click-with-warning.ts new file mode 100644 index 00000000..46f8b973 --- /dev/null +++ b/src/app/domains/shared/ui-common/click-with-warning.ts @@ -0,0 +1,35 @@ +import { Dialog } from '@angular/cdk/dialog'; +import { Directive, inject, input, output } from '@angular/core'; + +import { ConfirmComponent } from '../util-common/confirm'; + +@Directive({ + selector: '[appClickWithWarning]', + exportAs: 'clickWithWarning', + host: { + class: 'btn btn-danger', + '(click)': 'handleClick($event.shiftKey)', + }, +}) +export class ClickWithWarning { + private readonly dialog = inject(Dialog); + + readonly warning = input('Are you sure?'); + readonly appClickWithWarning = output(); + + handleClick(shiftKey: boolean): void { + if (shiftKey) { + this.appClickWithWarning.emit(); + return; + } + + const ref = this.dialog.open(ConfirmComponent, { + data: this.warning(), + }); + ref.closed.subscribe((result) => { + if (result) { + this.appClickWithWarning.emit(); + } + }); + } +} diff --git a/src/app/domains/shared/ui-common/tooltip.ts b/src/app/domains/shared/ui-common/tooltip.ts new file mode 100644 index 00000000..3e037ca1 --- /dev/null +++ b/src/app/domains/shared/ui-common/tooltip.ts @@ -0,0 +1,79 @@ +import { + afterRenderEffect, + DestroyRef, + Directive, + ElementRef, + EmbeddedViewRef, + inject, + input, + TemplateRef, + ViewContainerRef, +} from '@angular/core'; + +@Directive({ + selector: '[appTooltip]', + host: { + '(mouseover)': 'setHidden(false)', + '(mouseout)': 'setHidden(true)', + }, +}) +export class Tooltip { + private readonly viewContainer = inject(ViewContainerRef); + private viewRef: EmbeddedViewRef | undefined; + private readonly host = inject(ElementRef); + private readonly destroyRef = inject(DestroyRef); + + readonly template = input | undefined>(undefined, { + alias: 'appTooltip', + }); + + constructor() { + afterRenderEffect(() => { + const template = this.template(); + if (!template) { + return; + } + this.initTooltip(template); + }); + + this.destroyRef.onDestroy(() => { + this.removeTooltip(); + }); + } + + private removeTooltip() { + if (this.viewRef) { + this.viewRef.destroy(); + this.viewRef = undefined; + } + } + + initTooltip(template: TemplateRef): void { + if (this.viewRef) { + this.viewRef.destroy(); + } + + this.viewRef = this.viewContainer.createEmbeddedView(template); + + this.viewRef?.rootNodes.forEach((nativeElement) => { + nativeElement.className = 'tooltip'; + nativeElement.hidden = true; + }); + } + + setHidden(hidden: boolean): void { + this.viewRef?.rootNodes.forEach((nativeElement) => { + if (!hidden) { + this.updatePosition(nativeElement); + } + nativeElement.hidden = hidden; + }); + } + + private updatePosition(toolTipElement: HTMLElement) { + const r = this.host.nativeElement.getBoundingClientRect(); + toolTipElement.style.left = `${r.left + r.width / 2}px`; + toolTipElement.style.top = `${r.top - 8}px`; + toolTipElement.style.transform = 'translate(-50%, -100%)'; + } +} diff --git a/src/app/shell/about/about.html b/src/app/shell/about/about.html index 5c337103..a66ed393 100644 --- a/src/app/shell/about/about.html +++ b/src/app/shell/about/about.html @@ -1,3 +1,27 @@

About

by AngularArchitects.io

+
+ +
+ + +
+

2 Tips for Success

+
    +
  1. Don't tell everything!
  2. +
+
+
+
+ +

 

+ + diff --git a/src/app/shell/about/about.ts b/src/app/shell/about/about.ts index 69d93077..3c8451b8 100644 --- a/src/app/shell/about/about.ts +++ b/src/app/shell/about/about.ts @@ -1,9 +1,12 @@ import { ChangeDetectionStrategy, Component, signal } from '@angular/core'; +import { ClickWithWarning } from '../../domains/shared/ui-common/click-with-warning'; +import { Tooltip } from '../../domains/shared/ui-common/tooltip'; import { Flight } from '../../domains/ticketing/data/flight'; @Component({ selector: 'app-about', + imports: [ClickWithWarning, Tooltip], templateUrl: './about.html', changeDetection: ChangeDetectionStrategy.OnPush, }) @@ -40,4 +43,8 @@ export class About { prices: [], }, ]); + + protected deleteAll(): void { + console.log('Delete all flights'); + } }