Angularjs directive with ng-repeat and ajax - angularjs

I have a directive which links to a jquery ticker plugin call. Inside the directive i have some elements repeating for the plugin to cycle through. The data in the elements itself is containing from an ajax request.
Here is the code
myApp.directive('vTicker', ['$timeout', function($timeout) {
return {
link: function($scope, iElm, iAttrs, controller) {
$timeout(function () {
// console.log($(iElm).html());
$(iElm).vTicker($scope.$eval(iAttrs.vTicker));
}, 0);
}
};
}]);
And here is the element
<div id="newsticker" v-ticker="{speed: 400,pause: 6000}">
<ul class="list-unstyled">
<li class="news-item" ng-repeat="newsItem in news">
{{newsItem.issuer}} : {{newsItem.headline}}
</li>
</ul>
The problem is when the directive renders, the ng-repeat hasnt completed rendering. $timeout is not helping either as the content is coming from an ajax call in the controller.
Any help? I want the directive to wait rendering until the controller get the data and ng-repeat render the content in the dom.
Thanks.

Assuming that the items get added to news array. What you need to do is watch on the news array and see when it gets filled and then apply your ticker.
Your current directive definition does not create a new scope so the news array would be accessible so yo can do something like
link: function($scope, iElm, iAttrs, controller) {
$scope.$watch("news", function(newValue,oldValue) {
if(newValue && newValue.length > 0) {
$timeout(function () {
// console.log($(iElm).html());
$(iElm).vTicker($scope.$eval(iAttrs.vTicker));
}, 0);
}
}
}
}
Now if you reassign your news array this watch would fire. You can pass the news array using isolated scope too.

Related

Directive link function not called

