pedropeixoto
pedropeixoto

Reputation: 1633

Wait for DOM to be ready on an AngularJS directive

I'm building a "tour guide" for my angular powered website.

I looked around for options and Intro.js seemed like the best fit. It had an Angular Directive already ready and everything: Angular Intro.js.

Everything worked as expected, until I had to add a step to first (and only the first) a DOM object that is being injected by a ng-repeat binding. I gave all ng-repeat items a unique ID (via $index) but Intro.js just fails to acknowledge it.

I'm guessing Intro is trying to find the DIV by the ID specified, but since the ng-repeat isn't complete yet, there's no DIV by that name.

I made a plunker where you can see that it' working on static content but fails to aknowledge the objects inside the ng-repeat.

Relevant code:

HTML:

<!-- Works -->
<div id="static">This is static content.</div>

<!-- Doesnt work -->
<div id="item{{$index}}" ng-repeat="item in items">
    {{ item.name }}
</div>

Angular Controller:

  $scope.IntroOptions = {
        steps:[
        {
            element: document.querySelector('#static'),
            intro: "This is static content"
        },
        {
            /** ID "item0" belongs to the first element on the ng-repeat **/
            element: document.querySelector('#item0'),
            intro: "Doesnt work!"
        }
        ],
        showStepNumbers: false,
        exitOnOverlayClick: true,
        exitOnEsc:true,
        nextLabel: '<strong>NEXT!</strong>',
        prevLabel: '<span style="color:green">Previous</span>',
        skipLabel: 'Exit',
        doneLabel: 'Thanks'
    };

Plunker: http://plnkr.co/edit/kE8P5Kq2Y5CVWEYgyBIo?p=preview

Upvotes: 7

Views: 2306

Answers (4)

mostafa shaheydari
mostafa shaheydari

Reputation: 21

.directive('onLastRepeat', function() {
    return function(scope, element, attrs) {
        if (scope.$last) setTimeout(function(){
            scope.$emit('onRepeatLast', element, attrs);
        }, 50);
};});

Use this directive like this :

<div id="item{{$index}}" ng-repeat="item in items" on-last-repeat>
    {{ item.name }}
</div>

in your controller wait for finish the loop and call introjs

$scope.$on('onRepeatLast', function (eve, elem) {
//call intro
});

Upvotes: 2

Matias Quaranta
Matias Quaranta

Reputation: 15603

I know this is quite old, but if anyone stumbles upon this answer, the best thing to do is to use a getter, it gets evaluated the moment it needs to access the property:

$scope.IntroOptions = {
    steps:[
    {
        element: document.querySelector('#static'),
        intro: "This is static content"
    },
    {
        /** ID "item0" belongs to the first element on the ng-repeat **/
        get element(){return document.querySelector('#item0')},
        intro: "Doesnt work!"
    }
    ],
    showStepNumbers: false,
    exitOnOverlayClick: true,
    exitOnEsc:true,
    nextLabel: '<strong>NEXT!</strong>',
    prevLabel: '<span style="color:green">Previous</span>',
    skipLabel: 'Exit',
    doneLabel: 'Thanks'
};

Upvotes: 0

Loc Nguyen
Loc Nguyen

Reputation: 9562

You can observe for the $viewContentLoaded event that $rootScope will broadcast.

Upvotes: 0

Alex
Alex

Reputation: 23300

The most clueless approach I'd attempt at first would be to wrap the dynamic content:

<div id="dynamic">
    <div id="item{{$index}}" ng-repeat="item in items">
        {{ item.name }}
    </div>
</div>

The scope variable then becomes

$scope.IntroOptions = {
    steps:[
    {
        element: document.querySelector('#static'),
        intro: "This is static content"
    },
    {
        element: document.querySelector('#dynamic'),
        intro: "This is dynamic content"
    }
    ],
// ... cut ...

It looks like your original code is deadlocking: the view depends on the data ($scope.items) which depends on the view (IntroOptions)

Upvotes: 0

Related Questions