0

Our webapplication currently has a collapsable side menu, the logic of which is handled via an external library called CleanZone. The problem occurs with loading submenu items. These are appended to the DOM after page load when the menu is collapsed, causing click event handlers (passed via ng-click) to not respond.

Normally for stuff like this, we'd create a directive and append it to the DOM via the $compile service. However in this case, I need to rely on the logic of this external library, which itself doesn't use AngularJS.

Is it possible to somehow catch whatever this library added to the HTML, recompile it through AngularJS, so that the ng-click event works for it?

Here's the code to help understand the problem:

The DOM contains this at all times:

<ul class="sub-menu">
    <li>
        <a ng-click="someFunction()" href="#">Text</a>
    </li>
</ul>

If the menu is not collapsed, above submenu is shown, as it was already in the DOM, ng-click just works.

However when the menu is collapsed, the user has to hover over an icon on the menu, and the submenu is then appended dynamically:

<div id="sub-menu-nav">
    <ul class="sub-menu">
        <li>
            <a ng-click="someFunction()" href="#"></a>
        </li>
    </ul>
</div>

Here's the relevant part of how that library adds the element:

/*SubMenu hover */
var tool = $("<div id='sub-menu-nav' style='position:fixed;z-index:9999;'></div>");

function showMenu(_this, e) {
    if (($("#cl-wrapper").hasClass("sb-collapsed")) {
        var menu = $("ul", _this);
        tool.appendTo("body");
        tool.html('<ul class="sub-menu">' + menu.html() + '</ul>');
        tool.show();
    } else {
        tool.hide();
    }
}

$(".cl-vnavigation li").hover(function (e) {
    showMenu(this, e);
}, function (e) {
    tool.removeClass("over");
    setTimeout(function () {
        if (!tool.hasClass("over") && !$(".cl-vnavigation li:hover").length > 0) {
            tool.hide();
        }
    }, 500);
});

I've attempted to use a directive, but it has the same behaviour ie. only the element that already was in the DOM initially will respond to click events.

For completeness sake, here's the directive:

app.directive("testAnchor", function ($compile) {
    return {
        restrict: "E",
        replace: true,
        scope: { onClick: '&' },
        template: '<a href="#" ng-click="onClick({arg1: string, arg2: string})>TestText!</a>',
        link: function (scope, element, attrs) {
            $compile(element.contents())(scope.$new());
        }
    }
});

And I call it like this:

<test-anchor on-click="myFunction(argument1, argument2)"></test-anchor>

I had hoped AngularJS would compile the appended directive, but it apparently doesn't work that way.

So: Can AngularJS compile HTML appended by an external library after load?

Sangman
  • 171
  • 10

2 Answers2

1

Maybe try bootstrap angularjs into created dynamically elements as below

UPD: update code for handle events.

var button = $('#action').click(createSomething);

function createSomething() {
  $(document.body).append('<div id="dynamic"><div>Dynamic created</div><app-dir></app-dir></div>');
  angular.bootstrap(
    document.getElementById('dynamic'),
    ['app']
  );
}

angular.module('app', [])
  .directive('appDir', appDir);

function appDir() {
  return {
    restrict: 'E',
    template: '<h2 ng-click="clickHandler()">Hello from Angularjs</h2>',
    link: appDirLinkFn
  };
}

function appDirLinkFn($scope) {
  $scope.clickHandler = clickHandler;
  
  function clickHandler() {
    console.log('Click from angularjs elememnt');
  }
}
#dynamic {
  margin: 1rem;
  background-color: #ccc;
  border-color: #999;
  border-radius: 5px;
  padding: 1rem;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id="action">Create something</button>
eustatos
  • 686
  • 1
  • 10
  • 21
  • This answer has no click event handling IN the dynamically created element? – Sangman Feb 13 '19 at 10:53
  • updated code in the answer. Now click `Hello from Angularjs` – eustatos Feb 13 '19 at 11:12
  • The other problem with this is that I need to update the code of the external library to call angular.bootstrap. I'd prefer not to do this, I'd have to somehow subscribe AngularJS to some kind of DOM append event and then call the bootstrap function I'm guessing. I tried the angular-dom-events approach listed here, but it didn't work for me (same behaviour still) https://stackoverflow.com/questions/21332671/angularjs-watch-dom-change – Sangman Feb 13 '19 at 13:23
  • Can you give the links to resources about `cleanZones` that you used? Possible, that in its api has needed opportunity. – eustatos Feb 13 '19 at 15:13
  • I found a solution for my problem, will create a separate answer for it – Sangman Feb 13 '19 at 16:18
0

From what I understand the reason that the event handlers were not triggering for the click event, is because the DOM elements being referred to must already exist at the time the event handler is defined.

As I didn't want to update the code (ie. the hover event handler) in the external library, I instead declared a second hover (actually: mouseover) event handler in my Angular controller.

This event handler will then create a second event handler for the click event, after a short delay (which in theory should allow enough time for the other handler to append the DOM element).

Something like this:

$scope.registerClickEvent = function () {
    if (!$scope.clickEventRegistered) {
        setTimeout(function () {
            $('.testclass').click(
                function (event) {
                    $scope.someFunction(event);
                }
            );
        },
        100);

        $scope.clickEventRegistered = true;
    }
}

And in the HTML, I set ng-mouseover on the element that also listens for the hover event:

<ul class="cl-vnavigation" ng-mouseover="registerClickEvent()"></ul>

.testclass is set on the anchor element inside the unordered list.

Sangman
  • 171
  • 10