if ng-if and a custom directive are put together on the same DOM element, the initial animation does not work.
<div ng-if="value" myDirective class="fadeMe"></div>
Here is a plunkr and clearly shows the problem. Notice that only the initial show fade fails.
More Details:
I am guessing it has something to do with the priority of both directives (ngif is compiled first).
I tried to set a higher priority to the custom directive but I ran into more issues such as the child scope of the custom directive does not get destroyed by ng-if, therefore, unnecessary watchers in the custom directive keep watching values.
Seems to be related to this issue:
https://github.com/angular/angular.js/issues/14074
If you use inline template instead of templateURL in your directive it starts working.
It looks like there is a timing issues in data binding in Angular directive "restrict type" of class.
restrict: 'C'
So a as fast fix use
restrict: 'A'
http://plnkr.co/edit/2iB9jvpGSMi3IwelicXT
Related
I'm trying to wrap the SharePoint People Picker in an AngularJS directive. In order to initialise a people picker I need to place a div on the page, give it an ID and pass that ID into a SharePoint function.
I have this working with a basic directive like this:
<sp-people-picker id="test"></sp-people-picker>
But I wish for the directive to be useable anywhere, including in a repeating section:
<div ng-repeat="item in dataset">
<sp-people-picker id="test-{{ $index }}"></sp-people-picker>
</div>
This fails. I stepped through the code to see what was going wrong and found that while I was happily calling the SharePoint people picker function with "test-0" it was failing to find the element. document.getElementById("test-0") returned null. The reason for this is that my div still had the id "test-{{ $index }}" and only gets "test-0" AFTER my directive has compiled.
How can I make sure my directive runs after the {{ }} has been rendered?
(Not tagging with SharePoint as the SharePoint stuff is just the context, it's not actually relevant to the issue I'm trying to solve)
You need to use attrs.$observe inside your directive link function, that will act as the same as like $watch, the difference is it can watch on the {{}} interpolation directive, Your link function will look like below. It call function whenever interpolation directive gets evaluated.
Directive(Link Function)
link: function(scope, element, attrs){
attrs.$observe(attrs.id, function(newVal, oldVal){
//here you can get new value & `{{}}` is evaluated.
});
}
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).
I've created a custom directive that has contains related inputs and dropdowns. I've also used an isolate scope to properly bind the outer scope to the inner scope to assist with two databinding, and this also allows me to use the same directive multiple times on the same page. All works well up to this point. My next question is how to handle validation within the directive.
I no longer can use something along the lines of
ng-show="formName.controlname.$invalid && !formname.controlname.$pristine"
for the following 2 reasons,
My directive should not have to worry about the external form.
Because i'm using the same directive twice on the same page, using the syntax formName.controlname would actually map to two different controls.
Some ideas on this would be helpful at this point. What am I missing here?
You should definitely not make your directive depend on the form name. What you should do instead is to provide a parent form directive dependency so you can use its controller in your directive link function:
.directive('yourDirective', function() {
return {
require: '^form',
link: function(scope, element, attrs, formController) {
// use formController.$errors object
}
};
});
Probably you should do the validation in your directive.
Create a copy of the two-way bound scope members (assuming these are bound to the form inputs) to check pristine-ness and undo-ability.
Thanks for the feedback, but I discovered that using ng-form meets my requirement.
So in my directive mark up I added:
<div ng-form="[some name]">
.......
</div>
Doing this allowed me to continue to use the ng-* attributes.
Now I can do this in my directive:
ng-show="somename.controlname.$invalid && !somename.controlname.$pristine"
It is self contained so I don't have to worry about crossing any boundaries. I can add the control over and over again and validation stays intact per directive.
I am using this jquery plugin selectric and trying to hook it up with my angular drop down.
It works fine if my html for the dropdown has the options hardcoded in the html and I just do
$('select, .select').selectric();
But if I load my dropdown data dynamically like this
<select class="form-control" ng-model="myAddress" ng-options="address.addressLines for address in search.result.addresses" size="{{numAddressOptions}}">
</select>
I get a javascript error in the selectric plugin
TypeError: $li[index] is undefined
I tried adding the selectric plug in as a directive
<select class="form-control" ng-model="myAddress" ng-options="address.addressLines for address in search.result.addresses" size="{{numAddressOptions}}" selectric>
</select>
and my directive
.directive('selectric', function(){
'use strict';
return{
restrict: 'AE',
link: function(scope, element, attrs) {
$(element).selectric();
}
};
});
Any Ideas of what could be going wrong here ?
Since ng-options is a priority 0 directive your directive's link function cannot be guaranteed to fire after it: Angular 1.3.7 Compile Docs.
priority
When there are multiple directives defined on a single DOM element,
sometimes it is necessary to specify the order in which the directives
are applied. The priority is used to sort the directives before their
compile functions get called. Priority is defined as a number.
Directives with greater numerical priority are compiled first.
Pre-link functions are also run in priority order, but post-link
functions are run in reverse order. The order of directives with the
same priority is undefined. The default priority is 0.
To achieve the desired behavior you will want to pass your objects into a directive that creates the select element in its template. This is a very good example: https://stackoverflow.com/a/14586825/1861459. This should move your link method invocation to the correct part of the directive lifecycle (after the compile is completed for the directives in your template).
You would also change restrict: 'AE', to restrict: 'E', and change your HTML to something like <selectric addresses="{{ search.result.addresses }}" ...></selectric>
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