2

I use a tooltip directive from Angular UI Bootstrap inside one of my own directives. I want to manually toggle tooltip's visibility using tooltip-is-open attribute that Bootstrap Tooltip provides. Angular UI documentation states:

tooltip-is-open <WATCHER ICON> (Default: false) - Whether to show the tooltip.

So I assume it watches on the attribute's value. And so I want to bind a scope variable tooltipIsOpen to the attribute, hoping that changing tooltipIsOpen value in my directive's link function would toggle the tooltip visibility.

The behaviour that I get is weird.

  1. If I bind tooltipIsOpen value as an expression: tooltip-is-open="{{ tooltipIsOpen }}", the tooltip doesn't appear at all, though I see that the DOM is updated properly (tooltip-is-open="false switches to tooltip-is-open="true" and back again to false) in reaction to mouse events.

  2. If I bind tooltipIsOpen value as a variable: tooltip-is-open="tooltipIsOpen", the tooltip apears properly after the first mouse event, but then doesn't react to any further events, staying displayed all of the time with no way to hide it.

  3. The working solution I found is bind tooltipIsOpen as a function call :scope.tooltipIsOpenFun = function() { return tooltipIsOpen; } and tooltip-is-open="tooltipIsOpenFun()", or as an object property: tooltip-is-open="tooltip.isOpen". Only then the tooltip works fine - shows and hides all of the time.

I suspect it's related to how AngularJS watchers work, also to the difference in how JavaScript primitives and objects are treated when assigning the values. Still I can't understand it.

Question

Can somebody explain to me step by step why the solution 1 doesn't work and 2 works only once?

Directive template (for method 2)

<div uib-tooltip="Tooltip message"
     tooltip-is-open="tooltipIsOpened">
   ...
</div>

Directive link function

module(...).directive(..., function() {
    return {
        link: function(scope, elem, attr) {
            /* Stop further propagation of event */
            function catchEvent(event) {
                event.stopPropagation();
            };

            function showTooltip(event) {
                scope.$apply(function() {
                    scope.tooltipIsOpened = true;

                    /* Hide tooltip on any click outside the select */
                    angular.element(document).on('click', hideTooltip);
                    elem.on('click', catchEvent);
                });
            };

            function hideTooltip() {
                scope.$apply(function() {
                    scope.tooltipIsOpened = false;

                    /* Remove previously attached handlers */
                    angular.element(document).off('click', hideTooltip);
                    elem.off('click', catchEvent);
                });
           };

           scope.tooltipIsOpened = false;

           elem.on('mouseenter', showTooltip);
           elem.on('mouseleave', hideTooltip);
        });
2
  • what about toggleTooltip example on link you gave? 1st way is just incorrect, 2nd way you should use apply in click, not when you register event. Commented Jan 27, 2016 at 14:33
  • @PetrAveryanov: Why is the 1st way incorrect? Why do you say I should use $apply() inside click in 2nd method? It is used, only inside a click handler hideTooltip(). Commented Jan 27, 2016 at 14:49

1 Answer 1

3

1st way is incorrect because this directive expects variable name, not variable value. So correct:

<div uib-tooltip="Tooltip message" tooltip-is-open="xxx">

Or correct (you pass {{}} but then you pass not value of variable but value of variable that stores variable name):

<div ng-init="yyy='xxx'">
    <div uib-tooltip="Tooltip message" tooltip-is-open="{{yyy}}">

2nd way astually works somehow: http://plnkr.co/edit/OD07Oj2A0tfj60q2QKHe?p=preview But it is very strange - how user supposed to click outside element without triggering mouseleave event?

The thing is that you do not need all this, just:

<button uib-tooltip="Tooltip message" trigger="mouseenter"
     tooltip-is-open="tooltipIsOpened">
   hello
</button>

works fine.

3rd way: if you see that 'open' does not work, but 'smth.open' works that usually means you faced 'dot issue' - you can not change parent scope variable from child scope directly, but you can change variable properties. There is a lot of examples and explanations of this issue.

Sign up to request clarification or add additional context in comments.

4 Comments

You wondered how user would be able to click outside an element without first trigerring mouseleave. It's possible in my app, because the element (I simplified it to <div> to make this example cleaner) is a select component with a drop-down menu. After clicking element in a drop-down menu, it disappears and you're outside the select element without trigerring mouseleave.
Can you give me some example of the 'dot issue' related to angular watchers?
What is a trigger attribute in your last example?
What does a trigger attribute do?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.