ng-show | ng-hide: watch element height - angularjs

I'm using this code to watch children height change:
$scope.$watch(function () {
return element.children().height();
},
function (newHeight) {
if (newHeight) {
element.height(newHeight);
}
});
It works pretty well with ng-if, but ng-show | ng-hide doesn't fire height change event.
Is there any way to watch the height when I use ng-show | ng-hide directives?
Example:
<div class="form-slider">
<div ng-if="step === 1" class="animate">
<div ng-show="error">Error message</div>
Some content
</div>
<div ng-if="step === 2" class="animate">
<div ng-show="error">Error message</div>
Some content
</div>
</div>
I'm using animation to switch between ng-ifs, so:
.form-slider has position: relative, while .animate: position: absolute. As you might know, html doesn't calculate height for .form-slider in this case and I have to use JS to change it's height.

The only way I found to fix this is to use removeClass | addClass events for ng-hide class:
myModule.animation('.ng-hide', function () {
var changeHeight = function (element) {
var container = element.closest('.my-selector');
container.parent().height(container.height());
};
return { removeClass: changeHeight, addClass: changeHeight }
});
Not sure if this is the best solution, but at least it works.
Update
I've found better solution. When you need to calculate height of your page using JS and you want to update height dynamically, when ng-show | ng-hide changes its state.
All what you need to do is just "extend" ngShow & ngHide:
(function () {
var heightDirective = ['$rootScope', '$timeout', function ($rootScope, $timeout) {
return {
restrict: 'A',
link: function ($scope, element, attrs) {
$scope.$watch(attrs.ngShow, function (newVal, oldVal) {
if (!angular.equals(newVal, oldVal)) {
$timeout(function () {
$rootScope.$broadcast('element:heightChange', element.height());
}, 100);
}
});
$timeout(function () {
$rootScope.$broadcast('element:heightChange', element.height());
}, 500);
}
}
}];
angular.module('mean.system').directive('ngShow', heightDirective);
angular.module('mean.system').directive('ngHide', heightDirective);
angular.module('mean.system').directive('ngIf', heightDirective);
})();
Then, add listener into your controller or directive:
$rootScope.$on('element:heightChange', function () {
element.height(element.children().height());
});

Related

Angular - Add Attribute Based on Window Width

