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>"
Related
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.
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.
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 have a rather complex modal dialog directive that includes some child directives, transclusion, isolated scope. Here is an example of what it looks like:
<dm-modal reference="previewDialog" additional-class="datasourcePreview">
<dm-modal-header><div class="dialog-title" ng-controller="dsPreviewCtrl">Preview of {{previewData.dataSourceName}}</div> </dm-modal-header>
<dm-modal-body>
<div ng-controller="dsPreviewCtrl" ng-include="dsPreview.html'" ></div>
</dm-modal-body>
<dm-modal-footer>
Choose one
</dm-modal-footer>
<dm-modal-footer-buttons>
<dm-modal-footer-button type="done" ng-click="doSomething()"></dm-modal-footer-button>
<dm-modal-footer-button type="delete"></dm-modal-footer-button>
</dm-modal-footer-buttons>
</dm-modal>
When using this directive, 80%+ of the time I only need to fiddle with a couple things because it's just a confirm dialog, and the rest of the options are standard. Rather than a hairy interface that looks like the above, I'd like to implement a directive that would look like the following, but ultimately just produce the directive above.
<dm-simple-modal reference="confirmDialog" title="Are you sure?" okClick="handleOk()">
<div ng-controller="dsPreviewCtrl" ng-include="'dsPreview.html'"></div>
</dm-simple-modal>
So it seems simplest to me to basically transform the above markup into the full markup for the other "more complete" directive which would look like the following:
<dm-modal reference="confirmDialog">
<dm-modal-header>
Are you sure?
</dm-modal-header>
<dm-modal-body>
<div ng-controller="dsPreviewCtrl" ng-include="'dsPreview.html'"></div>
</dm-modal-body>
<dm-modal-footer-buttons>
<dm-modal-footer-button text="Yes" ng-click="handleOk()"></dm-modal-footer-button>
<dm-modal-footer-button type="No"></dm-modal-footer-button>
</dm-modal-footer-buttons>
</dm-modal>
And then angular would compile this directive into the working html.
Putting this problem into a more contrived, but simpler example, I have created a fiddle http://jsfiddle.net/josepheames/JEYJa/1/
I even included an attempt to make the simpler directive using a template patterned after the more complex directive but I cannot get that to work.
the directives in this fiddle look like this:
<div ng-controller="MyCtrl" ng-click="doClick()">
<complex-directive handle="complexDir" titleColor="blue">
<h1>{{title}}</h1>
</complex-directive>
<simple-directive handle="simpleDir" title="another {{title}}" color="red" />
</div>
And the working complex directive is this:
myApp.directive('complexDirective', function() {
return {
restrict: 'E',
scope: {
handle: '='
},
replace: true,
transclude: true,
template: '<div ng-transclude></div>',
link: function(scope, element, attr) {
scope.handle= {
doSomething: function() {
element.css('background-color', attr.titlecolor);
}
}
}
}
});
And my failing attempt at a simple directive is this:
myApp.directive('simpleDirective', function() {
return {
restrict: 'E',
scope: {
handle: '='
},
replace: true,
transclude: true,
template: '<complex-directive handle="{{thehandle}}" titleColor="{{color}}"><h1>{{title}}</h1></complex-directive>',
link: function(scope, element, attr) {
scope.thehandle = attr.handle,
scope.color = attr.titlecolor,
scope.title = element.html(),
scope.handle= {
doSomething: function() {
element.css('background-color', attr.titlecolor);
}
}
}
}
});