I have a portion of view that refreshes itself, say the div hides when an API call is in progress and shows up when the response is obtained.
This portion of view (div) has a angular directive.
View
<div ng-controller="myCtrl>
<input type="button" ng-click="callAPI()">
<div ng-show="isAPICallComplete">
<p data-my-directive="something" ng-repeat="name in names">{{name}}</p>
</div>
</div>
Directive
angular.module('myModule')
.controller('myCtrl', function ($scope, $http) {
$scope.callAPI = function () {
$http.get('someURL').then(function (response) {
$scope.isAPICallComplete = true;
$scope.names= response.names;
});
}
})
.directive('myDirective', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
console.log('reached directive');
}
}
});
With the above code, on page load the API call is already complete and hence the div shows up which then invokes the angular directive and I could see the log in console. But when on other conditions the API is called, the div hides itself and shows up again. In this case, the angular directive is not invoked (I don't see the console log message).
You can Do:
Just change the ng-show to ng-if it will work,
As the DOM will be created again on using ng-if
Just thought it is worth mentioning
ng-if removes or adds the element to the DOM whereas ng-show only hides or shows the element using css properties.

How to get jQuery to wait until after AngularJS has rendered an input element to set focus on it?

I have an input element being created in an AngularJS directive:
<span ng-repeat="phrasePart in phraseParts">
{{phrasePart}}<input ng-attr-id="{{ 'phrasePart-' + $index }}" ng-if="!$last"/>
</span>
and I want the first input element i.e. (<input id="phrasePart-0" /> to receive focus, so I do this with jQuery:
$(function () {
$("#phrasePart-0").focus();
});
However, this code does set focus in an input element that is directly in the HTML and not rendered by AngularJS.
How can I get jQuery to wait until the input element "phrasePart-0" is created by AngularJS before setting focus?
Is there an "AngularJS way" to set focus that would be better?
The more 'Angular' way to do it would to use a directive.
then apply that directive to the first occurance as a class
Just checked this. This is not the case because ng-class does not recompile dom elements for angular directives.
Instead, this may be a more reliable solution:
http://jsfiddle.net/HB7LU/9958/
.directive("focusme", function(){
return {
scope:{
focusme:"="
},
link:function(scope, element, attrs, $timeout){
if (scope.focusme){
element[0].focus();
}
}
}
})
and for the HTML
<span ng-repeat="phrasePart in phraseParts">
{{phrasePart}}<input ng-attr-id="{{ 'phrasePart-' + $index }}" ng-if="!$last" focusme="$first" />
</span>
Use, $timeout
Check out this fiddle
http://jsfiddle.net/cndhpqrz/
var myApp = angular.module('myApp',[]);
function myfunc($scope, $timeout)
{
$scope.phraseParts=[1,2,3,4,5,6];
$timeout(function()
{
$("#phrasePart-2").focus();
});
}
use $timeout service of angularjs.
$timeout handler will be called after the digest cycle completes. So at that time you will have your input rendered.
$timeout(function () {
$("#phrasePart-0").focus();
});

Directive does not have access to controller function (AngularJS)

When I declare a directive to use that accesses a controller function, it cannot find the controller.
Here is my directive declared in app.js:
app.directive("delete", function() {
return {
restrict: 'A',
link: function(scope, elem, attr, ctrl) {
elem.bind('click', function(e) {
alertCtrl.alert();
});
}
}
});
Here is my controller:
app.controller('AlertController', function() {
this.alert = function() {
alert('Ahah!');
}
});
And here is my HTML:
<div ng-controller="AlertController as alertCtrl">
<div ng-repeat="i in [1,2,3]">
<img src='image.png' delete />
</div>
</div>
If I click on the image, I get no alert and the console says that alertCtrl is not defined. How come alertCtrl is not defined when you click on the image with the delete directive on it?
If I change the controller to have $scope.alert = function()... it works fine. But I do not want this.
Also, is this the proper way to handle such a situation? If not, what is the best practice?
You need to call, it on the scope. scope has the property alertCtrl which is your controller instance. Your controller alias (alertCtrl) is available when you bind it on the html since scope is implicit there. but when you do it in the javascript i.e in the directive, or another controller you would need to get the controller instance (defined as alias) from the scope as a property.
scope.alertCtrl.alert();
Plnkr

How to force angular to render outside elements from the custom directive?

I need a small help in angularjs,
please have a look on this
code (chrome browser):
http://jsfiddle.net/Aravind00kumar/CrJn3/
<div ng-controller="mainCtrl">
<ul id="names">
<li ng-repeat="item in Items track by $index">{{item.name}} </li>
</ul>
<ak-test items="Items">
</ak-test>
</br>
<div id="result">
</div>
</div>
var app = angular.module("app",[]);
app.controller("mainCtrl",["$scope",function($scope){
$scope.Items = [
{name:"Aravind",company:"foo"},
{name:"Andy",company:"ts"},
{name:"Lori",company:"ts"},
{name:"Royce",company:"ts"},
];
$scope.Title = "Main";
}]);
app.directive("akTest",["$compile",function($compile){
return {
restrict: 'E',
replace: true,
scope: {
items: "="
},
link: function (scope, element, attrs) {
// var e =$compile('<li ng-repeat="item in Items track by $index">{{item.name}} </li>')(scope);
// $("#names").append(e);
var lilength = $("#names li").length;
var html ='<div> from angular ak-test directive: '+lilength+'</div>';
element.replaceWith(html);
}
};
}]);
$(function(){
$("#result").html('from jquery: '+$("#names li").length);
});
I have created a custom directive and trying to access an element from the view which in the ng-repeat above my custom directive
The problem is, in the directive it was saying ng-repeat not rendered yet.
Here is the problem
I have two elements
<svg>
<g>
List of elements
</g>
<g>
Based on the above rendered elements I have to draw a line between elements like a connection. I have to wait till the above elements to get render then only I can read the x,y positions and can draw a line.
</g>
</svg>
Both elements and the connections are scope variables. As per my understanding both are in the same scope and execution flow starts from parent to child and finishes from child to parent. How can I force above ng-repeat rendering part to complete before starting the custom directive?
is there any alternative available in angular to solve this dependency?
It's been a while, so my Angular is getting a bit rusty. But if I understand your problem correctly, it's one that I have run into a few times. It seems that you want to delay processing some elements of your markup until others have fully rendered. You have a few options for doing this:
You can use timeouts to wait for the page to render:
$timeout(function() {
// do some work here after page loads
}, 0);
This generally works ok, but can cause your page to flash unpleasantly.
You can have some of your code render in a later digest cycle using $evalAsync:
There is a good post on that topic here: AngularJS : $evalAsync vs $timeout. Typically, I prefer this option as it does not suffer from the same page flashing issue.
Alternatively, you can look for ways to refactor your directives so that the dependent parts are not so isolated. Whether that option would help depends a lot on the larger context of your application and how reusable you want these parts to be.
Hope that helps!
I would create a directive for the whole list, and maybe a nested directive for each list item. That would give you more control I would think.
Thanks a lot for your quick response #Royce and #Lori
I found this problem causing because of ng-repeat I have solved it in the following way..
Created a custom directive for list elements and rendered all elements in a for loop before the other directive start. This fix solved the problem temporarily but i'll try the $evalAsync and $timeout too :)
var app = angular.module("app",[]);
app.controller("mainCtrl",["$scope",function($scope){
$scope.Items = [
{name:"Aravind",company:"foo"},
{name:"Andy",company:"ts"},
{name:"Lori",company:"ts"},
{name:"Royce",company:"ts"},
];
$scope.Title = "Main";
}]);
app.directive("akList",["$compile",function($compile){
return {
restrict: 'A',
replace : false,
link: function (scope, element, attrs) {
var _renderListItems = function(){
$(element).empty();
for(var i=0;i<scope.Items.length; i++)
{
var li ='<li> '+ scope.Items[i].name +' </li>';
element.append(li);
}
};
_renderListItems(scope);
scope.$watch('Items.length', function (o, n) {
_renderListItems(scope);
}, true);
}};}]);
app.directive("akTest",["$compile",function($compile){
return {
restrict: 'E',
replace: true,
scope: {
items: "="
},
link: function (scope, element, attrs) {
var lilength = $("#names li").length;
var html ='<div> from angular ak-test directive: '+lilength+'</div>';
element.replaceWith(html);
}
};
}]);
$(function(){
$("#result").html('from jquery: '+$("#names li").length);
});

AngularJS: How create a new directive joining existents directives?

I would like improve my current solution.
I have a big menu using <ul> and <li> tags. I need show to the user only the <li> tags that they have permission to access.
I have resolved this problem using two directives: ng-init and ng-show
...
<li ng-init="ok=hasPemission('item1')" ng-show="ok">
Item 1
</li>
...
I needed to use 'ng-init' to get a stable hasPermission result. I can't use ng-show="hasPemission('item1')" because 'hasPemission' returns a new 'promise' object and the ng-show has problem with not stable expressions ([$rootScope:infdig] infinit $digest loop).
Now, I wanted create a new directive that join this both directives. I created this one but I think that there is a way to reuse these two existents directives.
This is my new directive:
.directive('myPermissionShow',['$q','$animate','AccessControl',
function ($q, $animate, AccessControl) {
return {
restrict:'A',
scope: {
resourceName: '#myPermissionShow'
},
link: function ($scope, $element) {
var user = AccessControl.getLoggedUser();
AccessControl.hasPermission(user,$scope.resourceName).then(
function (value) {
// I copied the line below from ngShow directive:
$animate[value ? 'removeClass' : 'addClass']($element, 'ng-hide');
}
);
}
}
}])
So, my html changed to:
...
<li my-permission-show="item1">
Item 1
</li>
<li my-permission-show="item2">
Item 2
</li>
...
Is there a way to create this new directive reusing the directives 'ng-init' and 'ng-show'?
Something like this:
!!!NOT WORKING CODE!!!
.directive('myPermissionShow',['AccessControl',
function (AccessControl) {
return {
restrict:'A',
replaceAttribute: true, /* this property does not exists... */
template: 'ng-init="val=hasPermission(user,resourceName)" ng-show="val"',
scope: {
resourceName: '#myPermissionShow'
},
controller: function ($scope, $element) {
$scope.hasPemission = AccessControl.hasPermission;
$scope.user = AccessControl.getLoggedUser();
}
}
}])
!!!NOT WORKING CODE!!!

Resources