2

I am trying to build a side navigation menu, but want to dynamically populate its items based on the state of the DOM (i.e. <section>s in my DOM).

However, I have some ng-ifs applied to various <section>s that are based on scope variables that are retrieved via AJAX ($http.get). So when the directive is compiled (or linked), these scope variables have not yet been retrieved and the ng-ifs are not yet stable (they are evaluated as if the variables are undefined).

Main HTML

<my-sidenav></my-sidenav>

<!-- Bunch of other content and structure -->

<section id="foo1" class="sidenav-item">...</section>
<section id="foo2" class="sidenav-item">...</section>
<section id="foo3" class="sidenav-item" ng-if="fooVar1 === fooVar2">...</section>
<section id="foo4" class="sidenav-item">...</section>
<section id="foo5" class="sidenav-item" ng-if="fooVar3 !== fooVar4">...</section>

Sidenav HTML

<div>
    <a ng-repeat="section in ctrl.sections track by section" href="#" ng-click="ctrl.scrollTo(section)">{{section}}</a>
</div>

Directive Definition

function mySidenav() {
    return {
        templateUrl: 'sidenav.html',
        controller: function() {
            var ctrl = this;

            // After template URL is linked up
            // !!! ng-if still not stable !!!
            ctrl.$postLink = function() {
                // Convert nodeList to array for easier manipulation
                var nodeList = document.querySelectorAll('.sidenav-item');
                var nodeArray = Array.prototype.slice.call(nodeList);

                // Extract section id
                ctrl.sections = nodeArray.map(function(el) {return el.id;});
            };

            ctrl.scrollTo = /*...*/
        },
        controllerAs: 'ctrl'
    };
}

What is the best way to access DOM elements on the page after ng-if expressions have been stabilized? I was thinking $timeout but wouldn't really know what a "safe value" would be.

Or can/should I somehow use $watch? Is there a way to get ctrl.sections to dynamically update?

lebolo
  • 2,120
  • 4
  • 29
  • 44
  • use the link function: https://docs.angularjs.org/guide/directive – juvian May 10 '16 at 17:18
  • Is `link` different from using `$postLink` in the controller? From the [docs](https://docs.angularjs.org/guide/directive): `$postLink()` - Called after this controller's element and its children have been linked. Similar to the post-link function this hook can be used to set up DOM event handlers and do direct DOM manipulation. – lebolo May 10 '16 at 17:37

2 Answers2

0

Use the link function like so:

function mySidenav() {
    return {
        templateUrl: 'sidenav.html',
        link: function(scope, elm) {
                // Convert nodeList to array for easier manipulation
                var nodeList = document.querySelectorAll('.sidenav-item');
                var nodeArray = Array.prototype.slice.call(nodeList);
                var sections = nodeArray.map(function(el) {return el.id;});   
        }
    };
}

http://codepen.io/nicholasabrams/pen/oxVxQa?editors=1010

AlphaG33k
  • 1,588
  • 1
  • 12
  • 24
  • 1
    Sorry, I'll edit, but the JavaScript shown in my question **is** a directive definition. And I assume `link` and `$postLink` are similar (based on the [docs](https://docs.angularjs.org/guide/directive) for `$postLink`) – lebolo May 10 '16 at 17:36
  • I edited as well due to the fact that you did indeed form a directive. Have you tried accessing the element via the elm passed as the second argument into the link fn – AlphaG33k May 11 '16 at 02:26
  • Yes I have, the `ng-if`s are still not stable at that point (the AJAX result is still being retrieved). I think the only way is to either use a `$timeout` or set up a `$watch` like in my [answer](http://stackoverflow.com/a/37145347/1370556) – lebolo May 11 '16 at 13:18
0

So $watchCollection works, but I'll keep this question open for feedback on this or other solutions.

I modified my directive definition to be

function tipSidenav() {
    return {
        templateUrl: '/tip/resources/html/sidenav.html',
        controller: ['$rootScope', function($rootScope) {
            var ctrl = this;

            $rootScope.$watchCollection(
                function(scope) {
                    return getSections();
                },
                function(newValue, oldValue, scope) {
                    ctrl.sections = newValue;
                }
            );

            ctrl.scrollTo = /*...*/;

            function getSections() {
                // More efficient way of gettings IDs
                var ids = [];

                var nodeList = document.querySelectorAll('.sidenav-item');
                for (var i = 0; i < nodeList.length; i++) {
                    ids.push(nodeList[i].id);
                }

                return ids;
            }
        }],
        controllerAs: 'ctrl'
    };
}
lebolo
  • 2,120
  • 4
  • 29
  • 44