Skip to content

Comments

refactor(tooltip): move all logic into the service#1535

Draft
spike-rabbit wants to merge 1 commit intomainfrom
refactor/tooltip/move-all-logic-into-the-service
Draft

refactor(tooltip): move all logic into the service#1535
spike-rabbit wants to merge 1 commit intomainfrom
refactor/tooltip/move-all-logic-into-the-service

Conversation

@spike-rabbit
Copy link
Member

We currently cannot attach tooltips dynamically
on our components if the host element is interactive element.

When having the tooltip as a service, we can do this.

Alternatives considered:

  • extend the tooltip directive
  • make the tooltip input a model

Describe in detail what your merge request does and why. Add relevant
screenshots and reference related issues via Closes #XY or Related to #XY.


Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the tooltip logic by moving it from the directive into a dedicated service. This is a good architectural improvement that should make it easier to use tooltips dynamically.

I've found a few issues with the implementation:

  • There's a critical issue regarding Server-Side Rendering (SSR) where browser-specific code is not properly guarded.
  • The tests contain a focused test suite (fdescribe) which must be removed.
  • There's an inconsistency in a delay value used in tests compared to the implementation.
  • A minor simplification can be made in one of the directives.

Please see my detailed comments for suggestions on how to address these points.

Comment on lines +30 to +51
) {
const nativeElement = this.element.nativeElement;

fromEvent(nativeElement, 'focus')
.pipe(takeUntil(this.destroy$))
.subscribe(event => this.onFocus(event));

fromEvent(nativeElement, 'mouseenter')
.pipe(
takeUntil(this.destroy$),
delayWhen(() => timer(500).pipe(takeUntil(fromEvent(nativeElement, 'mouseleave'))))
)
.subscribe(() => this.onMouseEnter());

private subscription?: Subscription;
fromEvent(nativeElement, 'focusout')
.pipe(takeUntil(this.destroy$))
.subscribe(() => this.onFocusOut());

fromEvent(nativeElement, 'mouseleave')
.pipe(takeUntil(this.destroy$))
.subscribe(() => this.onMouseLeave());
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The TooltipRef constructor directly subscribes to DOM events using fromEvent. This will fail during Server-Side Rendering (SSR) as these events and nativeElement are browser-specific. This change removes a previous isPlatformBrowser check, introducing a regression.

This violates a general rule for our repository: 'When using browser-dependent UI features like Angular CDK Overlay, ensure they are only executed in a browser environment. Use a check like isPlatformBrowser to prevent errors during Server-Side Rendering (SSR).'

To fix this, you should ensure this logic only runs in a browser environment. The cleanest way would be to inject PLATFORM_ID in SiTooltipService and pass it down to TooltipRef.

Here is an example of how you could implement this:

  1. Modify SiTooltipService to inject PLATFORM_ID and pass it to TooltipRef:

    // projects/element-ng/tooltip/si-tooltip.service.ts
    @Injectable()
    export class SiTooltipService {
      private overlay = inject(Overlay);
      private platformId = inject(PLATFORM_ID); // Add this
    
      createTooltip(config: { /*...*/ }): TooltipRef {
        // ...
        return new TooltipRef(
          getOverlay(config.element, this.overlay, false, config.placement),
          config.element,
          injector,
          this.platformId // Pass it here
        );
      }
    }
  2. Update TooltipRef to accept platformId and guard the browser-specific code:

    // projects/element-ng/tooltip/si-tooltip.service.ts
    class TooltipRef {
      // ...
      constructor(
        private overlayRef: OverlayRef,
        private element: ElementRef,
        private injector: Injector | undefined,
        private platformId: object // Add this
      ) {
        if (isPlatformBrowser(this.platformId)) {
          const nativeElement = this.element.nativeElement;
          // Move all fromEvent subscriptions inside this block
          fromEvent(nativeElement, 'focus')
            .pipe(takeUntil(this.destroy$))
            .subscribe(event => this.onFocus(event));
          // ... other subscriptions
        }
      }
      // ...
    }

You will also need to add the necessary imports for PLATFORM_ID and isPlatformBrowser.

References
  1. When using browser-dependent UI features like Angular CDK Overlay, ensure they are only executed in a browser environment. Use a check like isPlatformBrowser to prevent errors during Server-Side Rendering (SSR).

@spike-rabbit spike-rabbit force-pushed the refactor/tooltip/move-all-logic-into-the-service branch from bebbc60 to 534dc16 Compare February 17, 2026 10:06
We currently cannot attach tooltips dynamically
on our components if the host element is interactive element.

When having the tooltip as a service, we can do this.

Alternatives considered:
- extend the tooltip directive
- make the tooltip input a model
@spike-rabbit spike-rabbit force-pushed the refactor/tooltip/move-all-logic-into-the-service branch from 534dc16 to c72e8db Compare February 17, 2026 10:09
@@ -35,20 +85,25 @@ class TooltipRef {
const tooltipRef: ComponentRef<TooltipComponent> = this.overlayRef.attach(toolTipPortal);

const positionStrategy = getPositionStrategy(this.overlayRef);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somehow the auto placement looks different now, any idea why?

Image Image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants