How to get element by classname or id - angularjs

I am trying to find the element in html by angularjs.
Here is the html:
<button class="btn btn-primary multi-files" type="button">
<span>Choose multiple files</span>
</button>
<br/><br/>
<input ng-file-select type="file" multiple style="display: none"/><br/>
I am trying to get the button element by class name multi-files, then I tried
var multibutton = angular.element(element.getElementsByClassName(".multi-files"));
But it does not work, and tried element.find but it only works for tag.
Is there any function that can get element by id or classname in angularjs?

getElementsByClassName is a function on the DOM Document. It is neither a jQuery nor a jqLite function.
Don't add the period before the class name when using it:
var result = document.getElementsByClassName("multi-files");
Wrap it in jqLite (or jQuery if jQuery is loaded before Angular):
var wrappedResult = angular.element(result);
If you want to select from the element in a directive's link function you need to access the DOM reference instead of the the jqLite reference - element[0] instead of element:
link: function (scope, element, attrs) {
var elementResult = element[0].getElementsByClassName('multi-files');
}
Alternatively you can use the document.querySelector function (need the period here if selecting by class):
var queryResult = element[0].querySelector('.multi-files');
var wrappedQueryResult = angular.element(queryResult);
Demo: http://plnkr.co/edit/AOvO47ebEvrtpXeIzYOH?p=preview

You don't have to add a . in getElementsByClassName, i.e.
var multibutton = angular.element(element.getElementsByClassName("multi-files"));
However, when using angular.element, you do have to use jquery style selectors:
angular.element('.multi-files');
should do the trick.
Also, from this documentation "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.""

