Angular attribute directive - add an ng-repeat below current directive - javascript

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);
}

Related

attribute directive not working for ajax loaded element?

I'm dealing with legacy code. My page is composed of three partial views, one for header, one for footer, one for the content. I have this element with my-directive in my footer:
<a my-directive>
<img>
</a>
My footer is rendered at the same time with the others on page load. However, in my header, I have #products_menu whose content is loaded via ajax:
// calls a route to do some processing before returning the view
// to be rendered inside #products_menu
$('#products_menu').load(...);
#products_menu contains the same element with the same directive:
<a my-directive>
<img>
</a>
This is my directive:
angular
.module('module1')
.directive('myDirective', ['$rootScope', 'ModalService', '$compile',
function($rootScope, ModalService, $compile) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
console.log(element);
element.on('click', function(e) {...}
} ...
The problem is when I click on the element in the footer, it fires. But when I click on the element in the header, it doesn't. I'm assuming it's because the element was loaded dynamically, and the attribute was not bound during the time angular was compiling.
AM I right in my assumption and if so, is there a workaround?
So just dumping the html via using jquery or DOM manipulation would not intimate angular to do its work. You have to tell angular about this or better compile the DOM using $compile service. Angular things will automatically triggers in for that template
$('#products_menu').load(..., function() {
$compile($('#products_menu'))($scope);
})
I'd recommend to mix jQuery with angular, best way to handle this in angular way is using ng-include directive
<div id="products_menu" ng-include="'template.html'">
</div>

Access the html content of directive in parent controller

I have an overlay and I set its position manually by accessing the html content and getting the height, width etc.
Now, I have converted the overlay to a directive. But I cant access the html content of the directive itself to update the position.
What have I tried: I tried using replace:true in the directive. But when I try to access the div - I still get the directive element rather than the directive html content.
Eg:
My Directive:
<my-dir somevalue='something'>
</my-dir>
Directive HTML:
<div class="overlay">
<span>{{somevalue}}</span>
</div>
Now when I try to access the directive element, I want to access the class 'overlay'
First, in your directive template, you should probably be referring to somevalue, not something, since presumably somevalue is what's being set on the directive's scope.
Then to access the .overlay child element, in your directive's link function you can use the querySelector method on the directive's native DOM element, like so:
link: function (scope, el, attrs) {
var overlayElement = el[0].querySelector('.overlay');
}
The el that's passed into the link function is the jqLite-wrapped directive element, so el[0] gets you the native DOM element.
Here's a fiddle demonstrating this approach.

Use $compile inside Angular link function AND access directive arguements

I am making a directive in angular.js 1.x
I call the directive as follows:
<mydirective dirarg={{value-1}}></mydirective>
I would like to create the directive by putting code to manipulate the DOM in the directive's link function. The structure of the DOM generated by the link function is dependant on the value of dirarg, and I would like some elements to have a ng-click attribute.
I have managed to get ng-clicks to work by doing the following:
app.directive('calendar',function($compile){
return{
link: function(scope, element, attributes){
element.append($compile("<button ng-click='testt()'>hi</button>")(scope));
}
}
});
When I click the button generated by this directive, the function testt() runs. However, the call to testt() breaks if I try to access dirarg.
app.directive('calendar',function($compile){
return{
scope:{
dirarg: '#'
},
link: function(scope, element, attributes){
element.append($compile("<button ng-click='testt()'>"+scope.dirarg+"</button>")(scope));
}
}
});
This code now populates the text of the button with dirarg, but the ng-click functionality breaks. Does somebody know how I can both have ng-click working, and access the arguments to the directive?
To be clear, this button is just an example. My actual situation is a lot more complicated than a button, so don't tell me better ways to make buttons in angular.
When add scope property to directive option it makes isolated scope for directive, you need pass testt function inside directive too, or define testt function inside directive link functiontion
<mydirective dirarg={{value-1}} testt="testt"></mydirective>
app.directive('calendar',function($compile){
return{
scope:{
dirarg: '#',
testt: '='
},
link: function(scope, element, attributes){
element.append($compile("<button ng-click='testt()'>"+scope.dirarg+"</button>")(scope));
}
}
});

How to exclude the directive's own element?

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.

Transclude in AngularJS without adding new element

Is there any way to transclude some content into a directive without adding extra elements.
For example
directive:
{
scope: {
someParam: "="
},
link: function(scope, element, attrs){
//do something
},
transclude: true,
template:'<div ng-transclude></div>'
}
source html:
<div my-directive some-param="somethingFromController">
my transcluded content: {{somethingElseFromController}}
</div>
With this example an extra div gets added to the markup. Normally this would be fine but I'm trying to use this directive inside a table so adding a div tag screws things up.
I also tried not specifying transclude or template which gets rid of the extra div tag but now {{somethingElseFromController}} cannot be found as the "transcluded" content is in an isolated scope. I know I could just get the parameters for my directive from the attrs object in the linking function instead of creating an isolated scope but I'd rather avoid needing to evaluate strings with scope.$apply().
Anyone know how to accomplish this?
Thanks!
What #Vakey answered is what I was searching for.
But as today, the Angular documentation says:
The transclude function that is passed to the compile function is deprecated, as it e.g. does not know about the right outer scope. Please use the transclude function that is passed to the link function instead.
So I used instead the controller (for the moment) and its $transclude function, as part of the example shown on the $compile documentation:
controller: function($scope, $element, $transclude) {
var transcludedContent, transclusionScope;
$transclude(function(clone, scope) {
$element.append(clone);
transcludedContent = clone;
transclusionScope = scope;
});
},
This actually is possible with Angular. Directives such as ng-repeat do this. Here is how you do it:
{
restrict: 'A',
transclude: true,
compile: function (tElement, attrs, transclude) {
return function ($scope) {
transclude($scope, function (clone) {
tElement.append(clone);
});
};
}
};
So what's going here? During linking, we are just appending the clone, which is the element we are trying to transclude, into the directive's element. Angular will apply $scope onto the clone element so you can do all the angular goodness inside that element.
To elaborate on #rob's post...
Transclusion requires that Angular creates an element that is a clone of the content of whatever tag the directive is/lives on... If the content is text, it will wrap it in a span.
This is so it has a DOM element to apply the scope to when $compile is called.
So, basically transclude adds an element for the same reason you can't $compile('plain text here {{wee}}').
Now, you can do something sort of like what you're trying to do with $interpolate, which allows you to apply a scope to bindings in a string like "blah {{foo}}".... but since I'm really not sure what you're trying to do, I can't really give you a specific example.

Categories

Resources