In a directive I need to have the width of an element, however this element is created in a ng-repeat. So when the code inside my link function runs, ngRepeat is not yet executed. I tried to delay things using $timeout but that didn't help
JS:
link: function (scope, element) {
$timeout(function () {
var width = $(element.find('li')).width(); // the li elements do not exist
});
}
HTML:
<ol>
<li ng-repeat="item in items">
<my-item>.....</my-item>
</li>
</ol>
I cannot use the <ol> element, because the <my-item> has styles like margins/paddings.
Any suggestions ?
From ng-repeat finish event
<ol>
<li ng-repeat="item in items" on-complete>
<my-item>.....</my-item>
</li>
</ol>
-
.directive('onComplete', function() {
return function(scope, element, attrs) {
angular.element(element).css('color','blue');
if (scope.$last){
window.alert("im the last!");
}
};
})
http://plnkr.co/edit/or5mys?p=preview
You could use the scope.$last to generate an event when the last item is done or just run the code you need in there.
You might also want to consider your design. Could there be any CSS solution for your problem? If not go ahead.
Related
Here is my code that i am struggling with:
.directive('mydirective', function() {
return {
template: '<ul>\
<li priority="500"><i></i>Inbox</li>\
<li priority="500"><i></i>Language</li>\
<li priority="1000"><i></i>Settings</li>\
<li priority="500"><i></i>Contact</li>\
<li priority="1000"><i></i>Help</li>\
</ul>',
link: function(v, e, a){
var elm = e[0].childNodes[0].children;
}
}
});
All i want to do is to add a class to list items that have let say the priority= 1000. I am able to get the element but when i do the loop i don't know how to filter the attribute so then i can add the class. Anyone came around this?
Thanks
If you are going to be sending data and using a ng-repeat then you should be able to use
ng-class="{'className': data.priority === 1000}"
Trying to remove an li from a ul using angular, successfully removing the element from the array but angularJS doesn't remove the li until it is hovered over / some action is taken on that specific li. Code follows:
app.js
myApp.run(function($rootScope, appAPIservice){
appAPIservice.getInterests().success(function (response) {
$rootScope.interests = [];
if (response.data) {
var interests = response.data;
for (var i = 0; i < interests.length; i++) {
$rootScope.interests.push(interests[i]));
}
}
});
});
index.html
<ul ng-controller="interestsController">
<li ng-repeat="interest in interests">
{{interest.parentName}} / {{interest.childName}}
<button ng-click="deleteInterest($index)"></button>
</li>
</ul>
controllers.js: deleteInterest is defined here
myApp.controller('interestsController', function($scope) {
$scope.deleteInterest = function(arrayIndex) {
$scope.interests.splice(arrayIndex, 1);
});
}
});
This produces the following output on page load:
<ul class="ng-scope" ng-controller="interestsController">
<li class="ng-scope" ng-repeat="interest in interests">
Other Parent/Other Child
<button ng-click="deleteInterest($index)"><i class="icon-close"></i></button>
</li>
</ul>
The problem occurs when clicking deleteInterest() button. The following classes get added to the list item class: ng-animate, ng-leave, ng-leave-active. Unfortunately, the list item remains in the list until the item is hovered over. At that point, the list item is successfully removed from the DOM.
<li class="ng-scope ng-animate ng-leave ng-leave-active" ng-repeat="interest in interests">
Some Parent / Some Child
<button ng-click="deleteInterest($index)"><i class="icon-close"></i></button>
</li>
I've tried wrapping the interestsController.deleteInterest's
$scope.interests.splice(arrayIndex, 1); line as
$scope.$apply(function(){
$scope.interests.splice(arrayIndex, 1);
});
but I receive an error message saying that $scope.$digest is already in progress.
Is there a way to force angularJS to remove all ng-leave items?
A couple of ideas. Instead of $scope.$apply, try
$timeout(function(){
$scope.interests.splice(arrayIndex, 1);
});
$timeout will internaly trigger the digest if necessary, but won't throw that error if a digest is already in progress.
Alternatively, how about just using CSS to hide the li until angular gets round to removing it?
.ng-leave { display:none; }
I have a similar problem with ng-repeat.
After setting item array length to 0 I must run timeout with long delay:
$timeout(function(){
do_some_action
}, 2000);
Even evalAsync doesn't work:
$scope.$evalAsync(function () {
do_some_action
});
Another workaround is simply don't consider old items:
var elems = angular.element(document.getElementsByClassName('repeated_items'));
_.each(elems, function (itm) {
if (itm.$$hashKey) return true;
do_some_action
});
I am trying to create a directive that can create multiple elements an replace the calling element with the multiple. Specifically I want to set the directive on a single list-item and have it create multiple list items w/o a wrapping element. (Using the <UL> for the directive works but prevent me from including 'static' items.) Here is the markup:
<ul>
<li>static first</li>
<li my-repeater="myVar"></li>
<li>static last</li>
</ul>
In my controller I'll define myVar:
$scope.myVar = ['one', 'two', 'three'];
And my directive looks like this:
myApp.directive('myRepeater', function () {
return {
restrict: 'A',
transclude: 'element',
replace: true, //<--- DEPRECATED
scope: {
val: '=myRepeater'
},
template: '<li ng-repeat="item in val">{{item}}</li>'
};
};
In AngularJS v1.2.26 this works UNLESS you remove 'replace', then you get nothing. Is this just not possible? I did note that in the docs for v1.3.4 that they feel:
There are very few scenarios where element replacement is required for the application function, ...
But my case above seems to be a clear example of the need for this, unless there is a 'better way'!...?
If you don't have to do it as an attribute, then you can do it using an element:
<div ng-app="myApp">
<div ng-controller="MyCtrl">
<ul>
<li>static first</li>
<my-repeater var="myVar"></my-repeater>
<li>static last</li>
</ul>
</div>
</div>
And the directive
.directive('myRepeater', function () {
return {
restrict: 'E',
scope: {
val: '=var'
},
template: '<li ng-repeat="item in val">{{item}}</li>'
};
})
I updated the fiddle to show it http://jsfiddle.net/6rjr8nq5/1/
Since unknown tags are just ignored, it works fine.
And if you need the attribute, you could stick it on the ul, transclude, and use the link function to properly place the static data, assuming you know where it goes.
I am using "draggable" directive to support image dragging. However, as per the role of the user, I need to disable image dragging for certain groups of users. I have used following code.
<!--draggable attribute is used as handle to make it draggable using jquery event-->
<li ng-repeat="template in templates" draggable id="{{template._id}}" type="template" class="template-box">
<!-- Images and other fields are child of "li" tag which can be dragged.-->
</li>
The method dragSupported is in the template scope and returns true or false. I don't want to create two big duplicate <li> elements by using ng-if for each value returned by dragSupported(). In other words, I am not looking for the following approach to solve this.
<!--draggable attribute is used as handle to make it draggable using jquery event-->
<li ng-if="dragSupported() ==true" ng-repeat="template in templates" draggable id="{{template._id}}" type="template" class="template-box">
<!-- Images and other fields are child of "li" tag which can be dragged.-->
</li>
<!--remove "draggable" directive as user doesn't have permission to drag file -->
<li ng-if="dragSupported() !=true" ng-repeat="template in templates" id="{{template._id}}" type="template" class="template-box">
<!-- Images and other fields are child of "li" tag which can be dragged.-->
</li>
Is there any other approach to avoid code duplicity?
ng-attr-<attrName>
Support for conditionally declaring an HTML attribute is included with Angular as the dynamically-titled ng-attr-<attrName> directive.
Official Docs for ng-attr
Example
In your case, the code might look like this:
<li
id="{{template._id}}"
class="template-box"
type="template"
ng-repeat="template in templates"
ng-attr-draggable="dragSupported() === true"
></li>
Demo
JSFiddle
This contains examples of usage for the following values: true, false, undefined, null, 1, 0, and "". Note how typically-falsey values may yield unexpected results.
Thanks Jason for your suggestion. I took little different approach here. Since I don't want to change the "scope" variable therefore I used "attrs" to check if drag is allowed or not. Following is approach I tool which seems good so far.
Directive code:
app.directive('draggable', function () {
return {
// A = attribute, E = Element, C = Class and M = HTML Comment
restrict: 'A',
replace:true,
link: function (scope, element, attrs) {
if(attrs.allowdrag =="true")
{
element.draggable({
cursor: 'move',
helper: 'clone',
class:'drag-file'
});
}
}
}
});
HTML Code:
<ul>
<!--draggable attribute is used as handle to make it draggable using jquery event-->
<li ng-repeat="template in templates" draggable allowdrag="{{userHasPrivilege()}}" >
<!--Ohter code part of li tag-->
</li>
</ul>
Controller is having implementation of userHasPrivilege().
Not sure if this is correct way or not. Looking for thoughts.
There is no way to directly add or remove an attribute from an element. However, you could create a directive that simply adds the attribute to the element when the condition is met. I've put something together that illustrates the approach.
Demo: http://jsfiddle.net/VQfcP/31/
Directive
myApp.directive('myDirective', function () {
return {
restrict: 'A',
scope: {
canDrag: '&'
},
link: function (scope, el, attrs, controller) {
/*
$parent.$index is ugly, and it's due to the fact that the ng-repeat is being evaluated
first, and then the directive is being applied to the result of the current iteration
of the repeater. You may be able to clean this by transcluding the repeat into the
directive, but that may be an inappropriate separation of concerns.
You will need to figure out the best way to handle this, if you want to use this approach.
*/
if (scope.canDrag&& scope.canDrag({idx: scope.$parent.$index})) {
angular.element(el).attr("draggable", "draggable");
}
}
};
});
HTML
<ul>
<!-- same deal with $parent -->
<li ng-repeat="x in [1, 2, 3, 4, 5]" my-directive="true" can-drag="checkPermissions(idx)">{{$parent.x}}</li>
</ul>
Controller
function Ctl($scope) {
$scope.checkPermissions = function(idx) {
// do whatever you need to check permissions
// return true to add the attribute
}
}
I used a different approach as the previous examples didn't work for me. Perhaps it has to do with using custom directives? Perhaps someone can clear that up.
In my particular example, I'm using ui-grid, but not all ui-grids should use pagination. I pass in a "paginated" attribute and then $compile the directive based on true/false. Seems pretty brutish but hopefully it can push people in a positive direction.
HTML
<sync-grid service="demand" paginated="true"></sync-grid>
Directive
angular
.module('app.directives')
.directive('syncGrid', ['$compile', SyncGrid]);
function SyncGrid($compile){
var nonPaginatedTemplate = '' +
'<div>' +
' <div ui-grid="gridOptions" class="grid"></div>' +
'</div>';
var paginatedTemplate = '' +
'<div>' +
' <div ui-grid="gridOptions" class="grid" ui-grid-pagination></div>' +
'</div>';
return {
link: link,
restrict: 'E',
replace: true
};
function link(scope, element, attrs) {
var isPaginated = attrs['paginated'];
var template = isPaginated ? paginatedTemplate : nonPaginatedTemplate;
var linkFn = $compile(template);
var content = linkFn(scope);
element.append(content);
// Continue with ui-grid initialization code
// ...
}
}
How can I move an element to different places in the DOM with angular js?
I have a list of elements like so
<ul id="list" ng-controller="ListController">
<li ng-controller="ItemController"><div>content</div></li>
<li ng-controller="ItemController"><div>content</div></li>
<li ng-controller="ItemController"><div>content</div></li>
<li ng-controller="ItemController">
<div>content</div>
<div id="overlay"></div>
</li>
</ul>
What I'm trying to accomplish is moving the #overlay from place to place within the list without having to have a hidden duplicate in every item that I flag hidden/unhidden.
If this was jquery I could just do something like this:
$("#overlay").appendTo("#list li:first-child");
Is there an equivalent way to do this in angular?
Thanks to your clarifications I can understand that you've got a list of items. You would like to be able to select one item in this list (swipe but potentially other events as well) and then display an additional DOM element (div) for a selected item. If the other item was selected it should be un-selected - this way only one item should have an additional div displayed.
If the above understanding is correct, then you could solve this with the simple ng-repeat and ng-show directives like this:
<ul ng-controller="ListController">
<li ng-repeat="item in items">
<div ng-click="open(item)">{{item.content}}</div>
<div ng-show="isOpen(item)">overlay: tweet, share, pin</div>
</li>
</ul>
where the code in the controller would be (showing a fragment of it only):
$scope.open = function(item){
if ($scope.isOpen(item)){
$scope.opened = undefined;
} else {
$scope.opened = item;
}
};
$scope.isOpen = function(item){
return $scope.opened === item;
};
Here is the complete jsFiddle: http://jsfiddle.net/pkozlowski_opensource/65Cxv/7/
If you are concerned about having too many DOM elements you could achieve the same using ng-switch directive:
<ul ng-controller="ListController">
<li ng-repeat="item in items">
<div ng-click="open(item)">{{item.content}}</div>
<ng-switch on="isOpen(item)">
<div ng-switch-when="true">overlay: tweet, share, pin</div>
</ng-switch>
</li>
</ul>
Here is the jsFiddle: http://jsfiddle.net/pkozlowski_opensource/bBtH3/2/
As an exercise for the reader (me), I wanted to try a custom directive to accomplish this. Here is what I came up with (after many failed attempts):
<ul ng-controller="ListController">
<li ng-repeat="item in items">
<div singleton-overlay>{{item.content}}</div>
</li>
</ul>
A service is required to store the element that currently has the overlay, if any. (I decided against using the controller for this, since I think a 'service + directive' would make for a more reusable component than a 'controller + directive'.)
service('singletonOverlayService', function() {
this.overlayElement = undefined;
})
And the directive:
directive('singletonOverlay', function(singletonOverlayService) {
return {
link: function(scope, element, attrs) {
element.bind('click', moveOrToggleOverlay);
function moveOrToggleOverlay() {
if (singletonOverlayService.overlayElement === element) {
angular.element(element.children()).remove();
singletonOverlayService.overlayElement = undefined;
} else {
if (singletonOverlayService.overlayElement != undefined) {
// this is a bit odd... modifying DOM elsewhere
angular.element(singletonOverlayService.overlayElement.children()).remove();
}
element.append('<div>overlay: tweet, share, pin</div>')
singletonOverlayService.overlayElement = element;
jsFiddle: http://jsfiddle.net/mrajcok/ya4De/
I think the implementation is a bit unconventional, though... the directive not only modifies the DOM associated with its own element, but it may also modify the DOM associated with the element that currently has the overlay.
I tried setting up $watches on scope and having the singleton store and modify scope objects, but I couldn't get the $watches to fire when I changed the scope from inside the moveOrToggleOverlay function.