I found some answers to this already - tried them however none of them work for me.
I have the following div where I use the attribute equalizer:
<div class="text-center" equalizer='group'>
However I only want that attribute equalizer to exist if the window width is > 400.
So I also use this code in my controller:
$scope.windowWidth = $window.innerWidth;
$window.onresize = function(event) {
$timeout(function() {
$scope.windowWidth = $window.innerWidth;
});
};
Now I understand I can do something like:
equalizer="{{ windowWidth>400 ? 'group' : ''}}"
However the problem is whether or not I have a value in equalizer it is still applied - namely <div class="text-center" equalizer=''> works in the same as <div class="text-center" equalizer='group'>
So how do completely control whether that attribute is inserted or not?
TO ADD
The only solution i have is duplicating the code and the using ng-if
so :
<div ng-if="windowWidth<400" class="text-centre">
and
<div ng-if="windowWidth>=400" class="text-center" equalizer='group'>
Thanks.
I would create a resize directive that's updating a scope variable that you can check inside of your equalizer directive.
I'm not exactly sure what your equalizer directive is doing but something like in the demo below (or in this fiddle) should work.
In the demo I'm removing the equalizer directive for testing if the resize event is removed with the directive.
Adding a directive would look like the untested code below but I would do it like in the demo with ng-if:
var childScope = $scope.$new();
var directiveElement = angular.element('<equalizer></equalizer>');
$document.append($compile(directiveElement)(childScope));
(Here is a demo found with Google for adding directives dynamically.)
angular.module('demoApp', [])
.controller('mainController', function($scope) {
$scope.equalizerOptions = {
group: true
};
$scope.removeEqualizer = function() {
// just for testing if resize handler is removed
var equalizer = angular.element(document).find('equalizer');
//console.log(angular.element(document).find('equalizer').scope());
equalizer.scope().$destroy();
equalizer.remove();
}
})
.directive('equalizer', equalizerDir)
.directive('resize', resizeDir);
function resizeDir($window) {
return {
link: function(scope, element, attrs) {
scope.window = {};
function onResize(event) {
//console.log('Resized', scope, element, event);
scope.window.width = $window.innerWidth;
}
$window.onresize = function(evt) {
//console.log('Resize');
scope.$apply(onResize);
};
onResize(); //initial call
scope.$on('$destroy', function() {
//console.log('destroy dir');
$window.onresize = undefined;
});
}
};
}
function equalizerDir($timeout) {
return {
scope: {
options: '=',
width: '#'
},
template: '<div ng-if="width >= 400"><h2>equalizer with ng-if & resize dir</h2>' +
'<p>{{options.group ? \'grouped\': \'not grouped\'}}</p></div>',
link: function(scope, element, attrs) {
/*scope.$watchCollection('options', function() {
// would work but watch with ternary operator directly in markup is easier
scope.text = scope.options.group ? 'grouped': 'not grouped';
});*/
}
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular.js"></script>
<div ng-app="demoApp" ng-controller="mainController">
window width debugging: {{window.width}}
<equalizer options="equalizerOptions" width="{{window.width}}" resize>
</equalizer>
<button ng-click="equalizerOptions.group = !equalizerOptions.group">
toggle group
</button>
<button ng-click="removeEqualizer()">
remove equalizer (test unbinding resize)
</button>
</div>

multiple function on ng-click angularjs

I'm new to AngularJs and learning now, in my current assignment I need to achieve multiple things on ng-click.
To hide and show some DOM elements based on the ng-click
Change the background of the element where the ng-click is applied on, I'm trying to acheive this using a directive.
Mark-up:
<div class="catFilter f6" ng-click="showSubCat = !showSubCat;toggleDropDown()">
Choose A Genre
</div>
<div class="inactive" ng-show="showSubCat" ng-click="hideSubCat = !hideSubCat" ng-hide="!hideSubCat">
</div>
<div class="cat-drop-menu-list" ng-show="showSubCat" ng-hide="!hideSubCat">
</div>
angular directive
retailApp.directive('toggleDropDown', function() {
return function(scope, element, attrs) {
$scope.clickingCallback = function() {
element.css({'background':'url("../images/down-arrow.png") no-repeat 225px 12px;'});
};
element.bind('click', $scope.clickingCallback);
}
});
Issues:
I'm not able to see the directive being applied, i.e., when I click on choose a genre, it is hiding and showing the other two divs, but not changing the back ground.
You can do this a couple ways, with bindings or directives:
http://jsfiddle.net/abjeex75/
var app = angular.module('app', []);
app.controller('AppCtrl', function ($scope) {
$scope.show_sub_cat = false;
$scope.show = function () {
$scope.show_sub_cat = true;
}
$scope.hide = function () {
$scope.show_sub_cat = false;
}
});
app.directive('toggleBg', function () {
var directive = {
restrict: 'A',
link: link
}
return directive;
function link(scope, element, attr) {
element.on('click', function () {
element.toggleClass('red');
});
}
});

AngularJS - scope watch is not triggered athough ng-class has been updated

I am trying to implement a dropdown mega-menu using Angular, where if the user clicks on Link 1, then the content for Link 1 should appear. My directives are as followed:
mobile-menu which acts as a controller and keeping track of the links and menu items states
menu-link, the actual link that user clicks on to open/close the menu items
menu-item, the menu item that should show/hide based on the scope.test value
Here is my AngularJS code:
angular.module("myApp", [])
.controller("testCtrl", function ($scope) {})
.directive("mobileMenu", function () {
return {
controller: function ($scope) {
this.menuLinks = [];
this.menuItems = [];
this.addMenuLink = function (l) {
this.menuLinks.push(l);
};
this.addMenuItem = function (m) {
this.menuItems.push(m);
};
// Function to close all other menu items if they are open.
// This is because only one menu item can be active at a time
this.closeOthers = function (selectedMenuLink) {
angular.forEach(this.menuLinks, function (l) {
if (l !== selectedMenuLink) {
l.selected = false;
}
});
angular.forEach(this.menuItems, function (m) {
if (selectedMenuLink.target == m.menuId) {
m.test = true;
} else {
m.test = false;
}
});
};
}
};
}).directive("menuLink", function () {
return {
require: "^mobileMenu",
scope: {},
link: function(scope, element, attrs, menuController) {
scope.selected = false;
menuController.addMenuLink(scope);
scope.$watch('selected', function(newValue, oldValue) {
if (oldValue === newValue) {return};
if (newValue) {
scope.target = angular.element(element[0].children[0]).attr("data-menu");
menuController.closeOthers(scope);
}
});
}
};
}).directive("menuItem", function () {
return {
require: "^mobileMenu",
scope: true,
link: function (scope, element, attrs, menuController) {
scope.test = false;
scope.menuId = attrs.id;
menuController.addMenuItem(scope);
scope.$watch('test', watchLink);
scope.$watch(attrs.collapse, watchLink);
scope.$watch(function () {
return scope.test;
}, watchLink);
var watchLink = function (newValue, oldValue) {
// Initializing for the first time, do nothing
if (newValue === oldValue) return;
// If the collapse attribute has a true value, collapse this element
if (newValue) {
collapse();
} else {
expand();
}
};
// Helper function to collapse the element
var collapse = function () {
element.css({
height: "0px"
});
};
// Helper function to show the element
var expand = function () {
element.css({
height: "200px"
});
};
}
};
});
And here is my HTML code:
<div ng-app="myApp" ng-controller="testCtrl">
<div mobile-menu>
<ul>
<li menu-link>
Link 1
</li>
<li menu-link>
Link 2
</li>
</ul>
<div id="menu0" ng-class="{'expanded' : test, 'collapsed' : !test}" menu-item collapse="!test">
<p class="text">First Menu</p>
</div>
<div id="menu1" ng-class="{'expanded' : test, 'collapsed' : !test}" menu-item collapse="!test">
<p class="promo-text">Second Menu</p>
</div>
</div>
I have an issue where if menu-link #1 is clicked, the corresponding menu-item #1's scope.test value should be updated and its' scope watch should be triggered, but it does not. If the scope watch triggered the watchLink function, then I would expect menu-item #1 would have a height of 200px.
I have also attached a jsfiddle example:
http://jsfiddle.net/EmwBP/28/
If you look at the browser console tool, the corresponding menu-item ng-class is always updated based on its scope.test value. However, even with my 3 different scope watchers set up, none of them were triggered. I am also using the ng-class directive just to show that the scope.test value does get updated and will be removed in the final implementation.
Normally, I would have put the menu-item directives as a child of menu-links, but I have a requirement where I have to put the menu-items as it is right now to achieve the slide-down effect of pushing elements below it down.
Many thanks in advance for your advices and assistance
You create a new isolated scope in menuLink by using scope: {}. So whatever scope variables you set inside that scope (like selected) are only available within that scope, and will not propagate to sibling or parent scopes.
What you need to do is de-isolate the menuLink scope by using scope: true.
That is not the problem. The problem is that watchLink is used before you define it. Fix where watchLink is triggered: http://jsfiddle.net/EmwBP/31/

Simple Angular Directive for Bootstrap Modal

Anyone have a simple directive to automatically show a Bootstrap modal? In Bootstrap 3 they took away the ability to automatically show the modal so I can't use a angular ng-if show block. Any help would be great.
Updated for angular 1.2 & Bootstrap 3.1.1: http://embed.plnkr.co/WJBp7A6M3RB1MLERDXSS/
I extended Ender2050's answer so the directive does not have an isolated scope. This means the modal contents can contain references to scope objects. Also reuse the directive attribute so only one attribute is needed.
app.directive("modalShow", function ($parse) {
return {
restrict: "A",
link: function (scope, element, attrs) {
//Hide or show the modal
scope.showModal = function (visible, elem) {
if (!elem)
elem = element;
if (visible)
$(elem).modal("show");
else
$(elem).modal("hide");
}
//Watch for changes to the modal-visible attribute
scope.$watch(attrs.modalShow, function (newValue, oldValue) {
scope.showModal(newValue, attrs.$$element);
});
//Update the visible value when the dialog is closed through UI actions (Ok, cancel, etc.)
$(element).bind("hide.bs.modal", function () {
$parse(attrs.modalShow).assign(scope, false);
if (!scope.$$phase && !scope.$root.$$phase)
scope.$apply();
});
}
};
});
Usage:
<div modal-show="showDialog" class="modal fade"> ...bootstrap modal... </div>
Here's an Angular directive that will hide and show a Bootstrap modal.
app.directive("modalShow", function () {
return {
restrict: "A",
scope: {
modalVisible: "="
},
link: function (scope, element, attrs) {
//Hide or show the modal
scope.showModal = function (visible) {
if (visible)
{
element.modal("show");
}
else
{
element.modal("hide");
}
}
//Check to see if the modal-visible attribute exists
if (!attrs.modalVisible)
{
//The attribute isn't defined, show the modal by default
scope.showModal(true);
}
else
{
//Watch for changes to the modal-visible attribute
scope.$watch("modalVisible", function (newValue, oldValue) {
scope.showModal(newValue);
});
//Update the visible value when the dialog is closed through UI actions (Ok, cancel, etc.)
element.bind("hide.bs.modal", function () {
scope.modalVisible = false;
if (!scope.$$phase && !scope.$root.$$phase)
scope.$apply();
});
}
}
};
});
Usage Example #1 - this assumes you want to show the modal - you could add ng-if as a condition
<div modal-show class="modal fade"> ...bootstrap modal... </div>
Usage Example #2 - this uses an Angular expression in the modal-visible attribute
<div modal-show modal-visible="showDialog" class="modal fade"> ...bootstrap modal... </div>
Another Example - to demo the controller interaction, you could add something like this to your controller and it will show the modal after 2 seconds and then hide it after 5 seconds.
$scope.showDialog = false;
$timeout(function () { $scope.showDialog = true; }, 2000)
$timeout(function () { $scope.showDialog = false; }, 5000)
I'm anxious to see what other solutions people come up with. Cheers!

Input autofocus attribute

I have places in my code where I have this:
<input data-ng-disabled="SOME_SCOPE_VARIABLE" />
I would like to be able to use it like this too:
<input data-ng-autofocus="SOME_SCOPE_VARIABLE" />
Or even better, mimicking how ng-style is done:
<input data-ng-attribute="{autofocus: SOME_SCOPE_VARIABLE}" />
Does this exist in the current version of AngularJS? I noticed in the code there's a BOOLEAN_ATTR which gets all the attr's that AngularJS supports. I don't want to modify that in fear of changing versions and forgetting to update.
Update: AngularJS now has an ngFocus directive that evaluates an expression on focus, but I mention it here for the sake of completeness.
The current version of AngularJS doesn't have a focus directive, but it's in the roadmap. Coincidentally, we were talking about this on the mailing list yesterday, and I came up with this:
angular.module('ng').directive('ngFocus', function($timeout) {
return {
link: function ( scope, element, attrs ) {
scope.$watch( attrs.ngFocus, function ( val ) {
if ( angular.isDefined( val ) && val ) {
$timeout( function () { element[0].focus(); } );
}
}, true);
element.bind('blur', function () {
if ( angular.isDefined( attrs.ngFocusLost ) ) {
scope.$apply( attrs.ngFocusLost );
}
});
}
};
});
Which works off a scope variable as you requested:
<input type="text" ng-focus="isFocused" ng-focus-lost="loseFocus()">
Here's a fiddle: http://jsfiddle.net/ANfJZ/39/
You can do this with the built-in ngAttr attribute bindings.
<input ng-attr-autofocus="{{SOME_SCOPE_VARIABLE}}">
The autofocus attribute will be added if SOME_SCOPE_VARIABLE is defined (even if it's false), and will be removed if it's undefined. So I force falsy values to be undefined.
$scope.SOME_SCOPE_VARIABLE = someVar || undefined;
This directive should do the trick:
angular.module('utils.autofocus', [])
.directive('autofocus', ['$timeout', function($timeout) {
return {
restrict: 'A',
scope: {'autofocus':'='}
link : function($scope, $element) {
$scope.$watch 'autofocus', function(focus){
if(focus){
$timeout(function() {
$element[0].focus();
});
}
}
}
}
}]);
Taken from here: https://gist.github.com/mlynch/dd407b93ed288d499778
scope.doFocus = function () {
$timeout(function () {
document.getElementById('you_input_id').focus();
});
};
Create a directive like this
.directive('autoFocus', ['$timeout', function ($timeout) {
return {
restrict: 'A',
link: function ($scope, $element) {
$timeout(function () {
$element[0].focus();
});
}
}
<input type="text" auto-focus class="form-control msd-elastic" placeholder="">
What I did is using regular autofocus on my inputs: <input autofocus>
And then I set the focus on the first visible input with autofocus when angular is ready:
angular.element(document).ready(function() {
$('input[autofocus]:visible:first').focus();
});
Hope this helps.
I did it with two custom directives, something like this:
(function(angular) {
'use strict';
/* #ngInject */
function myAutoFocus($timeout) {
return {
restrict: 'A',
link: function(scope, element) {
$timeout(function() {
element[0].focus();
}, 300);
}
};
}
function myFocusable() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var focusMethodName = attrs.myFocusable;
scope[focusMethodName] = function() {
element[0].focus();
};
}
};
}
angular
.module('myFocusUtils', [])
.directive('myAutoFocus', myAutoFocus)
.directive('myFocusable', myFocusable);
}(angular));
If you add attribute my-auto-focus to an element, it will receive focus after 300ms. I set the value to 300 instead of 0 to let other async components to load before setting the focus.
The attribute my-focusable will create a function in the current scope. This function will set focus to the element when called. As it creates something in the scope, be cautious to avoid overriding something.
This way you don't need to add something to Angular's digest cycle (watch) and can do it entirely in the view:
<input my-focusable="focusOnInput"></input>
<button ng-click="focusOnInput()">Click to focus</button>
I created a JSFiddle to show the myFocusable directive: http://jsfiddle.net/8shLj3jc/
For some reason I don't know, the myAutoFocus directive does not work in JSFiddle, but it works in my page.
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<body>
<div ng-app="myApp" ng-controller="namesCtrl">
<div ng-repeat="x in names">
<input ng-attr-focus={{$first}} value="{{x.name + ', ' + x.country }}" />
</div>
</div>
<script>
var myApp = angular.module('myApp', []);
myApp.controller('namesCtrl', function($scope) {
$scope.names = [
{name:'x1',country:'y1'},
{name:'x2',country:'y2'},
{name:'x3',country:'y3'}
];
});
myApp.directive("focus", function(){
return {
restrict: "A",
link: function link(scope, element, attrs) {
if(JSON.parse(attrs.focus)){
element[0].focus();
}
}
};
});
</script>
</body>
</html>
had created above custom directive for one of my use case.
always focusses on first input element.
works for ajax data, browser back/forward buttons.
Tested on chrome and firefox(default autofocus is not supported here)
JSON.parse is used to parse string "true" returned from html to boolean true in JS.
another way to use attrs.focus === "true" for if condition.
so without $timeout you can also use auto focus like this -
<input type="text" ng-show="{{condition}}" class='input-class'></input>
angular.element(document).ready(function(){
angular.element('.input-class')[0].focus();
});
Combining whar others mentioned above:
JS Code:
myApp.directive('ngAutofocus', ['$timeout', function ($timeout) {
var linker = function ($scope, element, attrs) {
$scope.$watch('pageLoaded', function (pageLoaded) {
if (pageLoaded) {
$timeout(function () {
element[0].focus();
});
}
});
};
return {
restrict: 'A',
link: linker
};
}]);
HTML:
<input type="text" ng-model="myField" class="input-block-level edit-item" ng-autofocus>
Set pageLoaded to true from your initial load method of the page get:
var loadData = function () {
..
return $http.get(url).then(function (requestResponse) {
$scope.pageLoaded = true;
......
}

Resources