161

I'm using the "Angularised" version of the Spin control, as documented here: http://blog.xvitcoder.com/adding-a-weel-progress-indicator-to-your-angularjs-application/

One of the things I don't like about the shown solution is the use of jQuery in the service that effectively attaches the spin control to the DOM element. I would prefer to use angular constructs to access the element. I'd also like to avoid "hard-coding" the id of the element that the spinner needs to attach to within the service and instead use a directive that sets the id in the service (singleton) so that other users of the service or the service itself don't need to know that.

I'm struggling with what angular.element gives us vs what document.getElementById on the same element id gives us. eg. This works:

  var target = document.getElementById('appBusyIndicator');

None of these do:

  var target = angular.element('#appBusyIndicator');
  var target = angular.element('appBusyIndicator');

I'm clearly doing something that should be fairly obvious wrong! Can any one help?

Assuming I can get the above working, I have a similar problem with trying to replace jQuery access to the element: eg $(target).fadeIn('fast'); works angular.element('#appBusyIndicator').fadeIn('fast') or angular.element('appBusyIndicator').fadeIn('fast') doesn't

Can someone point me to a good example of documentation that clarifies use of an Angular "element" vs the DOM element? Angular obviously "wraps" the element with its own properties, methods etc but it's often hard to get the original value. For example if I have an <input type='number'> field and I want to access the original contents that are visible in the ui when the user types "--" (without the quotes) I get nothing, presumably because the "type=number" means Angular is rejecting the input even though it's visible in the UI and I want to see it so I can test for it and clear it down.

Any pointers/answers appreciated.

Thanks.

irascian
  • 1,945
  • 2
  • 15
  • 9

10 Answers10

234

It can work like that:

var myElement = angular.element( document.querySelector( '#some-id' ) );

You wrap the Document.querySelector() native Javascript call into the angular.element() call. So you always get the element in a jqLite or jQuery object, depending whether or not jQuery is available/loaded.

Official documentation for angular.element:

If jQuery is available, angular.element is an alias for the jQuery function. If jQuery is not available, angular.element delegates to Angulars built-in subset of jQuery, that called "jQuery lite" or jqLite.

All element references in Angular are always wrapped with jQuery or jqLite (such as the element argument in a directives compile or link function). They are never raw DOM references.

In case you do wonder why to use document.querySelector(), please read this answer.

Community
  • 1
  • 1
