I am aware that Angular is used for generating HTML. Setting $scope.items to an array will cause to generate multiple LI items.
In my case, for every element of items, I would like to invoke a Javascript function. The Javascript then adds somthing to the DOM. is not allowed by Angular. What is the best way to invoke a javascript repeatedly using Angular?
Thanks,
Yash
You can use ng-init directive wich will invoke custom function every ng-repeat iteration.
<li ng-repeat="member in members" ng-init="myFunc($index)">
{{member.mId}}
</li>
and in controller:
$scope.myFunc = function(index){
console.log(index);
}
http://jsfiddle.net/FuvRQ/1/
best is to use a directive and perform your javascript function's functionality in
link:
and whatever you want to insert in the DOM can go as plain html in the property of directive
template:
read about directives these are one of the most powerful features of angular
Related
Is there a right way to apply custom directive to HTML template based on some condition
Eg: <li my-custom-directive="{{item}}">
I need to apply "my-custom-directive" only if {{item}} is defined.
This feels like a design problem rather than a technical one.
Rather than apply the custom directive conditionally, simply figure out what to do inside the directive. Semantically, this makes more sense.
For instance, if item is undefined in this case, simply don't do something inside the directive.
Use ng-if, DOM is not inserted until condition is met.
AngularJS leaves a comment within the DOM for its reference,
so <li my-custom-directive="{{item}}"> would not be within the DOM at all until {{item}} is defined.
If you need to add directives dynamically to the DOM from a variable, use $compile provider. I've created myself a directive for such things
angular.module('test', []).directive('directiveName', ['$compile', function($scope) {
return {
link: function($scope, element, attrs, ctrl) {
element.replaceWith($compile(attrs.direciveName)($scope))
}
}
}]);
And you can use it as such:
<div directive-name="{{customDirectiveName}}"></div>
{{customDirectiveName}} being a $scope variable from somewhere else. From this point you could ng-repeat on JSON objects recieved from server, ect.
It depends on your requirement , if you use it has as element instead of attribute you can achieve using ng-if.
for ex in the below code li wouldnt appear in the dom as and when item is undefined,
<my-custom-directive="{{item}}" ng-if="item">
<li>{{item}}</li>
</my-custom-directive>
$scope.addNew = function(){
$('.thumb-img-gallary').append("<li><span class='img-del' ng-click='delThumbImgGallaryPhoto($event)'>X</span><img class='thumb-img' src='data:image/jpeg;base64,"+imageData+"'/></li>");
}
I am calling this function to add element dynamically. But then delThumbImgGallaryPhoto() is not getting called.
you cannot just append an element with a ng-click or any other directive, and expect it to work. it has got to be compiled by angular.
explenation about compilation from angular docs:
For AngularJS, "compilation" means attaching directives to the HTML to make it interactive
compelation happens in one of two cases:
When Angular bootstraps your application, the HTML compiler traverses the DOM matching directives against the DOM elements
when you call the $compile service inside an angular context
so what you need to do, is first to compile it(remeber to inject the $compile service in your controller), and then append it:
$scope.addNew = function(){
var elem = $compile("<li><span class='img-del' ng-click='delThumbImgGallaryPhoto($event)'>X</span><img class='thumb-img' src='data:image/jpeg;base64,"+imageData+"'/></li>")($scope);
$('.thumb-img-gallary').append(elem);
}
BTW, remember it is prefable not to have any DOM manipulations done in the controller. angular has directives for that.
You have to compile it with scope
try like this
var item="<li><span class='img-del' ng-click='delThumbImgGallaryPhoto($event)'>X</span><img class='thumb-img' src='data:image/jpeg;base64,"+imageData+"'/></li>"
$('.thumb-img-gallary').append(item);
$compile(item)($scope);
angular doesn't know anything about your newly added element. You need to compile your newly added element using $compile. and you should better use directive for this task.
It is a bad habit to access ui elements from controller.
edit: it would be best using ng-repeat for this task. lets say you have a thumb-gallery directive which is repeated using ng-repeat by thumbs array.
when you need to add a new image you only need to add it to your thumbs array.
it is simple and straightforward
Your html would look like
<thumb-gallery ng-repeat="gallery in galleries"></thumb-gallery>
and your js would look like
var gallery = {};
$scope.galleries.add(gallery);
I have few questions regarding angular directives
I'm basing my questions based on the following block of codes
<ul class='parent'>
<li class='child1'></li>
<li class='child2 active'></li>
<li class='child3'></li>
</ul>
So based on the above
How can i create a directive based on the above (assuming this comes from an Angular plugin, and there is no angular directive attribute defined within the tag)?
Assuming the above is from an Angular plugin, and i don't intend to edit the plugin, how can i use the directive to see which list is currently active?
How can i do operations through the code above using Angular? With Jquery, i can easily do it as
For checking the child length
$('.parent').children().length
For iterating through the child elements
$.each($('.parent').children(), function(){ console.log(this); });
But when i tried to do it with Angular way
angular.element(document.querySelectorAll('.parents')).children().length
Somehow this part always returned me the wrong value. I guess my Jquery mindset is the reason why i couldn't get the value but still, i read somewhere that angular.element object is the equivalent of the jquery $() object, thus both should be able to use the same functions.
If i want to do event binding on the code above i should do it inside the link function correct? If i want to watch and apply changes for the code above, then i should use the directive's scope and do it inside the link function too right?
Is there easy-to-chew guide on how Angular's directives? Somehow most of those that i read are pretty much as confusing as the directive guide in Angularjs documentation itself.
I'm using angularjs and need to find the watch of the ng-repeat, because I need ng-repeat to stop working from a specific point. this my code:
<ul>
<li ng-repeat="item in items">
<div>{{item.name}}</div>
</li>
</ul>
I need to find the watcher of the ng-repeat. If I go to scope.$parent.$$watchers[x] and perform splice the watch is removed, but how can I find the specific watch?
It's not possible to find the watch (see explanation below), but you can achieve what you wish by use the following directive
app.directive('repeatStatic',
function factory() {
var directiveDefinitionObject = {
restrict : 'A',
scope : true, // this isolates the scope from the parent
priority : 1001, // ng-repeat has 1000
compile : function compile() {
return {
pre : function preLink(tElement, tAttrs) {
},
post : function postLink(scope, iElement, iAttrs, controller,
transcludeFn) {
scope.$apply(); // make the $watcher work at least once
scope.$$watchers = null; // remove the $watchers
},
};
}
};
return directiveDefinitionObject;
}
);
and its usage is
<ul>
<li repeat-static ng-repeat="item in items">
{{ item }}
</li>
</ul>
See http://plnkr.co/k9BTSk as a working example.
The rational behind is that
the angular directive ng-repeat directive uses internal function $watchCollection to add a self created listener that watchs the items object. Since the listener is a function created during the process, and is not keep anywhere as reference, there is no good way to correctly identify which function to remove from the $$watchers list.
However a new scope can be forced into the ng-repeat by using an attribute directive, in this way the $$watchers added by ng-repeat are isolated from the parent scope. Thus, we obtain full control of the scope.$$watchers. Immediate after the ng-repeat uses the function that fills the value, the $$watchers are safe to be removed.
This solution uses hassassin's idea of cleaning the $$watchers list.
I have a fork of Angular that lets you keep the watch in the $$watchers but skip it most of the time. Unlike writing a custom directive that compiles your HTML it lets you use normal Angular templates on the inside, the difference is that once the inside is fully rendered the watches will not get checked any more.
Don't use it unless you really genuinely need the extra performance because it can have surprising behaviour.
Here it is:
https://github.com/r3m0t/angular.js/tree/digest_limit
Here's the directive you can use with it (new-once):
https://gist.github.com/r3m0t/9271790
If you want the page to never update you can use new-once=1, if you want it to sometimes update you can use new-once=updateCount and then in your controller run $scope.updateCount++; to trigger an update.
More information: https://github.com/Pasvaz/bindonce/issues/42#issuecomment-36354087
The way that I have dealt with this in the past is that I created a custom directive that copies the logic of the built in ngRepeat directive but never sets up the watches. You can see an example of this here, which was created from the ngRepeat code from version 1.1.5.
The other way, as you mentioned was to remove it from $$watchers of a scope, which is a little stranger since it accesses a private variable.
How this could be done is that you create a custom directive on the repeat to remove the watch that is the repeat. I created a fiddle that does this. It basically just on the last element of the repeat clears the parent watch (which is the one on data)
if (scope.$last) {
// Parent should only be the ng-repeat parent with the main watch
scope.$parent.$$watchers = null;
}
This can be modified to fit your specific case.
Hope this helps!
It sounds like you want to put the bindonce directive on your ng-repeat.
https://github.com/Pasvaz/bindonce
If you don't need angular dual-binding, have you tried a custom directive with a complie function where you construct HTML yourself by creating DOM from scratch without any angular dual-binded mechanisms ?
I know manipulating the DOM goes against the rules of Angular but in this case, am having do transverse the DOM to modify a sibling node.
In jQuery you can do something like this:
$(this).parent().addClass('.loading');
While in Angular you would do something like this:
angular.element(this).parent().addClass('.loading');
Certainly this doesn't work because there is no parent() method or addClass() method support on the API.
Which brings me to the question, how else can I accomplish this?
Thanks!
Angular elements are wrapped with jqLite by default (Angular's own jQuery implementation). If you have added jQuery to your project, then elements are wrapped with full jQuery.
Here's a list of methods available with jQuery lite http://docs.angularjs.org/api/angular.element
As you can see, you have access to parent() and addClass() So you get a lot of DOM manipulation power w/o adding jQuery as a dependency.
-*-
It's perfectly fine to manipulate the DOM with angular, the best practice is to do it from directives, here's a little example of an element having access to the parent element
HTML
<div ng-app='app'>
<div id="someparent">
<div my-directive>
</div>
</div>
</div>
In your JS
var app = angular.module('app', []);
app.directive('myDirective', function(){
return{
restrict: 'A',
link: function(scope, element, attributes){
console.log(element.parent().attr('id')); // "someparent"
element.parent().addClass('loading'); // adding loading class to parent
}
};
});
jsfiddle: http://jsfiddle.net/jaimem/Efyv4/1/
Of course when building your app you might want to have directives manipulating only elements within itself.
-*-
Also, as Mark mentions, you can use angular's existing directives such as ngClass instead of creating your own. It's not clear what you want to achieve, but I recommend looking at ngCloak.
In jQuery, we often have some event trigger, then from the element that triggered the event, we traverse the DOM to find some other element which is then manipulated. E.g., your example:
// some event causes this code to execute:
$(this).parent().addClass('.loading');
In AngularJS, we declare where the manipulation points are in HTML first (e.g., ng-class as #blesh suggests), then events modify $scope properties, and Angular does the DOM manipulation automagically for us. E.g.:
<div ng-controller="MyCtrl" ng-class="loadingClass">parent
<a ng-click="loadingClass='loading'">child</a>
</div>
Controller:
function MyCtrl($scope) {
// if you want some other class initially, uncomment next line:
// $scope.loadingClass = 'notLoading';
}
Above, clicking the "child" link modifies $scope.loadingClass. Angular will notice the change and apply the new class to the parent div.
For more complicated manipulations, a custom directive like #jm- shows would be required.
jQuery: events drive DOM traversal and DOM manipulation code (imperative).
Angular: events modify $scope and the magic happens (on our declarative HTML).