Dynamic Controller and templateUrl inject in an Angular Directive - angularjs

I have troubles to find a solution to make a fully dynamic directive. I use the angular-gridster library to make an overview of tiles for a dashboard page. I want to dynamicly load some specific tiles by a flexible directive.
<div gridster="vm.model.gridsterOptions">
<ul>
<li gridster-item="tile.tileParams.gridParams" ng-repeat="tile in vm.model.dashboards.tiles">
<div class="box" ng-controller="tileGrid_TileController">
<eg-tile template-url={{tile.tileEngine.tempUrl}}
controller-name={{tile.tileEngine.tileCtrl}}>
</eg-tile>
</div>
</li>
</ul>
</div>
I have created the egTile directive :
(function () {
function implementation() {
return {
restrict: 'E',
transclude: true,
scope: true,
controller: '#',
bindToController:true,
controllerAs: 'vm',
name: 'controllerName',
templateUrl: function (elem, attrs) {
return attrs.templateUrl || 'app/module/tileGrid/view/templates/empty.html';
}
};
}
var declaration = [implementation];
angular.module('app.tileGrid').directive('egTile', declaration);
}());
This directive will work if I use a fixed string in the egTile directive like
<eg-tile template-url="string_url" controller-name= "string_ctrl"></eg-tile>
but I want to dynamicly select the controller and templateUrl.
I already tried to use the $parse and $observe service but without succes.
Is this even possible to make the directive so flexible ?
Thanks in advance

I found a solution for my problem.....
I used 2 extra directives that will provide the controller-string and the templateUrl-string for the "flexible directive" egTile.
One for creating the controller-string :
(function () {
function implementation($compile, $parse) {
return {
restrict: 'A',
scope: true,
terminal: true,
priority: 99999,
link: function (scope, elem) {
var name = $parse(elem.attr('eg-parse-controller'))(scope);
elem.removeAttr('eg-parse-controller');
elem.attr('controller-name', name);
$compile(elem)(scope);
}
};
}
var declaration = ['$compile', '$parse', implementation];
angular.module('app').directive('egParseController', declaration);
}());
And one for creating the template-string:
(function () {
function implementation($compile, $parse) {
return {
restrict: 'A',
scope: true,
terminal: true,
priority: 99998,
link: function (scope, elem) {
var name = $parse(elem.attr('eg-parse-template'))(scope);
elem.removeAttr('eg-parse-template');
elem.attr('template-url', name);
$compile(elem)(scope);
}
};
}
var declaration = ['$compile', '$parse', implementation];
angular.module('app').directive('egParseTemplate', declaration);
}());
Than I can use it as :
<div gridster="vm.model.gridsterOptions">
<ul>
<li gridster-item="tile.tileParams.gridParams" ng-repeat="tile in vm.model.dashboards.tiles" ng-controller="tileGrid_TileController">
<eg-tile tile="tile"
eg-parse-template=tile.tileEngine.tempUrl
eg-parse-controller='tile.tileEngine.tileCtrl'>
</eg-tile>
</li>
</ul>
with the directive definition :
(function () {
function implementation($parse) {
return {
restrict: 'E',
transclude: true,
scope: {
tile : '='
},
controller: '#',
bindToController:true,
controllerAs: 'vm',
name: 'controllerName',
templateUrl: 'app/module/tileGrid/view/tileTemplate.html',
link: function(scope, element, attrs) {
scope.getContentUrl = function() {
return attrs.templateUrl || 'app/module/tileGrid/view/templates/empty.html';
}
}
};
}
var declaration = ['$parse' , implementation];
angular.module('app.tileGrid').directive('egTile', declaration);
}());
With a tileTemplate.html :
<div class="box">
<div class="box-header">
<h3>{{ vm.tile.tileParams.title }}</h3>
<div class="box-header-btns pull-right">
<a title="Remove widget" ng-click="vm.removeTile(tile)">
<i class="fa fa-trash"></i>
</a>
</div>
</div>
<div class="box-content">
<div ng-include="getContentUrl()"></div>
</div>
</div>
With this aproach I have fully access to the tile that I passed to the egTile Directive in the dynamic loaded controllers and dynamic loaded views.
All remarks are welcome.

Related

How to build directive accept the same nested directive inside it?

Maybe the title of the question is not clear.
What I want to do is to create a directive that I can use like this:
<menu-item title="var1" state="var2">
<menu-item title="var3" state="var4">
</menu-item>
<menu-item title="var5" state="var6">
<menu-item title="var7" state="var8">
</menu-item>
</menu-item>
</menu-item>
And the directive menuItem will have to generate the proper html of course.
The answer was as easy as adding transclude: true' to the directive and then addng-transclude` to the template of this directive where you want the children to be rendered:
(function () {
'use strict';
angular.module('myModule')
.directive('menuItem', menuItem);
menuItem.$inject = [];
function menuItem() {
var menuItemTemplate =
'<li>\
<a ui-sref="{{state ? state : "javascript:;"}}">\
<span class="title"> {{label}} </span>\
</a>\
<ul ng-transclude class="sub-menu">\
</ul>\
</li>';
var directive = {
restrict: 'E',
replace: true,
template: menuItemTemplate,
transclude: true,
scope: {
state: '#',
label: '#'
},
link: function (scope, element, attrs) {
}
};
return directive;
}
})();
Now we use it like I mentioned in the question

