Inside a directive I want to get the result of getBoundingClientRect() for a DOM element that is no where near the element of the directive.
How should I go about this? Service that just returns that object? Is it OK to have DOM logic in a service?
I'd suggest passing the id of the element the directive needs to interact with as an attribute to the directive. Then use the document object to get a handle to that element.
See: http://jsfiddle.net/afX63/6/.
You'll obviously need to work directly with the raw DOM element to access the information you're interested in.
Your markup:
<my-directive handle-id="thatOne"/>
<div id="thatOne">Your directive can find this element easily now</div>
You directive:
app.directive('myDirective', function () {
return {
link: function (scope, elem, attrs) {
var theHandle = angular.element(document.getElementById(attrs.handleId));
theHandle.text('Changed it');
}
}
});
Related
I am trying to create a directive as custom video control. I loading an html file to templateUrl of this directive. The problem is when there are more than one controls, they have the same src file set to all of them and they are sharing state of video as well. When I pause from another control, it pauses video being played on 1st control. Here is directive template that I am using:
dApp.directive('myVideoControl', function(){
return {
scope: {
cameraUrl: '=vcCameraUrl'
},
restrict: 'E',
templateUrl: './../../js/directives/myVideoControl.html',
link: function (scope, element, attrs) {
scope.playVideo = function(){
var v = document.getElementsByTagName("video")[0];
v.play();
}
scope.pauseVideo = function(){
var v = document.getElementsByTagName("video")[0];
v.pause();
}
}
}
});
Will greatly appreaciate if anyone can point out if I am doing anything wrong here.
Thanks.
It looks like the problem you are having is that you are looking up the element by tag name. Basically, every element in your dom with the tag <video> is going to be effected by any use of your directive.
The idea with directives, is that they provide direct access to the element that the directive was assigned. In your case element inside the link function parameters. So you need to reference the individual associated elements like this:
var v = element[0];
v.play();
If you have assigned the directive on a parent element, and want all children, then use the find() jqLite function on the directive element:
var v = element.find('video')[0];
v.play();
var v = document.getElementsByTagName("video")[0];
You are selecting the first video tag in the entire page.
instead get the element inside your template,something like
element.find('video')[0]
I'm making a directive that resizes a div based on changes in the controller. I need to calculate the amount of available space left in the browser window when changes happen to the model. How do you pass in the element from the link function into the $watch function?
In short, how do I manipulate the DOM based on changes to the model?
var module = angular.module('cmsApp')
module.directive("changeWidth", function($timeout) {
return {
restrict: 'A',
link: function($scope, element, attrs) {
width = element.width();
$scope.$watch('currentFolder', function(value){
// manipulate dom here
});
}
}
});
<!-- need to calculate the size of this -->
<div change-width class="col-md-9 right-pannel"></div>
I don't think Angular is even executing your directive based on your template code. It should be
<div change-width class="col-md-9 right-pannel"></div>
I know this is a source of errors if you are new to Angular. From the docs:
Angular uses name-with-dashes for its custom attributes and camelCase
for the corresponding directives which implement them)
I'm new to AngularJS. I've learned that I can find elements in the DOM using queries like the following:
var e = angular.element(document.querySelector('#id'));
var e = angular.element(elem.querySelector('.classname'));
This is useful for finding elements by ID, or by CSS class name. However, I need to be able to find an element using a different approach. I have an element that looks like the following:
<div my-directive class='myContainer'>...</div>
I can't query on 'myContainer' because of how much its reused. For that reason, I would like to find any element with the attribute 'my-directive'. How do I search the DOM and find any element that makes use of 'my-directive'?
Rather than querying the DOM for elements (which isn't very angular see "Thinking in AngularJS" if I have a jQuery background?) you should perform your DOM manipulation within your directive. The element is available to you in your link function.
So in your myDirective
return {
link: function (scope, element, attr) {
element.html('Hello world');
}
}
If you must perform the query outside of the directive then it would be possible to use querySelectorAll in modern browers
angular.element(document.querySelectorAll("[my-directive]"));
however you would need to use jquery to support IE8 and backwards
angular.element($("[my-directive]"));
or write your own method as demonstrated here Get elements by attribute when querySelectorAll is not available without using libraries?
You haven't stated where you're looking for the element. If it's within the scope of a controller, it is possible, despite the chorus you'll hear about it not being the 'Angular Way'. The chorus is right, but sometimes, in the real world, it's unavoidable. (If you disagree, get in touch—I have a challenge for you.)
If you pass $element into a controller, like you would $scope, you can use its find() function. Note that, in the jQueryLite included in Angular, find() will only locate tags by name, not attribute. However, if you include the full-blown jQuery in your project, all the functionality of find() can be used, including finding by attribute.
So, for this HTML:
<div ng-controller='MyCtrl'>
<div>
<div name='foo' class='myElementClass'>this one</div>
</div>
</div>
This AngularJS code should work:
angular.module('MyClient').controller('MyCtrl', [
'$scope',
'$element',
'$log',
function ($scope, $element, $log) {
// Find the element by its class attribute, within your controller's scope
var myElements = $element.find('.myElementClass');
// myElements is now an array of jQuery DOM elements
if (myElements.length == 0) {
// Not found. Are you sure you've included the full jQuery?
} else {
// There should only be one, and it will be element 0
$log.debug(myElements[0].name); // "foo"
}
}
]);
Your use-case isn't clear. However, if you are certain that you need this to be based on the DOM, and not model-data, then this is a way for one directive to have a reference to all elements with another directive specified on them.
The way is that the child directive can require the parent directive. The parent directive can expose a method that allows direct directive to register their element with the parent directive. Through this, the parent directive can access the child element(s). So if you have a template like:
<div parent-directive>
<div child-directive></div>
<div child-directive></div>
</div>
Then the directives can be coded like:
app.directive('parentDirective', function($window) {
return {
controller: function($scope) {
var registeredElements = [];
this.registerElement = function(childElement) {
registeredElements.push(childElement);
}
}
};
});
app.directive('childDirective', function() {
return {
require: '^parentDirective',
template: '<span>Child directive</span>',
link: function link(scope, iElement, iAttrs, parentController) {
parentController.registerElement(iElement);
}
};
});
You can see this in action at http://plnkr.co/edit/7zUgNp2MV3wMyAUYxlkz?p=preview
I really love how the new ng-click directive in Angular now automatically includes functionality for touch events. However, I am wondering if it is possible to access that touch-event service from my custom directive? I have lots of directives that require that I bind a click event to the given element, but I'm simply doing that using the typical jquery syntax (ex: element.on('click', function(){ ... })). Is there a way that I can bind an ng-click event to an element within a directive? Without having to manually put a ng-click tag on my element in the HTML of my view...?
I want to be able to harness the power of both click and touch events. I could obviously import a library (such as HammerJS or QuoJS) but I would prefer not to have to do that, especially since Angular is already doing it.
I can access the $swipe service and bind different elements to that, but is there a similar service for ngTouch?
For reference, this is an example of when I would want to do this:
mod.directive('datepicker', ['$timeout', function($timeout){
return {
link: function(scope, elem, attrs){
var picker = new DatePicker();
elem.on('click', function(e){
picker.show();
});
// I would rather do something like:
// elem.on('ngTouch', function(){ ... });
//
// or even:
// $ngTouch.bind(elem, {'click': ..., 'touch': ...});
}
}
}]);
UPDATE: As noted by below, the source code for the ng-click directive is here. Can anyone see a way to harness that code and turn it into a "bindable" service?
I don't think that's quite the right approach. I'd approach this by using a template within your directive and then using ngTouch within that.
mod.directive('datepicker', ['$timeout', function ($timeout) {
return {
template: '<div ng-touch="doSomethingUseful()"></div>',
link: function (scope, elem, attrs) {
var picker = new DatePicker();
scope.doSomethingUseful = function () {
// Your code.
}
}
}
}]);
UPDATE
Full example with additional attributes on the directive element:
http://codepen.io/ed_conolly/pen/qJDcr
I would like make angular directive where I use filter on data which are passed as argument.
So something like this:
<div class="my-module" data="a in array | orFilter:filter"></div>
Where "data" is attribute of directive "my-module".
I looked into ngRepeat source, but they parse ng-repeat argument and then they evaluate them. I can't use ng-repeat because I'm creating new instance of object (Marker for map) from data parameter.
Is it realy so hard? Is possible do this in custom directive and how?
Small example what i want: http://jsfiddle.net/PjRAr/1/
EDIT
I'm trying extend this map wrapper to render filtered markers.
My potencional solution is holding copy of all markers and visible markers. Add $watch to filter and when filter was changed call $scope.markers = $scope.$eval("allMarkers | orFilter:filter");.
With this solution we need hold two copy of all markers (~500).
You can $eval the filter expression.
in your directive link function:
elem.text( scope.$eval( attrs.data ).join(', ') );
in your template:
<div my-directive data="['Hello', 'xxx', 'World'] | filter:'o'"></div>
and the directive renders (by filtering-out 'xxx') to:
Hello, World
EDIT:
If the values are dynamic, you can of course do:
scope.$watch( attrs.data, function( arr ) {
elem.text( arr.join(', ') );
});
I do not think you can avoid having $watch, though.
I think you're mixing a few things. I'm not sure why you don't want to use ng-repeat, that's what it's made for. Because you isolate the scope you don't have access to the parent scope. The '=' binding tries to bind your isolated scope data attribute to the parent scope's model called what is in the attribute, but you can't bind to something that has been filtered. If you don't want to repeat the div with the attributes, put them on your own element, it just creates the content...
Here's a fiddle showing both using ng-repeat. You can see the binding is 2-way, it adds an updated: true property.
(FIDDLE)
link: function (scope, element, attrs, ctrl) {
element.append('<p>a: ' + scope.data.a + ', b: ' + scope.data.b);
scope.data.updated = true;
}
So, when I use one-way binding i got empty array in $watch (i think it's trying evaluate in local scope [removed "data" from directive scope for one way binding]. When i use two way binding [in directive scope is {data: "=data"}] i got error "Watchers fired in the last 5 iterations" (it's common error for filtering in angular).
So my solution:
Directive:
...
scope: {
data: "=data"
}
...
link: function (scope, element, attrs, ctrl) {
...
scope.$watch("data", function (newValue) {
angular.forEach(newValue, function (v, i) {
model.add(v);
}
}
}
...
Controller:
...
$scope.filter = { a:true, b:false, ... };
$scope.all = [..data..];
$scope.visible = [..data..];
$scope.$watch("filter", function(newValue) {
$scope.visible = $scope.$eval("all | orFilter:filter");
}, true);
...
HTML:
<div my-module data="visible"></div>
Thank you very much guys, you're help me so much. I'm learned many new things about angular binding.