Plunker:
Direct Edit example.
In the above plunker, I have a directive (direct-edit) which uses
transclude: 'element',
to surround the element with other markup. The directive
requires: 'ngModel',
to connect the additional markup to the model. However, because the initial element is wrapped up with ng-transclude, it becomes disconnected from the model. Does anyone know how to fix this?
EDIT (from comments below):
To clarify: I want to take any arbitrary directive:
<custom attr1="" attr2="" style="" ng-model="random" />
and add the direct-edit directive so that the arbitrary directive is paired with a field that edits the value directly. For the purposes of simplicity, I'm only showing a text input in the example code.
Related
If I had an attribute directive, for example something like this:
<select multiple ... ng-model="ctrl.model" custom-directive="ctrl.customModel" />
where let's say that ngModel and customModel are arrays. Is there a way I can, within the directive's code, add a piece of html below the directives element which could have access to the scope of the directive and be able to reference the customModel so that in the end it looks something like this:
<select multiple ... ng-model="ctrl.model" custom-directive="ctrl.customModel" />
<div><!-- this code gets added by the custom-directive directive and uses it's scope -->
<span ng-repeat="item in customDirectiveCtrl.customModel" ng-bind="item.property"></span>
</div>
I know I can add html manually using jqLite, however this html doesn't have access to directive scope. The reason I don't want to convert the custom-directive directive from attribute directive to element directive is because it makes it way more difficult to add attributes such as id, name, required, disabled,... to underlying template elements (in the case of this example, a select element)
EDIT: as requested here's an example of how to add an element after the directives element:
{
restrict: 'A',
require: 'ngModel',
scope: { customModel: '=customDirective' },
link: function(scope, element, attrs, ngModel) {
//element.after('<div></div>'); //this adds a div after the directives element
element.after('<div><span ng-repeat="item in customModel" ng-bind="item.property"></span></div>'); //this will add the html in the string, but will not interpret the angular directives within since (i assume) that it is not bound to any scope.
}
}
Any angular component/directive added like this will not work properly or at all.
If you are injecting new HTML into the page in your directive, and you need that HTML to use angular directives (ng-repeat, ng-bind, etc) then you will need to use the $compile service to make angular aware of your new DOM elements. In your case, you would inject the $compile service into your directive and then use it like this:
link: function(scope, element, attrs, ngModel) {
//create the new html
var newElement = angular.element('<div><span ng-repeat="item in customModel" ng-bind="item.property"></span></div>');
//compile it with the scope so angular will execute the directives used
$compile(newElement)(scope); //<-this is the scope in your link function so the "customModel" will be accessible.
//insert the HTML wherever you want it
element.after(newElement);
}
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
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.
Motivation
Create a layout that directly descends the body element. The layout should wrap the ng-view with a scaffold template.
Constraints
The layout template will have arbitrary content (and potentially any number of root elements, so replace: true will not work here).
What have I tried
Writing a directive that utilizes ng-transclude to wrap the ng-view with the layout structure. As ng-transclude interaction with ng-view seems is no longer supported in version 1.2, no help here.
How, than, can I still exclude the directive's element itself from the DOM?
We can utilize the linking function to replace the directive's target element with the template's contents, as follows:
angular.module('myApp')
.directive('scaffold', function () {
return {
templateUrl: 'views/scaffold-template.html',
restrict: 'EA',
link: function (scope, element, attrs) {
// exclude the directive's own element
element.replaceWith(element.contents());
}
};
});
This comes in handy when the template absolutely must have arbitrary content, or simply can't have one root element.
As this manipulation will take place in all the directive's instances regardless, it's perhaps more appropriate to use the compile function, but link seems sufficient for the proof of concept.
replace property of the Directive Definition Object allows you to specify whether the directive's template will replace the host element ({..., replace: true, ...}) or just insert the template within ({..., replace: false, ...} - default setting).
So in your case you will want to set the replace to true.
One thing to note though is that your directive's template needs to have a single root DOM node, otherwise angular will throw "Error: Template must have exactly one root element". (this is a known limitation).
If your directive's template looks like this:
<br />
<span>{{name}}</span>
you will need to wrap it in single root element, as in:
<span>
<br />
<span>{{name}}</span>
<span>
Note: This is needed only when using replace: true.
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