Nested ng-transclude in Angular

So, I have this piece of code using transclude in angular.
angular.module('playground', []).directive('tagA', function() {
return {
replace: true,
transclude: true,
controller: function() {
console.log('controller tagA')
},
compile: function($element, $attrs, $link) {
console.log('compile tagA', $element.html());
return {
pre: function($scope, $element, $attrs, controller, $transclude) {
console.log('pre tagA', $element.html());
},
post: function($scope, $element, $attrs, controller, $transclude) {
console.log('post tagA', $element.html());
}
}
},
template: '<u><tag-b><div ng-transclude></div></tag-b></u>'
}
}).directive('tagB', function() {
return {
require: '^tagA',
transclude: true,
controller: function() {
console.log('controller tagB')
},
compile: function($element, $attrs, $transclude) {
console.log('compile tagB', $element.html());
return {
pre: function($scope, $element, $attrs, controller, $transclude) {
console.log('pre tagB', $element.html());
},
post: function($scope, $element, $attrs, controller, $transclude) {
console.log('post tagB', $element.html());
}
}
},
template: '<h1 ng-transclude></h1>'
}
});
And the markup
<tag-a>
Test
</tag-a>
What I am trying to do is to pass the transcluded content from the parent (tagA) to the child (tagB). The above code works but I don't want to have <div ng-transclude></div> in my tagA's template. Apparently, using <u><tag-b ng-transclude></tag-b></u> doesn't work.
Can someone explain why the later (<u><tag-b ng-transclude></tag-b></u>) doesn't work?
The latter form doesn't work, because transclude replaces the inner HTML of the element that ng-transclude is placed on.
In the first, (working you have something like the following):
<tag-a>
<u>
<tag-b>
<div ng-transclude>
<h1 ng-transclude>
Test
</h1>
</div>
</tag-b>
</u>
</tag-a>
since the inner html is replaced, what you end up with is:
<u>
<div>
<h1>
Test
</h1>
<div>
</u>
In the second example, you have:
<tag-a>
<u>
<tag-b ng-transclude>
<h1 ng-transclude>
Test
</h1>
</tag-b>
</u>
</tag-a>
again, with the Directives removed and the inner html replaced, you get:
<u>
</u>

Require controller in directive when terminal is true

I can't load controller's parent in a directive. I have two directives: h-menu and h-menu-item. h-menu uses a controller, and h-menu-item requires that controller.
But h-menu directive has terminal = true, and with this I can't load controller. When I set terminal to false, I can load the controller.
JsFiddle: http://jsfiddle.net/gspVe/4/
html:
<div ng-app="test">
<h-menu>
<h-menu-item>
</h-menu-item>
</h-menu>
</div>
Here is the code js:
angular.module("test", [])
.controller("hMenu", function () {
this.msg = "controller was loaded";
return this;
})
.directive("hMenu", function($compile) {
return {
restrict: "E",
// comment this and controller will be loaded
terminal: true,
controller: "hMenu",
require: "hMenu",
link: function (scope, element, attrs) {
var ul = $("<ul/>");
ul.append(element.children());
$compile(ul)(scope);
element.replaceWith(ul);
}
};
})
.directive("hMenuItem", function($compile) {
return {
restrict: "E",
terminal: true,
require: "?^hMenu",
link: function (scope, element, attrs, controller) {
var li = $("<li/>");
if (controller)
li.html(controller.msg);
else
li.html("contoller not loaded!");
$compile(li)(scope);
element.replaceWith(li);
}
};
})
Just trying to understand what you're trying to do. If you want to replace HTML elements, why not use some of the facilities that a directive provides already?
For instance, it looks like you're trying to replace the directive elements with <ul> and <li>. Rather than define this as you've indicated, why couldn't you do the following:
angular.module("test", [])
.directive("hMenu", function() {
return {
restrict: "E",
transclude : true,
replace : true,
template : "<ul ng-transclude></ul>"
};
})
.directive("hMenuItem", function() {
return {
restrict: "E",
replace : true,
transclude : true,
template : '<li ng-transclude></li>'
};
})
Then, you can use the power of Angular to indicate menu items declaratively, for example:
<div ng-app="test">
<h-menu ng-init="menuitems=['Menu #1', 'Menu #2', 'Menu #3']">
<h-menu-item ng-repeat="m in menuitems">
{{m}}
</h-menu-item>
</h-menu>
</div>
This would show:
Menu #1
Menu #2
Menu #3
I've updated a jsfiddle at http://jsfiddle.net/gspVe/9/