#tasseKATT's Answer is great, but if you don't want to make a directive, why not use $document?
.controller('ExampleController', ['$scope', '$document', function($scope, $document) {
var dumb = function (id) {
var queryResult = $document[0].getElementById(id)
var wrappedID = angular.element(queryResult);
return wrappedID;
};
PLUNKR

If you want to find the button only by its class name and using jQLite only, you can do like below:
var myListButton = $document.find('button').filter(function() {
return angular.element(this).hasClass('multi-files');
});
Hope this helps. :)

Related

Html element with AngularJS attribute generate after init AngularJS

I've AngularJS issue when there are some elements(button) generate dynamically or generate after init AngularJS. Source code below shows the button not able to trigger the ng-click attribute. Any ideas that can trigger the AngularJS attribute after init the AngularJS. Thanks.
OperationFormatter: function (value, row) {
var operations = [];
operations.push('<button class="btn-mini glyphicon glyphicon-edit" ng-
click="myFunc()" title="Shared_Edit"></button>');
return operations.join(' ');
// This function only can execute after init the AngularJS due to certain
conditions.
}
$(function () {
MyCtrl();
})
function MyCtrl($scope) {
var app = angular.module("appAffiliateInfo", []);
app.controller("ctrlBankInfo", function ($scope) {
$scope.myFunc = function () {
alert('ok');
};
});
};
I assume you're trying to dynamically insert the generated HTML code in your template.
It cannot be done this way, as AngularJS already parsed your template at "runtime" and created all its watchers.
You have to use the $compile service, like so:
// scope: the scope containing the function called by ng-click
// htmlCode: your html code (as a string)
var newElem = $compile(htmlCode)(scope);
// use the proper code here, depending on what you want to achieve with the HTML element
document.body.appendChild(newElem[0]);
When "compiled" by $compile, the HTML code is parsed in order to create the required watchers. You must make sure that you provide the correct scope to $compile, or the function intended to ng-click will never be called.

Adding class to element using Angular JS

I know how to add a class on click of a button in 'jQuery'
$('#button1').click(function(){
$('#div1').addClass('alpha');
});
I want to achieve same thing by angular js. I have a controller - myController1. Can someone help me do it eazily?
AngularJS has some methods called JQlite so we can use it. see link
Select the element in DOM is
angular.element( document.querySelector( '#div1' ) );
add the class like .addClass('alpha');
So finally
var myEl = angular.element( document.querySelector( '#div1' ) );
myEl.addClass('alpha');
You can use ng-class to add conditional classes.
HTML
<button id="button1" ng-click="alpha = true" ng-class="{alpha: alpha}">Button</button>
In your controller (to make sure the class is not shown by default)
$scope.alpha = false;
Now, when you click the button, the $scope.alpha variable is updated and ng-class will add the 'alpha' class to your button.
Use the MV* Pattern
Based on the example you attached,
It's better in angular to use the following tools:
ng-click - evaluates the expression when the element is clicked (Read More)
ng-class - place a class based on the a given boolean expression (Read More)
for example:
<button ng-click="enabled=true">Click Me!</button>
<div ng-class="{'alpha':enabled}">
...
</div>
This gives you an easy way to decouple your implementation.
e.g. you don't have any dependency between the div and the button.
Read this to learn about the MV* Pattern
Try this..
If jQuery is available, angular.element is an alias for the jQuery function.
var app = angular.module('myApp',[]);
app.controller('Ctrl', function($scope) {
$scope.click=function(){
angular.element('#div1').addClass("alpha");
};
});
<div id='div1'>Text</div>
<button ng-click="click()">action</button>
Ref:https://docs.angularjs.org/api/ng/function/angular.element
First thing, you should not do any DOM manipulation in controller function.
Instead, you should use directives for this purpose. directive's link function is available for those kind of stuff only.
AngularJS Docs : Creating a Directive that Manipulates the DOM
app.directive('buttonDirective', function($timeout) {
return {
scope: {
change: '&'
},
link: function(scope, element, attrs) {
element.bind('click', function() {
$timeout(function() {
// triggering callback
scope.change();
});
});
}
};
});
change callback can be used as listener for click event.
querySelector is not from Angular but it's in document and it's in all DOM elements (expensive). You can use ng-class or inside directive add addClass on the element:
myApp.directive('yourDirective', [function(){
return {
restrict: 'A',
link: function(scope, elem, attrs) {
// Remove class
elem.addClass("my-class");
}
}
}
For Angular 7 users:
Here I show you that you can activate or deactivate a simple class attribute named "blurred" with just a boolean. Therefore u need to use [ngClass].
TS class
blurredStatus = true
HTML
<div class="inner-wrapper" [ngClass]="{'blurred':blurredStatus}"></div>
In HTML
To add the class named alpha, assign any variable like showAlpha to false first and then set it to true on click.
<div data-ng-class="{'alpha' : showAlpha}"> </div>
<button ng-click="addClass()"> </button>
In JS file
$scope.showAlpha = false;
$scope.addClass = function(){
$scope.showAlpha = true;
}
try this code
<script>
angular.element(document.querySelectorAll("#div1")).addClass("alpha");
</script>
click the link and understand more
Note: Keep in mind that angular.element() function will not find directly select any documnet location using this perameters
angular.element(document).find(...) or $document.find(), or use the standard DOM APIs, e.g. document.querySelectorAll()

angularjs directive rendered by third party component is not working

I have a simple angularjs directive that I use to show a tooltip.
<div tooltip-template="<div><h1>Yeah</h1><span>Awesome</span></div>">Click to show</div>
It works fine but now I'm trying to use it inside a timeline javascript component (visjs.org)
I can add items with html to this timeline like this
item...
item.content = "<div tooltip-template='<div><h1>Yeah</h1><span>Awesome</span></div>'>Click to show</div>";
$scope.timelineData.items.add(item);
The item is well displayed on the page BUT the code of the tooltip-template directive is never reached.
I suspect that because a third party component is rendering the item, the dom element is not read by angular.
I've tried to do a $scope.$apply(), $rootScope.$apply but the result is the same. The directive is never reached.
How can I tell angular to read my dom to parse these directives ?
Here is the directive code :
.directive("tooltipTemplate", function ($compile) {
var contentContainer;
return {
restrict: "A",
link: function (scope, element, attrs) {
var template = attrs.tooltipTemplate;
scope.hidden = true;
var tooltipElement = angular.element("<div ng-hide='hidden'>");
tooltipElement.append(template);
element.parent().append(tooltipElement);
element
.on('click', function () { scope.hidden = !scope.hidden; scope.$digest(); })
$compile(tooltipElement)(scope);
}
};
});
Edit
Added plunker : http://plnkr.co/edit/lNPday452GiZJBhMH4Kl?p=preview
I tried to do the same thing and came with a solution by manually creating scope and compile'ng the html of the directive with the scope using $compile method. Below a snippet
I did the below part inside a directive that created the timeline . Using the scope of that directive ,
var shiftScope = scope.$new(true);
shiftScope.name = 'Shift Name'
var shiftTemplate = $compile('<shift-details shift-name="name"></shift-details>')(shiftScope)[0];
I passed shiftTemplate as the content and it worked fine .
But trying to do this for >50 records created performance issues .

How to manipulate DOM elements with Angular

I just can't find a good source that explains to me how to manipulate DOM elements with angular:
How do I select specific elements on the page?
<span>This is a span txt1</span>
<span>This is a span txt2</span>
<span>This is a span txt3</span>
<span>This is a span txt4</span>
<p>This is a p txt 1</p>
<div class="aDiv">This is a div txt</div>
Exp: With jQuery, if we wanted to get the text from the clicked span, we could simple write:
$('span').click(function(){
var clickedSpanTxt = $(this).html();
console.log(clickedSpanTxt);
});
How do I do that in Angular?
I understand that using 'directives' is the right way to manipulate DOM and so I am trying:
var myApp = angular.module("myApp", []);
myApp.directive("drctv", function(){
return {
restrict: 'E',
scope: {},
link: function(scope, element, attrs){
var c = element('p');
c.addClass('box');
}
};
});
html:
<drctv>
<div class="txt">This is a div Txt</div>
<p>This is a p Txt</p>
<span>This is a span Txt </span>
</drctv>
How do I select only 'p' element here in 'drctv'?
Since element is a jQuery-lite element (or a jQuery element if you've included the jQuery library in your app), you can use the find method to get all the paragraphs inside : element.find('p')
To Answer your first question, in Angular you can hook into click events with the build in directive ng-click. So each of your span elements would have an ng-click attribute that calls your click function:
<span ng-click="myHandler()">This is a span txt1</span>
<span ng-click="myHandler()">This is a span txt2</span>
<span ng-click="myHandler()">This is a span txt3</span>
<span ng-click="myHandler()">This is a span txt4</span>
However, that's not very nice, as there is a lot of repetition, so you'd probably move on to another Angular directive, ng-repeat to handle repeating your span elements. Now your html looks like this:
<span ng-repeat="elem in myCollection" ng-click="myHandler($index)">This is a span txt{{$index+1}}</span>
For the second part of your question, I could probably offer an 'Angular' way of doing things if we knew what it was you wanted to do with the 'p' element - otherwise you can still perform jQuery selections using jQuery lite that Angular provides (See Jamie Dixon's answer).
If you use Angular in the way it was intended to be used, you will likely find you have no need to use jQuery directly!
You should avoid DOM manipulation in the first place. AngularJS is an MVC framework. You get data from the model, not from the view. Your example would look like this in AngularJS:
controller:
// this, in reality, typically come from the backend
$scope.spans = [
{
text: 'this is a span'
},
{
text: 'this is a span'
},
{
text: 'this is a span'
}
];
$scope.clickSpan = function(span) {
console.log(span.text);
}
view:
<span ng=repeat="span in spans" ng-click="clickSpan(span)">{{ span.text }}</span>
ng-click is the simpler solution for that, as long as I do not really understand what you want to do I will only try to explain how to perform the same thing as the one you have shown with jquery.
So, to display the content of the item which as been clicked, you can use ng-click directive and ask for the event object through the $event parameter, see https://docs.angularjs.org/api/ng/directive/ngClick
so here is the html:
<div ng-controller="foo">
<span ng-click="display($event)" >This is a span txt1</span>
<span ng-click="display($event)" >This is a span txt2</span>
<span ng-click="display($event)" >This is a span txt3</span>
<span ng-click="display($event)" >This is a span txt4</span>
<p>This is a p txt 1</p>
<div class="aDiv">This is a div txt</div>
</div>
and here is the javascript
var myApp = angular.module("myApp", []);
myApp.controller(['$scope', function($scope) {
$scope.display = function (event) {
console.log(event.srcElement.innerHtml);
//if you prefer having the angular wrapping around the element
var elt = angular.element(event.srcElement);
console.log(elt.html());
}
}]);
If you want to dig further in angular here is a simplification of what ng-click do
.directive('myNgClick', ['$parse', function ($parse) {
return {
link: function (scope, elt, attr) {
/*
Gets the function you have passed to ng-click directive, for us it is
display
Parse returns a function which has a context and extra params which
overrides the context
*/
var fn = $parse(attr['myNgClick']);
/*
here you bind on click event you can look at the documentation
https://docs.angularjs.org/api/ng/function/angular.element
*/
elt.on('click', function (event) {
//callback is here for the explanation
var callback = function () {
/*
Here fn will do the following, it will call the display function
and fill the arguments with the elements found in the scope (if
possible), the second argument will override the $event attribute in
the scope and provide the event element of the click
*/
fn(scope, {$event: event});
}
//$apply force angular to run a digest cycle in order to propagate the
//changes
scope.$apply(callback);
});
}
}
}]);
plunkr here: http://plnkr.co/edit/MI3qRtEkGSW7l6EsvZQV?p=preview
if you want to test things

Angular ng-click event delegation

So if i have a ul with 100 li's should there be ng-clicks in each li or is there a way to bind the event to the ul and delegate it to the li's kind of what jquery does? Would this be better or worse? Are we having 100 events or is it just one event in the end?
It seems angular doesn't do event delegation with repeaters. Someone opened an issue on github about it. The argument is if it actually leads to better performance.
There might be a workaround but it would require jQuery. It consists of creating a special directive to be used on a parent element and register the listener on its dom node.
Here's an example, that is passed a function name to be called when a children node is clicked, and is also passed a selector to help identify which children nodes to listen to.
Since angular's jquery implementation only gives us the bind method - which is limited to registering event listeners to the actual element - we need to load jQuery to have access to either the on or delegate method.
HTML
<ul click-children='fun' selector='li'>
<li ng-repeat="s in ss" data-index="{{$index}}">{{s}}</li>
</ul>
The function defined is defined in the controller and it expects to be passed an index
$scope.fun = function(index){
console.log('hi from controller', index, $scope.ss[index]);
};
The directive uses $parse to convert an expression into a function that will be called from the event listener.
app.directive('clickChildren', function($parse){
return {
restrict: 'A',
link: function(scope, element, attrs){
var selector = attrs.selector;
var fun = $parse(attrs.clickChildren);
element.on('click', selector, function(e){
// no need to create a jQuery object to get the attribute
var idx = e.target.getAttribute('data-index');
fun(scope)(idx);
});
}
};
});
Plunker: http://plnkr.co/edit/yWCLvRSLCeshuw4j58JV?p=preview
Note: Functions can be delegated to directives using isolate scopes {fun: '&'}, which is worth a look, but this increases complexity.
Working off of jm-'s example here, I wrote a more concise and flexible version of this directive. Thought I'd share. Credit goes to jm- ;)
My version attempts to call the function name as $scope[ fn ]( e, data ), or fails gracefully.
It passes an optional json object from the element which was clicked. This allows you to use Angular expressions and pass numerous properties to the method being called.
HTML
<ul delegate-clicks="handleMenu" delegate-selector="a">
<li ng-repeat="link in links">
<a href="#" data-ng-json='{ "linkId": {{link.id}} }'>{{link.title}}</a>
</li>
</ul>
Javascript
Controller Method
$scope.handleMenu = function($event, data) {
$event.preventDefault();
$scope.activeLinkId = data.linkId;
console.log('handleMenu', data, $scope);
}
Directive Constructor
// The delegateClicks directive delegates click events to the selector provided in the delegate-selector attribute.
// It will try to call the function provided in the delegate-clicks attribute.
// Optionally, the target element can assign a data-ng-json attribute which represents a json object to pass into the function being called.
// Example json attribute: <li data-ng-json='{"key":"{{scopeValue}}" }'></li>
// Use case: Delegate click events within ng-repeater directives.
app.directive('delegateClicks', function(){
return function($scope, element, attrs) {
var fn = attrs.delegateClicks;
element.on('click', attrs.delegateSelector, function(e){
var data = angular.fromJson( angular.element( e.target ).data('ngJson') || undefined );
if( typeof $scope[ fn ] == "function" ) $scope[ fn ]( e, data );
});
};
});
I'd love to hear feedback if anyone wishes to contribute.
I didn't test the handleMenu method since I extracted this from a more complex application.
Starting from BradGreens' delegateClicks above, I've adapted some code from georg which allows me to place the handleMenu function deeper in $scope (e.g. $scope.tomato.handleMenu).
app.directive('delegateClicks', function () {
return function ($scope, element, attrs) {
var fn = attrs.delegateClicks.split('.').reduce(function ($scope, p) { return $scope[p] }, $scope);
element.on('click', attrs.delegateSelector, function (e) {
var data = angular.fromJson(angular.element(e.target).data('ngJson') || undefined);
if (typeof fn == "function") fn(e, data);
});
};
});
According to the issue on GitHub there is no performance advantage to delegating event handling.
Simply use the ng-click directive with the item, $index, and $event:
<ul>
<li ng-repeat="item in collection"
ng-click="lineClicked(item, $index, $event)">
{{item}}
</li>
</ul>
$scope.lineClicked = function(item, index, event) {
console.log("line clicked", item, index);
console.log(event.target)
});
For more information, see
AngularJS Developer Guide - $event
AngularJS ng-repeat Directive API Reference - Special properties

Resources