I hav a directive that looks roughly like this, with an ng-class in the template:
module.directive('myDirective', [function() {
return {
restrict: 'E',
template: `<div ng-class="{'foo-expanded': expanded, 'foo': !expanded}"><div>`
//...
}
}]);
My problem is, my classes from the ng-class are applied to the div, which ends up being nested inside of the directive element after the directive is compiled: <my-directive><div>...</div></my-directive>.
Is there any way to apply the classes to the root <my-directive> element instead? I know I can dynamically add the class using javascript in the link function or controller instead of the ng-class, but I am looking for a way to avoid this.
You can do that using the link function which gives you access to the created element ( directive )
module.directive('myDirective', [function() {
return {
restrict: 'E',
template: `<div ng-class="{'foo-expanded': expanded, 'foo': !expanded}"><div>`
link: function(scope, element, attrs) {
element[0].classList.add('test')
}
}
}]);
This answer shows two different ways
Manipulate the classes from the controller
Use replace: true (deprecated)
Manipulate the classes from the controller
If you don't want to use replace: true, you can manipulate the directive's root classes from the controller by injecting $element.
app.directive('myDirective', function() {
return {
restrict: 'E',
template: `
<div ng-class="{'foo-expanded': expanded, 'foo': !expanded}">
MY DIRECTIVE
<div>
`,
controller: function($element) {
$element.addClass("foo test test2");
$element.toggleClass("foo-expanded");
$element.removeClass("test2");
}
}
});
The DEMO on PLNKR
For more information, see
AngularJS element API Reference
Use replace: true (deprecated)
Another approach is to use replace: true:
module.directive('myDirective', [function() {
return {
restrict: 'E',
replace: true,
template: `<div ng-class="{'foo-expanded': expanded, 'foo': !expanded}"><div>`
//...
}
}]);
Keep in mind that the replace property is deprecated in AngularJS and has been removed in the new Angular (v2+).
For more information, see
AngularJS Comprehensive Directive API Reference - replace
How to use the 'replace' feature for custom AngularJS directives?
Why is `replace` property deprecated in AngularJS directives?
AngularJS $compile Service API Reference - Issues with replace:true.
Related
I have a directive i'm using to do the same search filtering across multiple pages. So the directive will be using a service and get pretty hefty with code. Because of that I want to link to a controller instead of have the controller inside the directive like this:
.directive('searchDirective', function($rootScope) {
return {
restrict: 'E',
templateUrl:'searchtemplate.html',
controller: 'searchCtrl',
controllerAs: 'search'
};
});
I also want access to parent scope data inside the template, so I don't want to use a isolated scope.
Anyway here's what i'm not sure how to do. My directive looks like this:
<search-directive filter="foo"/>
How do I pass in the value in the filter attribute so that I can access it in my controller using $scope.filter or this.filter?
If I were using an isolated scope it'd be simple. If i had the controller in the same page I could use $attrs. But since i'm using a controller from another spot and don't want an isolated scope i'm not sure how to get the attrs values into the controller.
Any suggestions?
What about using the link function and passing the value to the scope?
return {
restrict: 'E',
templateUrl:'searchtemplate.html',
controller: 'searchCtrl',
controllerAs: 'search',
link: function (scope, element, attr) {
scope.filter = attr.filter;
}
};
searchDirective.js
angular
.module('searchDirective', []).controller('SearchCtrl', SearchCtrl)
.directive('SearchDirective', directive);
function directive () {
var directive = {
templateUrl:'searchtemplate.html',
restrict: "E",
replace: true,
bindToController: true,
controller: 'searchCtrl as search',
link: link,
scope: { filter:'=' } // <-- like so here
};
return directive;
function link(scope, element, attrs) {}
}
SearchCtrl.$inject = [
'$scope',
'$filter'];
function SearchCtrl(
$scope,
$filter) {
/** Init SearchCtrl scope */
/** ----------------------------------------------------------------- */
var vs = $scope;
// ....
Also I highly recommend checking out this AngularJS style guide, how you are writing your directive above is how I use to do it too. John Papa shows some way better ways: https://github.com/johnpapa/angular-styleguide
Directives:
https://github.com/johnpapa/angular-styleguide#directives
Controllers:
https://github.com/johnpapa/angular-styleguide#controllers
Flip the values of bindToController and scope around.
{
....
scope: true,
bindToController: { filter:'=' }
...
}
I have just hit the same issue over the weekend, and made a simple complete example here: bindToController Not Working? Here’s the right way to use it! (Angular 1.4+)
I would like my custom directive to be able to pre-process its own classes based on some values bound to its parent scope. However, I can't seem to get the directive to set its own ng-class and execute a function on the local scope.
directive('testDirective',function(){
restrict: 'E',
scope: {
inputValue: '='
},
template: '<div>dynamically styled content</div>',
bindToController: true,
controllerAs: 'ctrl',
compile: function(element){
element.attr('ng-class', 'ctrl.getClass()');
},
controller: function(){
this.getClass = function(){
//return array of classes based on value of this.inputValue
}
}
});
Unfortunately no styles get applied as the expression is never evaluated and is just being assigned as a string. I see the following when I inspect the DOM element
ng-class="ctrl.getClass()"
I realize that I could just use jQuery to add my classes in the link function, but then they would not be bound to my data. I was also hoping to avoid using $watch if at all possible.
Any ideas are welcome!
Lets imagine you have a css file like this:
.myButton1 {
color: red;
}
.myButton2 {
color: blue;
}
The in your directive you could do this with ng-class:
directive('testDirective',function(){
restrict: 'E',
scope: {
inputValue: '='
},
template: '<div ng-class="ctrl.getClass()">dynamically styled content</div>',
bindToController: true,
controllerAs: 'ctrl',
controller: function(){
this.getClass(){
return (ctrl.inputValue === 'something' ? "myButton1" : "myButton2");
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
Your getClass method must return valid pre-defined, css classes, one or an array of them.
preamble: It seems like this question has been asked and answered before, but I cannot seem to get it working, so if my question boils down to "can you debug my code?", I apologize.
I would like to write the following code:
<radio-set ng-model="obj.prop" name="obj_prop">
<radio-set-button ng-value="'public'">Public</radio-set-button>
<radio-set-button ng-value="'protected'">Protected</radio-set-button>
<radio-set-button ng-value="'private'">Private</radio-set-button>
</radio-set>
This renders a bunch of radio buttons and labels which need to populate whatever is passed to the <radio-set>'s ngModel. I'm missing something scope-related.
.directive("radioSet", function () {
return {
restrict: 'E',
replace: true,
scope: {
ngModel: '=?',
ngChange: '&',
name: '#'
},
transclude: true,
template: '<div class="radio-set" ng-transclude></div>',
controller: function () {}
};
})
.directive("radioSetButton", function () {
return {
restrict: 'E',
replace: true,
require: ['^radioSet', '?ngModel'],
scope: {
ngModel: '=?', // provided by ^radioSet?
ngValue: '=?',
ngChange: '&', // provided by ^radioSet?
name: '#' // provided by ^radioSet?
},
transclude: true,
link: function (scope, element, attr) {
element.children().eq(0).attr("name", scope.name); // scope.name is null
},
template: '<label class="radio-set-button">' +
'<input type="radio" name="name" ng-model="ngModel" ng-value="ngValue" ng-change="ngChange()">' +
'<div class="radio-content" ng-transclude></div>' +
'</label>'
};
})
Both the parent and child directives need their own scope definition, but it is unclear to me how to access to the radioSet's scope from within radioSetButton.
thanks for the help.
fiddle: http://jsfiddle.net/pmn4/XH5K2/2/
Transclusion
I guess i have to tell you that the transclusion you used in your directive does not work as you expect because in short: The transcluded directive doesn't inherit the scope you'd expect, actually it inherits the scope of the outer controller, but there are plenty of answers on this topic:
Access Parent Scope in Transcluded Directive
How to solve
To access a parents directive there are basically two ways:
1.) Require the parents directive's controller and create some API to do stuff on the parent
2.) Use the fifth parameter of the link function to access the transclude function, here you could change the injected scope and you could set it to the parents directive scope
Since the first solution is more intuitive i will go with this one:
On the radioSetdirective i set up a bidirectional databinding to the object in my Controller and i create a getter and setter method to interact with the value.
In the "child"'s directive i require the parent directive's controller which i get passed as the fourth parameter in my link function. I setup a click handler on the element to get the click and here i call the parents setter method with my value. To visualize the current selected object i add an ng-class directive which conditionally adds the active class.
Note: This way you can use the ngModel directive as well. It has an API to interact with the model.
The second solution uses the transclude function which you can use to pass in a scope. As i dont have time right now and as it adds more complexity i'd recommend using the first solution.
Recommendation
For your example transclusion might not be the right choice, use one directive and add the choices to the template or pass them into the directive. As i dont know what your intentions are i provided this solution. (I didn't know what the purpose of this name property is?)
The Code
Fiddle: http://jsfiddle.net/q3nUk/
Boilerplate:
var app = angular.module('myApp', []);
app.controller('MainController', function($scope) {
$scope.object = {
'property' : 'public'
};
});
The Directives:
app.directive('radioSet', function() {
return {
scope : {
radioValue : '='
},
restrict : 'E',
transclude : true,
replace : true,
template : '<div class="radioSet" ng-transclude></div>',
controller : function($scope) {
this.getRadioValue = function() {
return $scope.radioValue;
}
this.setRadioValue = function(val) {
$scope.$apply(function() {
$scope.radioValue = val
});
}
}
};
});
app.directive('radioSetButton', function() {
return {
restrict : 'E',
transclude : true,
replace : true,
scope : true,
template : '<div class="radioSetButton" ng-class="{active:isActive()}" ng-transclude></div>',
require : '^radioSet',
link : function(scope, elem, attrs, radioSetController, transclude) {
scope.isActive = function() {
return attrs.buttonValue === radioSetController.getRadioValue();
};
elem.on('click', function() {
radioSetController.setRadioValue(attrs.buttonValue);
});
}
};
});
The HTML:
<html>
<body ng-app="myApp">
<div ng-controller="MainController">
<p>{{ object.property }}</p>
<radio-set radio-value="object.property">
<radio-set-button button-value="public">Public</radio-set-button>
<radio-set-button button-value="private">Private</radio-set-button>
<radio-set-button button-value="protected">Protected</radio-set-button>
</radio-set>
</div>
</body>
</html>
CSS:
.radioSetButton {
display : block;
padding : 10px;
border : 1px solid black;
float : left;
}
.radioSetButton:hover {
cursor : pointer;
}
.radioSetButton.active {
background-color : grey;
}
I'm creating a reusable component/widget as a directive using a template and isolated scope. I'd like to be able to also send a callback into the directive and call it in the widget. Is this possible?
Something like...
mainView template:
<my-widget callback="someFunction"></my-widget>
directive:
return {
restrict: 'E',
scope: {
callback: '='
},
templateUrl: '/partials/widget.html',
}
And the template:
<input type="text" ng-change="callback()" />
So when the widget value is changed, it triggers the callback function that was passed in the main view
What you're looking for is &. Quoting the old angular docs: "& or &attr - provides a way to execute an expression in the context of the parent scope".
Try this as your directive code:
return {
restrict: 'E',
scope: {
callback: '&'
},
templateUrl: '/partials/widget.html',
}
You can also $watch for it in the link or controller.
.directive('myDirective', function() {
return {
restrict: 'E',
link: function(scope, element, attrs) { {
scope.$watch(attrs.myDirective, function(value){ ... });
}
...
Turns out I needed to do as suggested and use an & rather than an =, but also this still wouldn't work until I added ng-model as well to my input:
<input type="text" ng-model="myValue" ng-change="callback()" />
I am trying to do the following:
I have a recurring UI pattern called <overlay></overlay>
This overlay is dynamically created by a directive dir1 from a templateUrl.
Let's assume now I have a second UI element called <gallery></gallery>which is also dynamically created by a directive dir2.
Is it possible to pass the gallery template into the overlay creating something like:
<overlay>
<gallery></gallery>
</overlay>
Note that the Element overlay and gallery will be replaced by the template from its respective directive.
Here is a small Plunk with the updated problem ==> http://plnkr.co/edit/Bu2cwXYVQXKmjDVXaHuK?p=preview
Yes, you can use ng-transclude. Something like:
angular.module("myModule", []).
directive("overlay", function () {
return {
restrict: "E",
transclude: true,
// or templateUrl: ...
template: '<ul class="u" ng-transclude></ul>',
replace: true
};
}).
directive("gallery", function () {
return {
require: "^overlay",
restrict: "E",
scope:{cls:'#'},
transclude: true,
// or templateUrl: ...
template: '<li class="l"><button class="{{cls}}" ng-transclude></button></li>',
replace: true
};
});
Anyways, here is good example for your needs:
Demo Fiddle
You are writing transclude: true but you are not using transclusion anywhere
So instead of your code you should write as below
template: "<div class='my-generic-overlay' ng-transclude>This should be replaced by the myGallery template</div>"