kaiser
  • 21,817
  • 17
  • 90
  • 110
  • 42
    Any reason one would prefer document.querySelector('#...') to simply using document.getElementById()? – Kato May 22 '14 at 15:33
  • 4
    You may also do this on an angular element : var elDivParent = angular.element( elem[0].querySelector('#an-id'));, elem being an angular element. This way you may search inside an element. Useful for unit tests. – unludo Jun 05 '14 at 09:03
  • 4
    @Kato More info in [this answer](http://stackoverflow.com/a/26848360/376483). Hope that helps. – kaiser Dec 03 '15 at 17:43
  • working with Google Maps API, and this worked: ```var autocomplete = new google.maps.places.Autocomplete( $document[0].querySelector('#address'), { types: ['geocode'], componentRestrictions: {country: 'us'} } );```. Thanks for the tip @kaiser. For some reason, the maps api complained that what i passed in the first param was not an input when i used ```angular.element(document.querySelector('#address'))```, but all's well that ends well. – nymo Dec 21 '16 at 17:34
49

You should read the angular element docs if you haven't yet, so you can understand what is supported by jqLite and what not -jqlite is a subset of jquery built into angular.

Those selectors won't work with jqLite alone, since selectors by id are not supported.

  var target = angular.element('#appBusyIndicator');
  var target = angular.element('appBusyIndicator');

So, either :

  • you use jqLite alone, more limited than jquery, but enough in most of the situations.
  • or you include the full jQuery lib in your app, and use it like normal jquery, in the places that you really need jquery.

Edit: Note that jQuery should be loaded before angularJS in order to take precedence over jqLite:

Real jQuery always takes precedence over jqLite, provided it was loaded before DOMContentLoaded event fired.

Edit2: I missed the second part of the question before:

The issue with <input type="number"> , I think it is not an angular issue, it is the intended behaviour of the native html5 number element.

It won't return a non-numeric value even if you try to retrieve it with jquery's .val() or with the raw .value attribute.

garst
  • 6,042
  • 1
  • 29
  • 24
  • 2
    We have the full jQuery library - otherwise my $ scenario explained above wouldn't work. My question was around why the $ works but angular.element doesn't - even though full jQuery is available. – irascian Jun 21 '13 at 13:17
  • 1
    Are you including jQuery before or after angular.js? It should be included **before** angular. Selectors by id *do* work with jQuery available: http://jsbin.com/ohumiy/1/edit – garst Jun 21 '13 at 13:45
  • 1
    They are working in another directive (ie angular.element to access something by id). Problem seems to be the way this control works by attaching itself to that element, although I don't understand why the same jQuery equivalent directly should work. Using jQuery directly my directive HAS to have a closing tag - self-closing tag doesn't work (no errors just a blank screen) which makes me suspect the control. – irascian Jun 23 '13 at 07:38
  • 2
    On my 2nd point there is also something like view$value which can be used to retrieve the contents of an element but in the case of input type=number where the user has input "--" this is empty. I want a property something like $rawInputValue on element to see the contents BEFORE Angular has stepped in because the "--" is still in the UI even though I don't have access to it programatically in my directive. – irascian Jun 23 '13 at 07:39
29
var target = document.getElementById('appBusyIndicator');

is equal to

var target = $document[0].getElementById('appBusyIndicator');
Dasun
  • 3,244
  • 1
  • 29
  • 40
  • 1
    It is better to use the second example to ensure that your dependencies are explicit and can be mocked, right? – Luke Aug 02 '18 at 13:22
18

If someone using gulp, it show an error if we use document.getElementById() and it suggest to use $document.getElementById() but it doesn't work.

Use -

$document[0].getElementById('id')
msanford
  • 11,803
  • 11
  • 66
  • 93
Kanchan
  • 1,609
  • 3
  • 22
  • 37
17

I don't think it's the right way to use angular. If a framework method doesnt exist, don't create it! This means the framework (here angular) doesnt work this way.

With angular you should not manipulate DOM like this (the jquery way), but use angular helper such as

<div ng-show="isLoading" class="loader"></div>

Or create your own directive (your own DOM component) in order to have full control on it.

BTW, you can see here http://caniuse.com/#search=queryselector querySelector is well supported and so can be use safely.

Thomas Decaux
  • 21,738
  • 2
  • 113
  • 124
  • 2
    You're absolutely right. 90%+ of the cases that I thought I need to manually manipulate the DOM, I ended up refactoring the code to remove the need and in the end the code is much cleaner. – Stephen Chung Aug 23 '15 at 08:19
17

You can access elements using $document ($document need to be injected)

var target = $document('#appBusyIndicator');
var target = $document('appBusyIndicator');

or with angular element, the specified elements can be accessed as:

var targets = angular.element(document).find('div'); //array of all div
var targets = angular.element(document).find('p');
var target = angular.element(document).find('#appBusyIndicator');
Nishant Baranwal
  • 1,048
  • 1
  • 10
  • 18
10

You should inject $document in your controller, and use it instead of original document object.

var myElement = angular.element($document[0].querySelector('#MyID'))

If you don't need the jquery style element wrap, $document[0].querySelector('#MyID') will give you the DOM object.

Adamy
  • 2,789
  • 3
  • 27
  • 25
  • 1
    You may as well just reference window.document directly instead of using Angulars $document service overhead. – MatBee Jun 11 '15 at 19:01
  • 5
    Of course you can if you don't worry about mocking $document in your unittest – Adamy Jun 11 '15 at 22:18
  • Is not clear to me: I get this warnig: You should use the $document service instead of the default document object, but what does it mean? Any docs about it? Thanks! – realnot Feb 16 '18 at 08:42
  • @realnot you can access the HTML DOM Document Object from anywhere in your javascript. https://www.w3schools.com/jsref/dom_obj_document.asp – Adamy Feb 18 '18 at 23:45
6

This worked for me well.

angular.forEach(element.find('div'), function(node)
{
  if(node.id == 'someid'){
    //do something
  }
  if(node.className == 'someclass'){
    //do something
  }
});
susrut316
  • 140
  • 1
  • 5
  • 3
    element is undefined ? angular.element.find is not a function for me trying yours without JQuery. – landed May 26 '15 at 17:07
  • 3
    Please don't do this. Use one of the other examples. This uses no hash table lookup optimizations at all. – MatBee Jun 11 '15 at 19:00
4

Improvement to kaiser's answer:

var myEl = $document.find('#some-id');

Don't forget to inject $document into your directive

edi9999
  • 19,701
  • 13
  • 88
  • 127
Rob H
  • 348
  • 2
  • 5
  • 21
    As mentioned in the angular documentation for [element.find](https://docs.angularjs.org/api/ng/function/angular.element): `find() - Limited to lookups by tag name`, it doesn't work on ids. Also you have an extra closing `)`. – paaat Feb 09 '15 at 08:00
3

Maybe I am too late here but this will work :

var target = angular.element(appBusyIndicator);

Notice, there is no appBusyIndicator, it is plain ID value.

What is happening behind the scenes: (assuming it's applied on a div) (taken from angular.js line no : 2769 onwards...)

/////////////////////////////////////////////
function JQLite(element) {     //element = div#appBusyIndicator
  if (element instanceof JQLite) {
    return element;
  }

  var argIsString;

  if (isString(element)) {
    element = trim(element);
    argIsString = true;
  }
  if (!(this instanceof JQLite)) {
    if (argIsString && element.charAt(0) != '<') {
      throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element');
    }
    return new JQLite(element);
  }

By default if there is no jQuery on the page, jqLite will be used. The argument is internally understood as an id and corresponding jQuery object is returned.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Vishal Anand
  • 1,431
  • 1
  • 15
  • 32