Reputation: 1633
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
Assuming the reason above is the reason this isn't working, how do I make the directive wait for the DOM to be "ready"?
If my assumption is wrong, why isn't it working then?
Upvotes: 7
Views: 2306
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
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
Reputation: 9562
You can observe for the $viewContentLoaded event that $rootScope will broadcast.
Upvotes: 0
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