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).
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>
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
I'm writing an AngularJS app that gets a list of posts from a server, and then uses an ngRepeat and a custom post directive to output all the posts.
Part of the post object is a blob of html, which I currently add to the directive by first doing an $sce.trustAsHtml(blob), and then using the ng-bind-html directive and passing the trusted html blob to it. It works fine, but now I want to modify the html before adding it to the output. For instance, I want to find all link tags and add a target="_blank" to it. I also want to remove any content editable attributes from any element. etc.
What is the best way of doing this? I was thinking of just loading it up in a document fragment and then recursively iterating through all of the children doing what I need to do. But I assume there is a better AngularJS way to do this?
EDIT:
here is a codepen with an example of what I have:
http://codepen.io/niltz/pen/neqlC?editors=101
You can create a filter and pipe (|) your content through it. Something like:
<p ng-bind="myblob | myCleanupFilter">
Your myCleanupFilter would look something like this (not tested):
angular.module('myApp').filter('myCleanupFilter', function () {
return function cleanup (content) {
content.replace('......') // write your cleanup logic here...
};
});
If you want to add attributes that are themselves directives, then the best place to add them is in the compile function in a custom directive.
If they are just plain old attributes, then there's nothing wrong with hooking into DOM ready in your run block, and adding your attributes with jquery.
var app = app.module('app',[]);
app.run(function ($rootScope){
$(document).ready(function()
$rootScope.$apply(function(){
$('a').attr('title','cool');
});
})
});
If you want add the attributes after the compile phase but before the linking phase in the angular life cycle then a good place to do it is in the controller function for a directive that's placed on the body element.
<body ng-controller="bodyCtrl">
</body>
app.controller('bodyCtrl', function($element){
$('a', $element).attr('title','cool');
});
During the compile phase angular will walk the DOM tree, matching elements to directives, and transforming the HTML along the way. During the link phase, directives will typically set up watch handlers to update the view when the model changes. By placing a directive on the body element, it ensures that all directives have been compiled, but the linking phase hasn't started yet.
I'm creating a fall back image directive that looks like this http://plnkr.co/edit/wxy4Sp2K02iXoQNsvkah
angular.module('directives').directive('myDirective', function() {
return {
restrict: 'C',
link: function(scope, element, attrs) {
console.log('linking');
}
}
});
My directive doesn't work for elements that are added to the DOM by the typeahead.js plugin (https://github.com/twitter/typeahead.js).
<div class='tt-suggestion'>
<div><span class="my-directive">bla</span></div>
</div>
I guess it's because Angular is not informed about the elements that are added by jQuery and hence it doesn't invoke the directive. How do I notify Angular of these changes?
You can use the Angular compile service to do this: http://docs.angularjs.org/api/ng/service/$compile
Basicly it works like this:
document.getElementById("test").innerHTML = $compile("")($scope);
ideally you shouldnt be mixing jquery and angular because they both are based on different philosophy.
jquery-- is event driven i.e. have event listeners which cause changes to model and then the programmer has to code numerous lines to change the view i.e. changing css,text etc
angular-- woo hoo! just change the model which is binded to $scope and :) your view is automatically updated
to automatically react on changing of such events angular has a compiler which studies entire html code before the app is loaded so even if there is a template which you might use later you must enclose it in so that angular compiles this so that all the special angular directive and controller perform as expected even when you remove or add templates to the dom.
here you are using typehead.js and jquery to manually manipulate the view which is against angular philosophy because when you do such maipulation angular compiler wouldnt be aware of it as it runs only when the app is initialized. Thats why before appending you should use $compile to make the angular compiler aware of this template .
in your case i would suggest the typehead present on this url
http://angular-ui.github.io/bootstrap/
Still under 72 hours coming up to speed with angular. Having used knockout for a while, I have to say it's interesting. My question right now has to do with ng-class.
Say I have the following:
<div class="myClass" ng-class="{myClass: false}">
When angular bootstraps, it does not remove the myClass attribute that was rendered there before. I had expected it to, since that's what ko's css binding does. It seems that angular only removes the class if angular was the one who put it there.
So, is there a common workaround for this? Other than creating a custom directive?
Since you're only using Angular for the form and need a class to be active on elements within the form prior to Angular bootstraping it looks like a directive may be the best way to go.
The following directive will remove the specified class from the element its on once the directive is linked in by Angular (which is almost the same point when ngClass will kick in):
.directive('removeClass', function(){
return {
restrict: 'A',
link: function(scope,element, attrs){
element.removeClass(attrs.removeClass);
}
};
});
Used like so:
<div class="oldClass" remove-class="oldClass" ng-class="{newClass: true}">stuff</div>
demo fiddle