Angular - Add Attribute Based on Window Width - angularjs

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>

Related

element.on('click') on a directive within ng-if doesn't work

I use a directive on links to provide a modal login, if the user isn't logged in. And it works fine for all usecases until now. In a new case the directive is part of a ng-if section.
On the snippet the first link works fine, the second doesn't work. The element.on('click', function(evt) {…}) will never called. And the permissions are not checked and no modal login will prompt to the user.
Ok, if I use ng-show instead of ng-if, both links works. Because ng-show doesn't remove the element from the DOM and it doesn't create a child scope like ng-if. But in my usecase I must use ng-if. What can I do? How can I provide a click event, which works within a ng-if and other ng-x directives too?
var app = angular.module('myapp', []);
app.controller('MyCtrl', function($timeout) {
// something stupid here
var vm = this;
vm.bar = false;
$timeout(function() {
vm.bar = true;
});
});
/**
* I need an isolate scope, because some user state checks
* will take place in the directive. And there are different
* login forms based on type and other attributes.
*/
app.directive('modalLogin', function() {
return {
restrict: 'A',
scope: {
type: '#mlType'
},
compile: function(element, attrs) {
if (element[0].nodeName !== ('A')) {
// use directive only at links
return function() {};
}
return function(scope) {
function checkPermissions() {
// do the magic here
alert('foo');
}
element.on('click', function(evt) {
evt.preventDefault();
checkPermissions();
})
}
}
}
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<section ng-app="myapp" ng-controller="MyCtrl as FOO">
<div>
<!-- it works -->
Klick me, I'm working well
</div>
<!-- it doesn't work -->
<div ng-if="FOO.bar">
Klick me, I'm nested within a ng-if
</div>
</section>
Use link instead of compile.
link: function(scope, element) {
function checkPermissions() {
alert('foo');
}
element.on('click', function(evt) {
evt.preventDefault();
checkPermissions();
});
}

ng-show | ng-hide: watch element height

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());
});

Dynamically disable all ng-clicks within an element

I have a directive disable-ng-clicks and under certain conditions, I want to prevent all ng-clicks that are children of the directive. Here is some example markup:
<div disable-ng-clicks> <!-- directive -->
<a ng-click="someAction()"></a>
<div ng-controller="myController">
<a ng-click="anotherAction()"></a>
<a ng-click="moreActions()"></a>
</div>
</div>
If these were normal hyperlinks, I could do something like this in the link function:
function(scope, iElement, iAttrs) {
var ngClicks = angular.element(iElement[0].querySelectorAll('[ng-click]'));
ngClicks.on('click', function(event) {
if(trigger) { // a dynamic variable that triggers disabling the clicks
event.preventDefault();
}
});
}
But this does not work for ng-click directives. Is there another way to accomplish this?
Here is the best I could come up with. I created a new directive to replace ng-click:
directive('myClick', ['$parse', function($parse) {
return {
restrict: 'A',
compile: function($element, attrs) {
var fn = $parse(attrs.myClick);
return function (scope, element, attrs) {
var disabled = false;
scope.$on('disableClickEvents', function () {
disabled = true;
});
scope.$on('enableClickEvents', function () {
disabled = false;
});
element.on('click', function (event) {
if (!disabled) {
scope.$apply(function () {
fn(scope, { $event: event });
});
}
});
};
}
}
}]);
So in a different directive, I can have:
if (condition) {
scope.$broadcast('disableClickEvents');
}
and when I want to re-enable:
if (otherCondition) {
scope.$broadcast('enableClickEvents');
}
I don't like having to use a different directive for ng-click, but this is the best plan I could think of.
You are catching 'click' event on parent only because of JS events bubbling, so if you want to intercept it on all descendants, so your directive should get all descendants of current element, listen their 'click' event and prevent it if necessary.
This directive will iterate over all child elements, check to see if they have an ng-click attribute, and if they do, it will disable any registered click event handlers:
directive('disableNgClicks', function(){
return {
restrict: 'E',
link: function(scope, elem, attrs){
angular.forEach(elem.children(), function(childElem) {
if (childElem.outerHTML.indexOf("ng-click") > -1) {
angular.element(childElem).off('click');
}
});
}
}
})
Plunker demo
I know this is 2 years ago but I needed to do something similar and came up with a rather simple solution.
The object:
items: {
item1 : {
selected: 0,
other: 'stuff'
},
item2 : {
selected : 1,
other: 'stuff'
}
}
The HTML:
<div ng-repeat="item in items" ng-model="item.selected" ng-click="selectParent($event)">
<div ng-click="item.selected ? selectChild($event) : null">Child</div>
</div>
The functions:
$scope.selectParent = function($event) {
var itemScope = angular.element($event.currentTarget)scope().item;
itemScope.selected = !itemScope.selected;
}
$scope.selectChild = function($event) {
$event.stopPropagation;
console.log('I only get triggered if parent item is selected');
}
This is a pretty raw example of what I did. You should probably be using a directive that gives you $scope rather than angular.element($event.currentTarget).scope... either way the simplistic inline if logic is what I was really getting at. You can call a function or not based on some value.

