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>
Related
Here's a plunker example you can see: http://plnkr.co/edit/NQT8oUv9iunz2hD2pf8H
I have a directive that I would like to turn into a web component. I've thought of several ways as to how I can achieve that with AngularJS but am having difficulty with a piece of it. I'm hoping someone can explain my misstep rather than tell me a different way to do it.
Imagine you have a directive component that sets up some shell with css classes maybe some sub components, etc.. but lets the user define the main content of the component. Something like the following:
<my-list items="ctrl.stuff">
<div>List Item: {{ item.name }}</div>
</my-list>
The HTML for the list directive could be something like the following (with OOCSS):
<ul class="mas pam bas border--color-2">
<li ng-repeat="items in item track by item.id" ng-transclude></li>
</ul>
Normally this can be solved in the link function by linking the directives scope to the new content. And it does work for other components. However introducing the ng-repeat seems to break that portion of the control. From what I can tell, the appropriate place might be the compile function but the documentation says the transcludeFn parameter will be deprecated so I'm not sure how to proceed.
I should also note that when using the beta AngularJS, there is either a bug or a new paradigm coming, because this is no longer a problem. It seems like the transcluded content always gets access to the directives scope as well as the outer controllers scope.
I really appreciate any enlightenment on this.
It's by design that content added via ng-transclude will bind with an outer controller scope, not a scope of the current element that ng-transclude is on.
You could solve the problem by copy the ng-transclude's code and modify it a bit to give a correct scope:
.directive('myTransclude', function () {
return {
restrict: 'EAC',
link: function(scope, element, attrs, controllers, transcludeFn) {
transcludeFn(scope, function(nodes) {
element.empty();
element.append(nodes);
});
}
};
});
And replace the ng-transclude with my-transclude in your directive template.
Example Plunker: http://plnkr.co/edit/i7ohGeRiO3m5kfxOehC1?p=preview
Given this very simplistic markup:
<div ng-repeat="item in items" item>
And the directive:
app.directive('item', function()
{
return function(scope, element, attrs)
{
}
})
Say on a click event, I want to get the next item's scope and do something. I can either use scope.$$nextSibling() or element.next().scope().
Is there an advantage/disadvantage of using either? Or is there a better way of getting the next sibling's scope?
you should do like this
in the scope, call $rootScope.$broadcast('SOME_CHANGE_IN_SCOPE',...);
in sibling scope, call $scope.$on('SOME_CHANGE_IN_SCOPE',...)
you can of course wrap data in the event, check angularjs doc for more details
Not sure what you're trying to do, but you should never use any angular variables that begin with $$. They are meant for internal implementation and are not guaranteed to stay around from release to release.
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 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
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).