Custom Attribute Directive being matched to ng-attr-myCustomAttribute - javascript

I am using a third party lib with the following custom attribute directive:
angular.directive('snapDrawer', function () {
'use strict';
return {
restrict: 'AE',
...
So if the attribute "snap-drawer" is found in an HTML element the directive implementation has a match and fires, for example:
<div snap-drawer></div>
I am using Angular 1.3 which has an "AllOrNothing" approach to ng-attr where if the value conditional is undefined then the attribute does not render like so:
<div ng-attr-snap-drawer="{{data.addSnapDrawer}}"></div>
100% fact this works, the value of data.addSnapDrawer in my controller is undefined and the snap-drawer attr does not render in the DOM
I have verified that Angular 1.3 does this AllOrNothing approach with ng-attr here: What is the best way to conditionally apply attributes in Angular? (take a look at Mathew Foscarini's answer)
BUT what does render in the DOM is:
<div ng-attr-snap-drawer="{{data.addSnapDrawer}}" class="snap-drawer snap-drawer-left" style=""></div>
So, unbelievably, the angular.directive('snapDrawer') is matching to "ng-attr-snap-drawer". How can this be, I am really shocked that AngularJS, in all its glory, has a bug like this.
I cannot find anything online. I cannot set snap-drawer="false" I need it to not appear in the DOM, which I achieved by upgrading from Angular 1.2 to 1.3.

This is old, but I stumbled upon something very similar. https://github.com/angular/angular.js/issues/16441 - angular authors made it this way.
You need to devise a different way (depending on your usecase) to conditionally apply directives (e.g. use ng-switch and have two versions of the HTML; one with the directive and one without, or have a terminal directive with high priority that evaluates the expression during the linking phase, applies the necessary directive(s) and compiles the element).

Related

What is initialised first - controller or partial/html?

Hi please explain reason for following three scenarios as I am unable to know why is this happening -
1)
<div ng-controller="myctrl">
<p>something for DOM manipulation</p>
</div>
2)in route I write
('someroute',{
templateUrl : "mytemplate",
controller : "myctrl"
});
mytemplate:
<div>
<p>something for dom manipulation</p>
</div>
3)
<div ng-include="mytemplate" ng-controller="myctrl"></div>
with template being same as above
The controllers in all the above scenarios are same, and in all of them I am just trying to select p tag of DOM by writing angular.element('p'). But this seems inconsistent. It works very well in 2nd scenario, it never works in 3rd scenario and I am not sure about 1st sccenario. Can someone explain which method is best for dom selection/manipulation, as I have to add a class to this 'p' tag on hover.
I am not understanding which gets initialized first- controller or partial?
Manipulating DOM inside controllers is discouraged. Quote from Best Practice - Dom Manipulations:
Dom Manipulations should not exist in controllers, services or anywhere else but in directives.
If you only need to style elements on hover, using the p:hover CSS selector would be enough without touching the DOM. ng-class and ng-mouseover can help you if you really want the class.
For more complex scenarios, you may want to write your own directive. You can check the article above for a guide to do that.
Load order from the first case: HTML first. Directives like ngController are loaded after parsing the HTML. Therefore the HTML already exists when the controller is loaded.
Load order for the second case: I'm not sure about it. You may check documentation for ngRoute or uiRouter depending on the router you are using.
Execution order for the third case: Controller first. The directive ngController have higher priority than the ngInclude directive. Therefore, the controller is loaded first.
Quote from ngController documentation :
This directive executes at priority level 500.
Quote from ngInclude documentation :
This directive executes at priority level 400.

In angularjs we are having ng-disabled directive, why ng-enabled directive is not provided by the framework as we are having ng-show and ng-hide

In AngularJs ng-enabled directive is not provided. Is there any proper reason to not providing that directive in the framework, because we are having both ng-show and ng-hide when you can just use ng-hide to achieve our goal.
It wouldn't be nice just to check
ng-enabled="attribute.value === true"
instead of
ng-disabled="!(attribute.value === true)"
it will increase the readability of the code.
The reason why there is no ngEnabled directive in Angular is rather semantical - there is simply nothing corresponding to it in HTML specification. At the same time there is already ngDisabled directive that works with disabled attribute. For the same reason, there is no ngUnchecked directive, because there is already ngChecked that sets/removes checked attribute.
Now, the reasonable question: why we have both ngShow and ngHide then? Well it's just for convenience in this case I guess, because having both ngShow and ngHide is not more confusing than ngShow alone, but at the same time it's very handy to have both.
I am not missing an ng-enabled directive at all and I think it would add little to nothing to the framework.
Inputs are enabled by default and HTML inputs also do not have an enabled attribute, just a disabled. The angular directive sets the HTML disabled attribute, but after evaluating an expression.
You can just write
ng-disabled="!attribute.value"
I think it is pretty readable.
TLDR: Use angular-enabled instead.
The core team expressed their view in this this comment: https://github.com/angular/angular.js/issues/1252#issuecomment-49261373
They will not abide a feature request just because it has many +1-s in order to keep the core bloat free.
However, if you still want to have ng-enabled functionality, btford has created this handy little module just for you:
https://github.com/btford/angular-enabled
Angular sets the disabled attribute based on the result of the expression in ng-disabled. There is no enabled attribute in HTML5 so ng-Enabled wouldn't work.
This line worked for me.
ng-disabled="!attribute.value"
Not that this is an answer to the question of Why but for those who want to write their own directive, here you go. BTW it's in coffeescript.
.directive 'ngEnabled', [
'$parse'
($parse)->
dir =
restrict: 'AC'
link: ($scope, elem, attrs)->
getter = $parse attrs.ngEnabled
$off = $scope.$watch ->
getter $scope
, (val)->
elem.attr 'disabled', !val
$scope.$on '$destroy', -> $off()
]
http://plnkr.co/edit/F4RG2v859oFtTumvgoGN?p=preview

Getting an Element in AngularJS

It seems that getting an element in AngularJS is a bad idea, i.e. doing something like:
$('.myElement')
in say, a controller is not an angular way of doing things.
Now my question is, how should I get something in angular?
Right now, what I'm doing (and is an accepted way of doing it) is by watching a variable, and my directive does something based on it.
scope.$watch('varToWatch', function (varToWatch) {
if(attrs.id == varToWatch)
{
//Run my Directive specific code
}
});
However, while this particular design works for most cases, watch is an expensive operation, and having lots of directives watching can really slow down your application.
TL:DR - What is an angular way of getting a directive based on a variable on the directive? (like the one above)?
If you want to get/set values you don't need to fetch the element using jQuery. Angular data binding is the way to do it.
directives is the way to go if you want to do animations or any kind of element attributes and DOM manipulation.
Your code is basically right; the directive should watch something in the $scope and perform it's logic when that thing changes. Yes, watch statements are expensive, and that is a problem once your number of watches start to approach ~2000.
Looking at your code though, I see one problem:
The variable $scope.varToWatch references an id in the template.
When this variable changes, you want something to happen to the element which has this id.
The problem here is in the first point: The controller should know nothing about the DOM, including the id of any element. You should find another way to handle this, for example:
<div my-directive="one"> ... </div>
<div my-directive="two"> ... </div>
<div my-directive="three"> ... </div>
...etc
And in your directive:
scope.$watch('varToWatch', function (varToWatch) {
if(attrs.myDirective == varToWatch)
{
// Run my Directive specific code
}
});
You are very vague as to what you're trying to achieve, but I'll try to answer in context of your last comment.
I have a lot of the same directives (therefore the code will run on all of them), but I need to get only one directive from the lot.
You talk a lot about getting the right element. The directive element is passed to the link function in the directive. If you are not using this element (or children of it) directly, but rather trying to search for the element you want somehow, you are most likely approaching the problem the wrong way.
There are several ways to solve this, I'm sure. If you're thinking about animations, there is already support for that in Angular, so please don't try reinvent the wheel yourself. For other logic, here are two suggestions:
Secondary directive
If the logic you want to apply to this directive is generic, i.e. it could be applied to other directives in your application, you could create a new directive which works together with directives. You can set prioritization in directive in order to control which directive is executed first.
<main-directive ... helper-directive="{{condition_for_applying_logic}}"></main-directive>
jsFiddle example
Expanding main directive
If the logic is tightly coupled to this directive, you can just create a new attribute, either dynamic or static, and bind to it in the directive. Instead of checking 'attrs.id == varToWatch', you check if $scope.apply-logic === 'true' and apply the logic then.
<main-directive ...></main-directive> <!-- Not applied here -->
<main-directive apply-logic="true" ...></main-directive> <!-- Applied here -->
<main-directive apply-logic="{{some.varOnScope}}"...></main-directive> <!-- Conditional -->
Please comment if something is unclear.

DOM Manipulation like JQuery using AngularJS?

I have seen tutorials and other articles saying to not even include jQuery into a project when using Angular.js in order to make the transition as thorough as possible. However, how do we do simple DOM manipulations to css and other things like $('.item').css('top', '50px'); using only Angular? (or is this not possible)?
This is possible using the ng-class directive. On your element, you would say:
<div ng-class="{'classWithTop50':myScopeVariable}"></div>
Where classWithTop50 is defined in css as:
.classWithTop50{
top:50px;
}
When $scope.myScopeVariable is true, the element will have the class .classWithTop50
You can add classes like in ansewer reported by fairweather or you can also use ng-style to dynamically style the elements.
If you want more DOM manipulation then you can create a Directive for that element and do all the stuff using jqlite in the directive.
Angular comes with a lite version of jQuery, which allows a subset of jQuery. You can still include jQuery if you like (should be included before Angular).
Angular comes with a lite version of jQuery, which allows a subset of jQuery.
You can use this :
angular.element('.item').css('top', '50px');
AngularJS API Docs : element
Use ng-class directive:
(from angular doc)
<p ng-class="{'strike': deleted, 'bold': important, 'red': error}">Map Syntax Example</p>
=> this add the class 'strike' when the scope variable $scope.deleted == true, add the 'bold' class when $scope.important == true ...
Angular doc : http://docs.angularjs.org/api/ng.directive:ngClass
Don't think in jQuery, think in Angular (M-V-*).
angular.element() is there for directives DOM manipulations. DOM updates should be done with angular directives; css updates and animations with css classes.
see: "Thinking in AngularJS" if I have a jQuery background?

AngularJS : Apply a directive only if element is a textarea

I wrote an attribute restricted Angular directive (restrict:'a') that adds features to textarea. It makes no sense to apply it to any other type of element.
Adding a if (element.nodeName == 'TEXTAREA') { is really dirty and unreliable.
I tried to add require: textarea to it but it does not work and I get this error: Error: No controller: textarea
Question: Is there any cleaner way to properly apply this restriction?
EDIT: Additional constraint following the first answer.
I want to avoid using a template in this directive as I want to be able to use several directive of this type. Here is an example of what I'd like:
<textarea splittable shared mergeable></textarea>
When using your own directive (eg my-textarea) with restrict: 'E', replace: true, any additional attributes will get carried over to the root-element of the directive, even other attribute directives. So:
<my-textarea splittable class="foobar"></my-textarea>
could be rendered as:
<textarea splittable="" class="foobar"></textarea>
with splittable being executed.
demo: http://jsfiddle.net/LMq3M/
So I think using your own directive is realy the cleanest way to handle this.
In my opinion there is no need to add such controls as they just tend to add complex code when the real issue is human error.
Just document what the purpose and usage is for the directive.
The idea is you give your directive a unique name, like myDirective and you can use it in HTML as such.
<body>
<my-directive></my-directive>
</body>
The directive will replace the tag with the template you provided in the directive controller. Which could be in your case a simple textarea element with added properties.
I recommend watching this video to clearly grasp the concept of directives.
http://www.youtube.com/watch?v=xoIHkM4KpHM

Categories

Resources