I'm trying to write a directive that watches an element's width, and conditionally sets an ng-class variable. Not quite sure if I'm going about this the right way and would appreciate some help! My html
<div ng-class="{'compact' : compact}" calc-width>stuff here</div>
My directive (so far)
app.directive('calcWidth', function() {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
var width = elem[0].clientWidth;
scope.compact = false;
if (width < 600) {
scope.compact = true;
}
},
};
});
Firstly, this isn't modifying the ng-class variable as expected. Secondly, how do I go about 'watching' for changes in the element's width? Normally I could just use the window resize function, but the element's size also changes in other cases such as side panels opening. Should I even be doing this? Or would it be best to somehow trigger this directive from another directive/controller/service which controls the panels?
You need to wrap the contents of your link function inside a $watch function.
link: function(scope, elem, attrs) {
scope.$watch(function () { // this function will be executed for each digest cycle.
var width = elem[0].clientWidth;
scope.compact = false;
if (width < 600) {
scope.compact = true;
}
})
}
Related
I'm trying to manipulate an element class within a directive. The directive is of a toolbar and it's supposed to add a class to 2 elements after some scroll.
The element directive itseld;
The view, to add/remove margin;
This is my html structure:
<ag-toolbar class="ag-toolbar--sec"></ag-toolbar>
<div ui-view="app" autoscroll="false" id="appView"></div>
And this is my directive:
function agToolbar($window) {
return {
restrict: 'E',
link: function(scope, element, attrs) {
var elView;
setTimeout(function(){
elView = document.getElementById("appView");
}, 400);
angular.element($window).bind("scroll", function() {
if (this.pageYOffset >= 128) {
element.addClass('scroll');
elView.addClass('agMargin');
} else {
element.removeClass('scroll');
elView.removeClass('agMargin');
};
});
}
};
}
In the console I keep getting the error:
elView.addClass is not a function
elView.removeClass is not a function
But the element.addClass is working fine. Any ideas why?
addClass belongs to jqLite (or jQuery if available), see https://docs.angularjs.org/api/ng/function/angular.element.
That is, you need to wrap the DOM element in a jqLite/jQuery element:
elView = angular.element(document.getElementById("appView"));
How do you get the value of the binding based on an angular js directive restrict: 'A'?
<span directiverestrict> {{binding}} </span>
I tried using elem[0].innerText but it returns the exact binding '{{binding}}' not the value of the binding
.directive('directiverestrict',function() {
return {
restrict:'A',
link: function(scope, elem, attr) {
// I want to get the value of the binding enclosed in the elements directive without ngModels
console.log(elem[0].textContent) //----> returns '{{binding}}'
}
};
});
You can use the $interpolate service, eg
.directive('logContent', function($log, $interpolate) {
return {
restrict: 'A',
link: function postLink(scope, element) {
$log.debug($interpolate(element.text())(scope));
}
};
});
Plunker
<span directiverestrict bind-value="binding"> {{binding}} </span>
SCRIPT
directive("directiverestrict", function () {
return {
restrict : "A",
scope : {
value : '=bindValue'
},
link : function (scope,ele,attr) {
alert(scope.value);
}
}
});
During the link phase the inner bindings are not evaluated, the easiest hack here would be to use $timeout service to delay evaluation of inner content to next digest cycle, such as
$timeout(function() {
console.log(elem[0].textContent);
},0);
Try ng-transclude. Be sure to set transclude: true on the directive as well. I was under the impression this was only needed to render the text on the page. I was wrong. This was needed for me to be able to get the value into my link function as well.
my html is taking input in two form, input and contenteditable div . I want to write one directive that handles both, but I cannot find a way to figure out which tag has called the function (because Angular's JQLite doesnt provide a is() or get() function). The following code will be complete if I can figure out to evaluate IS_INPUT_TAG:
function funct() { return {
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
// view -> model
element.bind('input', function() {
scope.$apply(function() {
if(IS_INPUT_TAG)
ctrl.$setViewValue(element.val());
else
ctrl.$setViewValue(element.text());
scope.watchCallback(element.attr('data-ng-model'));
});
});
// model -> view
ctrl.$render = function() {
if(IS_INPUT_TAG)
element.val(ctrl.$viewValue);
else
element.text(ctrl.$viewValue);
};
}};
}
app.directive('input', funct);
app.directive('contenteditable', funct);
In your directive, you can make use of the element parameter of the linking function to identify the tag on which the directive is applied. You can then use that in your IF condition as follows:
ctrl.$render = function() {
var tagname = element["0"].tagName;
if(tagName === "INPUT")
element.val(ctrl.$viewValue);
else
element.text(ctrl.$viewValue);
};
After, this you can simply attach the directive to the input and the div tags as an attribute to the tags to identify the tag to which the directive is applied.
I'm tring to create a directive that will center a div.
So far, I have this code:
app.directive("setcenter", function () {
return {
scope:{
setcenter: '='
},
link: function (scope, element, attrs) {
scope.$watch('setcenter', function (newValue) {
if (newValue == true) {
var width = element.width();
element.css('position', 'absolute');
element.css('top', '80px');
element.css('left', '50%');
element.css('z-index', '200');
element.css('margin-left', '-' + width / 2 + 'px');
}
});
}
}
});
The problem is the width of the element. The whole point for this directive is that the div that uses this directive, don't have a width set. I want this directive to figure out the width and center the div.
The problem I encounter is that when the directive is invoked, the actual width of the div is not yet known. When I use this in my situation, the div is 800px, but when the page is finished loading, the div is 221px.
So, what can I do to wait till the actual width is known of the div?
First, I only have used this logic when I defined a controller for a directive rather than a link function. So defining it in a link function instead may cause different behavior, but I suggest you try it there first and if you can't get it to work then switch to using a controller.
As far as I can tell, the only change you would need to make this work would be to change $scope to scope calls and $element to element since the dependency injected objects become standard link function parameters.
$scope.getElementDimensions = function () {
return { 'h': $element.height(), 'w': $element.width() };
};
$scope.$watch($scope.getElementDimensions, function (newValue, oldValue) {
//<<perform your logic here using newValue.w and set your variables on the scope>>
}, true);
$element.bind('resize', function () {
$scope.$apply();
});
The idea for this usage came to me after reading a very similar type of usage about watching the $window rather than the current element, the original work can be found here.
Angular.js: set element height on page load
James' answer led me to:
app.directive('measureInto', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.$watch(function() {
return element[0].clientWidth;
}, function(value){
scope[attrs.measureInto] = element[0].clientWidth + 10;
});
}
};
});
So, at runtime, I add this and assign into whatever scope variable I want the width of the element I'm looking for
I had a similar issue and found that the dimensions were reliably correct when all the ng-ifs (or anything else using ngAnimate) on the page had been resolved - it's possible something similar is happening here. If so, this would do the trick without adding any new listeners:
$scope.tryGetElementDimensions = function () {
if (!angular.element("your-element") || ((angular.element("your-element")[0].classList).contains("ng-animate")
$timeout(function() {
$scope.tryGetElementDimensions()
})
}
else {
$scope.getElementDimensions()
}
$scope.getElementDimensions = function (){
//whatever you actually wanted to do
}
link: function (scope, element, attrs) {
$scope.tryGetElementDimensions()
}
Angular adds ng-animate and ng-enter, ng-leave classes while it's animating and you can be confident it's finished when these classes have all been removed. $timeout without a second argument just waits for the next digest.
Can't comment yet, therefore this answer.
Found a similar solution like the one strom2357 is suggesting. $timeout works really well to let you know when the dom is ready, and it is super simple. I am using this solution to get the ui-view element width. Found it in a fiddle.
var app = angular.module('app', []);
app.controller('MyController', function($timeout, $scope){
$timeout(function(){
//This is where you would get width or height of an element
alert('DOM ready');
});
alert('DOM not ready');
});
For an only visual editor I'm trying to create a new directive that writes a CSS style. I'm stuck at trying to get the directive to update when a checkbox is clicked to make the background-color property transparent.
Here's my (non-working) directive:
myApp.directive('customstyle', function () {
return {
restrict: 'E',
link: function (scope, element, attrs) {
var bgColor;
scope.$watch(attrs.myTransparent, function (value) {
if (value) {
bgColor = 'transparent';
} else {
bgColor = attrs.myBgcolor;
}
updateStyle();
}, true);
function updateStyle() {
var htmlText = '<style>.' + attrs.myClass + '{';
htmlText += 'background-color: ' + bgColor + ';';
htmlText += "}</style>";
element.replaceWith(htmlText);
}
updateStyle();
}
}
});
and html element:
<customstyle my-class="examplediv" my-transparent="settings.Window.Transparent" my-bgcolor="settings.Window.BackgroundColor"></customstyle>
Here's a jsfiddle of the situation: http://jsfiddle.net/psinke/jYQc6/
Any help would be greatly appreciated.
Try using the directive directly on the element you want to change, it's easier to do and to maintain.
HTML:
<div class="examplediv customstyle"
my-transparent="settings.Window.Transparent"
my-bgcolor="{{settings.Window.BackgroundColor}}">
</div>
Note: Use {{settings.Window.BackgroundColor}} to pass the property's value and not a String.
Directive:
myApp.directive('customstyle', function () {
return {
restrict: 'AC',
link: function (scope, element, attrs) {
scope.$watch(attrs.myTransparent, function (value) {
element.css('background-color', (value ? 'transparent' : attrs.myBgcolor));
});
}
}
});
Note: Use element.css() to change CSS properties directly on the element.
jsFiddler: http://jsfiddle.net/jYQc6/8/
I was having the same problem and using bmleite's solution solved it. I had a custom element with a custom attribute very similar to the one above, and changing the directive to be applied on a regular DIV instead of the custom attribute fixed it for me.
In my solution I also have the following line of code right after the element has been modified:
$compile(element.contents())(scope);
Remember to inject the $compile service in the directive function declaration:
myApp.directive('directiveName', function ($compile) { ...
Thanks for a great post!