I'm trying to write a directive that toggles classes based on a selector condition:
<label class-when="{'is-checked': ':has(input:checked)', 'is-disabled': ':has(input:disabled)'}">
<input type="checkbox">
Example checkbox
</label>
I need to somehow watch for DOM changes on the element and its descendents but I'm getting an ng:areq
error. How can I do this?
define(function (require) {
var _ = require('lodash');
return {
restrict: 'A',
scope: {
object: '@classWhen'
},
link: function (scope, element) {
scope.$watchCollection(function() {
return element.find('*').add(element);
}, function () {
_.forOwn(scope.object, function (test, classes) {
test = typeof test === 'boolean' ? test : element.is(test);
element.toggleClass(classes, test);
});
});
}
};
});
Component is used to break up the application into smaller components. But Directive is used to design re-usable components, which is more behavior-oriented. That is why components are widely used in later versions of Angular to make things easy and build a total component-based model.
AngularJS ng-class Directive The ng-class directive dynamically binds one or more CSS classes to an HTML element. The value of the ng-class directive can be a string, an object, or an array. If it is a string, it should contain one or more, space-separated class names.
AngularJS ng-controller Directive The ng-controller directive adds a controller to your application. In the controller you can write code, and make functions and variables, which will be parts of an object, available inside the current HTML element. In AngularJS this object is called a scope.
To create a custom directive we have to replace @Component decorator with @Directive decorator. So, let's get started with creating our first Custom Attribute directive. In this directive, we are going to highlight the selected DOM element by setting an element's background color. Create an app-highlight.
Okay, after reading your bounty comment, I understand that you want this to be controller independent. I spent some time completely reworking my solution, and I think I have finally figured out a way to accomplish what you want.
It really comes down to 2 things:
1) Detecting a change on the checkbox :checked
status, and
2) Detecting a change on the checkbox :disabled
status
Detecting 1)
was easy, as you can use a simple jQuery change handler, but detecting 2)
took a bit more research. It requires the use of scope.$watch
on the child ng-disabled
attribute.
Here is a demo of how this would work:
var app = angular.module("myApp", [])
.directive("classWhen", function() {
function setClasses(classWhen, $element, $input) {
Object.keys(classWhen).forEach(function(key) {
var test = classWhen[key];
// .toggleClass("className", true) === .addClass("className")
// .toggleClass("className", false) === .removeClass("className")
$element.toggleClass(key, $element.is(test));
});
}
return {
restrict: 'A',
link: function link (scope, element, attrs) {
var classWhen = JSON.parse(attrs.classWhen);
var $element = $(element);
$element.find("*").each(function (index, elem) {
var $elem = $(this);
// namespace the change event so we can easily .off() it
$elem.off("change.classWhen");
$elem.on("change.classWhen", function () {
setClasses(classWhen, $element, $elem);
});
// watch child ng-disabled attribute
scope.$watch($elem.attr("ng-disabled"), function (val) {
setClasses(classWhen, $element, $elem);
});
});
}
};
});
.is-checked {
background-color: yellow;
}
.is-disabled {
background-color: lightblue;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div ng-app="myApp">
<label>
<input type="checkbox" ng-model="disableAll" />Disable All</label>
<br>
<br>
<label class-when='{"is-checked": ":has(input:checked)", "is-disabled": ":has(input:disabled)"}'>
<input type="checkbox" ng-disabled="disableAll">Example checkbox 1
</label>
<br>
<label class-when='{"is-checked": ":has(input:checked)", "is-disabled": ":has(input:disabled)"}'>
<input type="checkbox" ng-disabled="disableAll">Example checkbox 2
</label>
<br>
<label class-when='{"is-checked": ":has(input:checked, .test)", "is-disabled": ":has(input:disabled)"}'>
<input type="text" ng-disabled="disableAll" ng-class="testingClass" ng-model="testingClass"/>
<br>
<input type="checkbox" ng-disabled="disableAll">
Example checkbox 3
</label>
<br>
<label class-when='{"is-checked": ":has(input:checked)", "is-disabled": ":has(input:disabled)"}'>
<input type="checkbox" ng-disabled="disableAll">Example checkbox 4
</label>
</div>
Here's my attempt to solve your problem in generic way: https://plnkr.co/edit/kVyJQClmQhF3FeBTmKaX?p=preview
Directive code:
app.directive('classWhen', () => (scope, element, attrs) => {
scope.$watchCollection(getClasses, setClasses)
function getClasses() {
// We have to evaluate expression in attrs.classWhen so that we get object
const rawClasses = scope.$eval(attrs.classWhen)
const classes = {}
// then we normalize it so that strings are treated as jquery selectors
Object.keys(rawClasses).forEach((className) => {
const expr = rawClasses[className]
classes[className] = typeof expr === 'string'
? element.is(expr)
: !!expr // we normalize falsy values to booleans
})
// then we return it to $watchCollection
return classes
}
/**
* will be called whenever any of the classes changes
*/
function setClasses(classes) {
Object.keys(classes).forEach((className) => {
element.toggleClass(className, classes[className])
})
}
})
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With