angular dynamically adding contenteditable directive doesn't work?

Runnable CODE: my code
I try to dynamically add contenteditable directive to the <div> element when it is double-clicked.
when I put contenteditable directive to <div> in the beginning, the ng-model still work, but when I remove it and add it dynamically in ng-dblclick callback, ng-model seams not work anymore.
It's kind of like this Question.
but I can't think of a angular-friendly way to finish my work here.
How can I fix this?
code: html
<div ng-app="customControl">
<form name="myForm" ng-controller="mainControl">
<!-- Dynamically adding contenteditable directive : doesn't work -->
<div name="myWidget" ng-model="userContent" ng-click="enableEdit($event)"
strip-br="true"
required>Change me!</div>
<hr>
<textarea ng-model="userContent"></textarea>
</form>
</div>
code: js
angular.module('customControl', []).
controller('mainControl', function($scope) {
$scope.enableEdit = function(e) {
$(e.target).attr('contenteditable', '');
}
})
.directive('contenteditable', function() {
return {
restrict: 'A', // only activate on element attribute
require: '?ngModel', // get a hold of NgModelController
link: function(scope, element, attrs, ngModel) {
if(!ngModel) return; // do nothing if no ng-model
// Specify how UI should be updated
ngModel.$render = function() {
element.html(ngModel.$viewValue || '');
};
// Listen for change events to enable binding
element.on('keyup change', function() {
scope.$apply(read);
});
read(); // initialize
// Write data to the model
function read() {
var html = element.html();
// When we clear the content editable the browser
// leaves a <br> behind
// If strip-br attribute is provided then we strip this out
if( attrs.stripBr && html == '<br>' ) {
html = '';
}
ngModel.$setViewValue(html);
}
}
};
});

Whats wrong with my AngularJS directive

To be able to watch the contents change for a contenteditable div or an input element, I have created the following directive:
app.directive('contenteditable',function() { return {
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
// view -> model
element.bind('input', function() {
scope.$apply(function() {
ctrl.$setViewValue(element["0"].tagName=="INPUT" ? element.val() : element.text());
scope.watchCallback(element.attr('data-ng-model'));
});
});
// model -> view
ctrl.$render = function() {
element.text(ctrl.$viewValue);
element.val(ctrl.$viewValue);
};
}};
});
My Test Controller looks like:
function TestController($scope) {
$scope.singleVal = "X";
$scope.multiVal = ["A"];
$scope.addRow = function() {
$scope.multiVal.push("");
};
$scope.watchCallback = function(modelName) {
console.log(modelName+" was changed");
};
}
When I test it against the following html, the singleVal (statically created) behaves well, but my multiVal (dynamically created using ng-repeat) doesnt. When I input a value, it just retains the original value (i.e the model is not getting refreshed). Please help.
<div data-ng-controller="TestController">
<div contenteditable="true" data-ng-model="singleVal"></div>
<button data-ng-click="addRow()">Add Row</button>
<table data-ng-repeat="val in multiVal"><tr><td>
<div contenteditable="true" data-ng-model="val"></div>
</td></tr></table>
</div>
You can't bind ngModel directly to a string in an array. You'll need to store an array of objects inside of multiVal:
$scope.multiVal = [{property: "A"}];
Demonstrated here: http://jsfiddle.net/YMJzN/
Btw, you'll also want to adjust $scope.addRow to do the same...
$scope.addRow = function() {
$scope.multiVal.push({property:'new'});
}

Resources