I have a single page application (angularjs + angular-ui-router) and there are many elements with 'someCssClass'.
I need to add a global handler (at window object), that handles all these elements init event. Is there a way to do this, except manually adding ngInit (or some like this) for each element in each view?
I end up with directive and linkoption.
app.directive('someCssClass', function () {
return {
restrict: 'C',
link: function initHandler(scope, element, attrs){}
};
});
Example on JSFiddle.
Angular directive has restrict parameter, which could be class. As you already have classes on elements — you could use them.
And $emit/$on for events, with what data you need.
Related
I am using an angularJS directive to get full-height elements, and it's working great. The problem is that I load the whole jQuery library just for using it once : on the $(element).
I've been playing around with angular.element or element[0] or event angular.element(element[0]) but I cannot make it work. The element appears undefined or nothing happens at all.
We should be able to select that element right ? Here's my code. Thanks a lot
app.directive('fullHeight', ['$window', function ($window) {
return {
restrict: 'A',
link: function (scope, element) {
scope.initializeWindowSize = function () {
$(element).css('min-height', $window.innerHeight);
};
scope.initializeWindowSize();
angular.element($window).bind('resize', function () {
scope.initializeWindowSize();
});
}
};
}]);
I load the whole jQuery library just for using it once
If jQuery is available, angular.element is an alias for the jQuery function. If jQuery is not available, angular.element delegates to Angular's built-in subset of jQuery, called "jQuery lite" or jqLite.
You don't need to load the library, just use element, as it is the jqLite-wrapped element that this directive matches.
element.css('min-height', $window.innerHeight);
However important note Angular's jqLite
css() - Only retrieves inline-styles, does not call `getComputedStyle(). As a setter, does not convert numbers to strings or append 'px', and also does not have automatic property prefixing.
You can use element.style.minHeight = $window.innerHeight
element is already wrapped within angular's jqLite. element within the link function in a directive refers to the main wrapper in the template. You can then use element[0].querySelector('selector_here') to get specific elements within.
I want to fetch all <img> elements inside an element. For this I created a directive 'find-images':
app.directive('findImages', function() {
return {
restrict: 'A',
scope: {},
link: function(scope, element, attrs) {
var img_elements = element[0].querySelectorAll('img');
console.log(img_elements) //doesn't have img elements nested inside ngInclude, uiView
}
}
})
I used this directive in the body tag like this:
<body find-images>
However, I am using ui-router and in many places ng-include(to load nested partials). And the problem I am facing is that querySelectorAll isn't returning img elements which are nested inside ng-include and ui-view. How can I get elements which are nested in them? Also, all nested elements(ui-view, ng-include) are within the body tag.
Implement the logic in the statechangesuccess event fired in ui-router which you could consider the same as page load.
angular.module('app', [])
.run(['$document', function ($document) {
$rootScope.$on("$stateChangeSuccess", function (event, toState, toParams, fromState, fromParams) {
var body = $document.getElementsByTagName("body")[0];
var img_elements = element[body].querySelectorAll('img');
}
});
UPDATE
Another option is to use the onload event raised by ng-include when it is finished rendering.
Also don't forget nginclude is itself a directive, you can use ngswitch in many instances rather than nginclude and render custom directives instead.
That way you have less logic in your controller, and more obvious in the view what is being rendered when.
The problem is, ng-include will include the element after the directive's link function. It will depend on the structure of your app and what you're trying to achieve, but essentially (via timeout, or promises, or callbacks), you need to have a mechanism to determine when your DOM is in a stable state (ng-includes may have more ng-includes etc and it can get complex), and then run your directive logic after that.
I have been developing an advanced web-based GUI in AngularJS. Recently, I decided to use the call document.getElementsByClassName() (I hate using element collecting methods, but here I had to use one) and my boss flipped his lid for accessing the document element. He says that I "need to use only Angular calls for everything", even for element collection! Is there an "Angular way" to collect elements by class name? If so, which way is better to use within the Angular framework? Please provide reasons why. Thanks!
UPDATE: Why I need to use an element collector...
So, I really wish I didn't have to do this, but I do...
I am using a third-party directive that I found online called the Bootstrap DateTimePicker. Its pretty cool and very nice looking, yet it might have a bug...
First, I make a directive bound to an attribute, stating that the element I pass in is meant to be a "DateTimePicker". I then pass that element to the DateTimePicker function.
When invoked, this function creates a new div with absolute positioning and appends it to the body of the page.
Now, I open a dialog in my GUI that has a table in it. On each row of the table, I have two DateTimePickers: one for end-date and one for start-date.
My problem is that, once I leave my screen and the elements which the DateTimePickers were bound to are destroyed, the DateTimePickers still remain! If I open the dialog box again, it creates a ton more of these divs as well!
Until I could determine a true solution to this issue, I decided to use the element collector as a temporary quick-fix. I grab all of the elements with the datetimepicker class and perform a:
elem[i].parentNode.removeChild(elem[i]);
Not having your exact use case but knowing that you are attempting to aggregate elements by class name in your controller makes me agree with you boss. Think of the controller as an object which exposes data and and services to your declarative html page. The data is bound into the markup for presentation and possible modification. THe services are usually wrapped in functions on your controller which are then tied to event handling directives like ng-click or ng-change. These services should operate exclusively on your data and never touch the DOM. If you need to modify a DOM element in your declarative markup then that should be done through directives like ng-class etc.
In any case, It would be useful to know what you are trying to accomplish so as to give you a better idea of the "angular way" to approach the problem.
Well, I have my answer. This does not solve the question "Grab all elements with a certain class name without touching the document element" yet it does solve my problem and eliminates my need to use document.getElementsByClassName.
First of all, it turns out that every element using the DateTimePicker directive have an element.datetimepicker("remove") function.
I use a directive for each DateTimePicker:
components.directive('DateTimePicker', function() {
// Requires bootstrap-datetimestamp.js
return {
restrict: 'E',
replace: true,
scope: {
dateTimeField: '='
},
template:
'<div>' +
'<input type="text" readonly data-date-format="yyyy-mm-ddThh:ii:ssZ" data-date-time required/>'+
'</div>',
link: function(scope, element, attrs, ngModel)
{
var input = element.find('input');
input.
datetimepicker(
{
//stuff
})
.on('changeDate', function(ev)
{
//more stuff
});
...
Directive drastically shortened for the sake of your eyeballs...
I then need to remove the DateTimePicker and the input it is bound to from the DOM on destruction of the dialog box that the input is a child of. To do so, I added this to my directive:
scope.$on("$destroy",function handleDestroyEvent()
{
input.datetimepicker("remove");
input = null;
});
And it works! The DateTimePicker gets removed, the DateTimePicker's handles to the input are cleaned up, and I've marked my input for the GC! WooHoo! Thanks everybody!
If you include jQuery in your project before AngularJS, Angular will use jQuery instead of jqLite for the angular.element function. This means you should be able to use jQuery's selectors for finding / referencing DOM elements.
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'm working with angularJS and would like to remove/modify the class of a specific child element (who's class/id is unknown because it is being added to the parent element dynamically).
I understand that using angular you can say something like:
angular.element('someknownParentClass').addClass('newClass');
However, I want to do something similar to:
angular.element('.someknownParentClass').find('i').addClass('newClass');
The class 'someknownParentClass' is a class assigned to an 'a' tag, and inside this tag, I have an 'i' tag with a glyphicon icon class that I would like to change from inside a specific function. It seems like this method isn't working. I know angular's jqLite has a children() attribute, but i'm a little unsure of how to use this or if it would be useful in this case, or maybe using jQuery with angular would be my best option (from what I understand, that's different than jqLite). Any suggestions?
I'm assuming you're doing this in a directive, in which case you can do something like:
var elem = angular.element('.someknownParentClass');
var iElems = elem.children('i');
iElems.addClass("newClass");
If you're more comfortable in jQuery, I don't see a problem in using it in an angular directive instead. According to the docs, angular.element is an alias for jQuery:
https://docs.angularjs.org/api/ng/function/angular.element
$('.someknownParentClass').find('i').addClass("newClass");
You shouldn't need to use angular.element if you are doing things within a directive. If you want to use angular.element in code, you are probably better off using jQuery. I put together a sample jsfiddle that binds a directive to a pre-defined class (some-known-parent-class) and searches for all the "i" elements under it and adds newClass class to it:
var mod = angular.module("myApp", []);
mod.directive("someKnownParentClass", function () {
return {
restrict: "C",
link: function (scope, element, attrs) {
element.find("i").addClass("newClass");
}
}
});
This way, you are decoupling the direct DOM manipulation (i.e. angular.element) through using a directive to do the manipulation. Fiddle here.