ngIf (.) DOT notation scope inheritance doesn't work 2-ways in directive template

I couldn't get why this doesn't work. I thought dot-notation on ng-if makes the 2 way data binding but I could not change the parent $scope.modal.active within ng-if scope.
template/lupus/modal/modal.html:
<div class="lupus-modal">
<img ng-src="{{lupusModalImg}}" class="img-responsive"/>
<div ng-if="modal.active" class="backdrop modal-backdrop" ng-click="modal.active = false" > //this ng-click doesn't work as I expected
<img ng-src="{{lupusModalImg}}"/>
</div>
</div>
lupusModal directive:
.directive('lupusModal', [
function () {
return {
scope: {
lupusModalImg: '='
},
restrict: 'AE',
transclude: true,
templateUrl: 'template/lupus/modal/modal.html',
controller: function ($scope, $element, $attrs, $transclude) {
$scope.modal = {
active: false,
close: function () {
$scope.modal.active = false;
$scope.$apply();
}
};
$element.on('click', function () {
$scope.modal.active = true;
$scope.$apply();
});
},
link: function (scope, iElement, iAttrs, backdropCtrl) {
//backdropCtrl.$element.$scope.slideElm[0].getBoundingClientRect();
}
};
}
]);
index.html:
If i understand the question correctly, you are creating isolated scope in your directive, which does not inherit from its parent scope.
To use the modal.active set a two way binding using = in scope declaration in directive
scope: {
lupusModalImg: '=',
modal:'='
},
and declare your directive using
<a href="javascript:void(0);" lupus-modal lupus-modal img="'img/certificates/UygunlukSertifikasi1.jpg'" modal='modal'></a>

Single angular js directive and many controllers

I want to know how to invoke a function residing in controller on the ng-click event of template element. I have to use this directive in many pages.Hence I need to handle the click event in respective controller pages.The below code invokes the click function (moreitemdetails) residing within the directive.I tried setting the scope as moreitemdetails: '=' . It is also not working.
I have been using the directive
app.directive('groceryList', function){
return {
restrict: 'E',
scope: {
array: '=',
listItemClick:'&',
moreitemdetails: '&',
},
templateUrl: 'list.html',
link: function(scope, element, attrs) {
scope.label = attrs.label;
scope.listItemClick=function(e){
$(e.currentTarget).find('.next-items').slideToggle('fast');
}
scope.moreitemdetails=function(name,type){
//other code
}
}
};
});
The call for directive is
<grocery-list array="items"></grocery-list>
This is the template file
<div ng-click="listItemClick($event)">
<div>
<div class="item">
<span class="item-details">
{{array[0].Item}}
</span>
<span class="down-arrow"></span>
</div>
<div class="next-items">
<ul>
<li class="item" ng-repeat="list in array">
<div class="item-details" ng-click="moreitemdetails(list.Name,list.Type)">{{list.Item}}</div>
</li>
</ul>
</div>
Is there a way to get around?
I also would like to know the use of $location within another directive. Quoting the previous example (everythin is same except the directive definition and action in moreitemdetails() )
app.ui.directive('groceryList', ['$location', function(location){
return {
restrict: 'E',
scope: {
array: '=',
listItemClick:'&',
moreitemdetails: '&',
},
templateUrl: 'list.html',
link: function(scope, element, attrs) {
scope.label = attrs.label;
scope.listItemClick=function(e){
$(e.currentTarget).find('.next-items').slideToggle('fast');
}
scope.moreitemdetails=function(name,type){
$location.path('/home/');
}
}
};
}]);
Thanks in advance
So by declaring
scope: {
array: '=',
listItemClick:'&',
moreitemdetails: '&',
},
you are creating an isolated scope for your directive. One solution might be to not declare this scope in your directive. This would mean that your ng-click="moreitemdetails(list.Name,list.Type) would trigger the function on your controllers scope.
Alternatively you could use an emit and listener. To do this, in your directive you could call:
scope.moreitemdetails=function(name,type){
var deets = {
name: name,
type: type
};
scope.emit('moreitemdetails',deets)
}
Then in your various controllers you implement:
scope.$on('moreitemdetails',function(event,details){
// do some code with your name and type
}
I'm not sure exactly what you would like to know about $location, if you could be a bit more specific I might be able to help a more.
Hope this helps in some way!
EDIT:
The directive without any scope declared would look like this:
return {
restrict: 'E',
templateUrl: 'list.html',
link: function(scope, element, attrs) {
scope.label = attrs.label;
scope.listItemClick=function(e){
$(e.currentTarget).find('.next-items').slideToggle('fast');
}
scope.moreitemdetails=function(name,type){
//other code
}
}
};

Resources