Suppose you have made a new directive in angularJS with attribute restriction, say my-directive.
Suppose to have the following HTML code:
<img src="..." my-directive />
Now, the load event of the <img> is triggered only when all the code of my-directive is executed and the DOM is completly loaded, or when all the information of the natively <img> tag are loaded (for example the src data, the style, ...)?
I want to know this information because I want understand what is the effect to intercept the load event inside the code of a directive.
As my comment mentions, you'll likely want to use a pre-compile link funciton, but you will also be aided by using ng-src instead of the native src attribute, which allows angular to insert itself into the process. Then you can do your interception prior to compilation like so:
module.directive('interceptImg',function(){
//other functions on your directive like template and controller
compile:{
return: {
pre: function preLink(scope, iElement, iAttrs, controller){
//you can listen/bind your events here by accessing iElement
}
}
}
})
Checkout the docs for $compile
Related
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>
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.
});
}
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.
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
I have a form with sections that are scrolled and lined up automatically when the user interacts with it. I would like to have all the logic defined in a directive but at the moment can't figure out how to get some of the DOM manipulation logic out of my controller. Most of the functionally can be attached to on scrolls, clicks or focus events but how do I get a function attached to my scope to trigger some DOM manipulation without having the DOM logic in my controller?
What I currently have is
$scope.scrollToNextSection = function(section){
//DOM manipulation logic to scroll to next section.
}
Would it be valid for me to have
directiveDOMObject.scrollToNextSection = function(section){
//DOM manipulation logic to scroll to next section.
}
and call it from my controller with
$scope.scrollToNextSection = function(section){
directiveDOMObject.scrollToNextSection(section);
}
Is attaching a function to a DOM object like this ok so all my DOM manipulation can be contained in the directive? Is there a standard pattern for triggering DOM manipulation logic defined in a directive from a controller?
HTML handles scrolling within the page using name anchors. <a name="sectionX"> and <a href="#sectionX"> These are getting heavily (mis)used in an SPA if you use a router.
The scope/controller does not know about the dom and cannot/shouldnot change it. The FAQ says:
DOM Manipulation
Stop trying to use jQuery to modify the DOM in controllers. Really.
That includes adding elements, removing elements, retrieving their
contents, showing and hiding them. Use built-in directives, or write
your own where necessary, to do your DOM manipulation. See below about
duplicating functionality.
Someone has written an ngScrollTo directive which keeps the logic in the view + directive. I haven't tried it out but it looks like the way to go.
See also See Anchor links in Angularjs? for alternative solutions.
Is attaching a function to a DOM object like this ok so all my DOM manipulation can be contained in the directive
The short answer here is no, not really. If the controller has business logic, then it shouldn't be concerned with what's going on in the DOM.
Is there a standard pattern for triggering DOM manipulation logic defined in a directive from a controller?
Not sure if they're standard, but they are a few ways. Their common theme is that the controller, that handles business logic either directly or via services, doesn't actually call the directive, or really know what's going on in the DOM / view. It just provides "hooks" in one form or another, so the directive can react appropriately.
The ways I know of are:
React to changes of variable on the scope. So you can have a variable, like state
<div scroll-listen-to="state"> .... </div>
And a directive, scrollListenTo, with a scope + link function as follows:
scope: {
scrollListenTo: '='
},
link: function postLink(scope, iElement, iAttrs) {
scope.$watch('scrollListenTo', function(newValue, oldValue) {
// Do something, maybe with scrolling?
});
}
React to events $broadcast from the controller. This sends the event to child scopes (and so scopes in directives within the sending scope). The name of this event can also be configurable. So, for example
<div ng-controller="MyController">
<input scroller-event="MyController::stateChanged" />
</div>
Then in MyController, at the appropriate point:
$scope.$broadcast('MyController::stateChanged', 'someData');
And in the directive:
scope: {
'eventName': '#scrollerEvent'
},
link: function postLink(scope, iElement, iAttrs) {
scope.$on(scope.eventName, function(e, data) {
// Do something the data
});
}
React to events $emited from the controller. This is very similar to $broadcast, but the event is emitted upwards through the hierarchy. You can "wrap" several controllers and then they can send events to a directive that wraps them.
<div scroller-event="MyController::stateChanged">
<div ng-controller="MyController">
</div>
<div ng-controller="MyController">
</div>
</div>
Then in MyController
$scope.$emit('MyController::stateChanged', 'someData');
In this case, you probably shouldn't use the scope parameter in the directive, as this would create an isolated scope, which in this case probably isn't desired. The directive could have something like
link: function postLink(scope, iElement, iAttrs) {
var eventName = iAttrs.scrollerEvent;
scope.$on(eventName, function(e, data) {
// Do something with the data, like scrolling.
});
}
You say you're using a form. You could create a set of custom directives that interact, much like ngModel and ngForm interact. So, for example, you could have:
<div scroller-container>
<input scroll-on-focus />
<input scroll-on-focus />
</div>
Then in the scrollOnFocus directive
require: '^scrollerContainer',
link: function(scope, iElement, iAttrs, scrollerContainerController) {
iElement.on('focus', function() {
scrollerContainerController.scrollTo(iElement);
});
}
And in the scollerContainer directive, you must define scrollTo on its controller
controller: function() {
this.scrollTo = function(element) {
// Some code that scrolls the container so the element is visible
};
}
I realise the above ways are not especially specific to your issue of scrolling: they are more generic, and to be honest, I'm not yet sure which to recommend in any given case.
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