I have bunch of places where I need to add ng-show to the element to hide it. Can it be done from one place in the code?
So instead of this template:
<tr my-row ng-show="$ctrl.model.toShow()"></tr>
It should be:
<tr my-row ></tr>
and then in the directive:
function myRow (){
return {
restrict: 'A',
templateUrl: 'my-row.html',
..
link = function(scope,el,attrs){
scope.$watch('modle.toShow', function(){
//something here?
})
}
};
}
Make these changes to the link function, and this adds an attribute ng-show to your my-row directive.
var app = angular.module('app', [])
.directive('myRow', function($compile) {
return {
restrict: 'A',
templateUrl: 'my-row.html',
link: function (scope, element, attrs) {
var attr;
element.attr('ng-show', true);
var fn = $compile(element);
return function(scope){
fn(scope);
};
}
};
})
HEre is the plunker
Related
I have two nested directive and a few controllers and I want inject controller to second controller.
When I bind action to some button it work but list don't show up, some one know why?
Dynamic Controller directive
.directive("dynamicController", ["$compile", function($compile) {
return {
restrict: "A",
scope: {
dynamicController: "#"
},
compile: function(tElement, tAttrs) {
return {
pre: function preLink(scope, iElement, iAttrs, controller) {
iElement.attr("ng-controller", scope.dynamicController);
iElement.removeAttr("dynamic-controller");
$compile(iElement)(scope);
}
}
}
}
}])
V1: http://codepen.io/anon/pen/LVeaWo
V2: http://codepen.io/anon/pen/EjoJVx
[ EDIT ]
I almost do it but it's one more problem.
I have two directive:
.directive("wrapDirective", function() {
return {
restrict: "A",
template: "<div dynamic-controller=\"Ctr1\">" +
"<button ng-click='action()'>Click</button>" +
"<ul>" +
"<li ng-repeat=\"item in list\">{{item}}</li>" +
"</ul>" +
"</div>",
scope: {
controller: "#wrapDirective"
}
}
})
and
.directive("dynamicController", function($compile) {
return {
restrict: "A",
scope: true,
controller: "#",
name: "dynamicController"
}
})
The problem is this line <div dynamic-controller=\"Ctr1\"> in warpDirective
I can't do something like this <div dynamic-controller=\"{{controller}}\">
CodePen with both cases: http://codepen.io/anon/pen/EjoJXV
You should use require and link to get the controllers of parent directives.
See Creating Directives that Communicate.
.directive('myDirective', function() {
return {
require: '^ngController', // <-- define parent directive
restrict: 'E',
scope: {
title: '#'
},
link: function(scope, element, attrs, ctrl) { // <-- get the controller via the link function
ctrl.doSomething();
}
};
The reason behind your code is not working is, {{}} interpolation value is not evaluated in you pre link function. So by compiling ng-controller with not value in it is throwing an error. You should use iAttrs.$observe as you are evaluating expression inside {{}}.
Code
var dynamicControllerObserver = iAttrs.$observe('dynamicController', function(newVal, oldVal) {
wrapElement.attr("ng-controller", scope.dynamicController);
wrapElement.append(iElement.html());
console.log(wrapElement)
iElement.html("");
console.log(iElement)
iElement.append(wrapElement);
$compile(wrapElement)(scope);
dynamicControllerObserver(); //destruct observe
})
Working Codepen
I did it, really helpful was this post: Dynamic NG-Controller Name
I modified it to my needs:
.directive('dynamicCtrl', ['$compile', '$parse', function($compile, $parse) {
return {
restrict: 'A',
terminal: true,
scope: {
dynamicCtrl: "#"
},
link: function(scope, elem, attr) {
var initContent = elem.html();
var varName = getName(elem.attr('dynamic-ctrl'));
update();
scope.$watch("dynamicCtrl", function() {
update();
})
function update() {
var wrapper = angular.element("<div></div>");
wrapper.append(initContent);
var name = $parse(varName)(scope.$parent);
wrapper.attr('ng-controller', name);
elem.empty();
elem.append(wrapper);
$compile(wrapper)(scope);
}
function getName(attr) {
var startIndex = attr.lastIndexOf("{") + 1,
endIndex = attr.indexOf("}");
return attr.substring(startIndex, endIndex);
}
}
};
}])
http://codepen.io/anon/pen/xGYyqr
Recently I use angular to develop a directive, there is an directive which like ng-repeat to generate some records, I used transclude to implement it. but it raise an error that "Controller 'aArea', required by directive 'bSpan', can't be found!".
1. ModuleA code
var moduleA = angular.module("moduleA", []);
moduleA.directive("aArea", function () {
return {
restrict: 'E',
transclude:'element',
scope: {
amount:"="
},
template: '<div id=\"cc\" ng-transclude></div>',
controller: function ($scope,$element,$attrs) {
this.getData = function (data) {
return data + " is ok";
}
},
compile: function (tElement, attrs, linker) {
var parentElement = tElement.parent();
return {
pre: function () {
},
post: function (scope) {
linker(scope.$parent,function (clone,scope) {
parentElement.append(clone);
});
linker(scope.$parent, function (clone, scope) {
parentElement.append(clone);
});
linker(scope.$parent, function (clone, scope) {
parentElement.append(clone);
});
}
}
}
}
});
moduleA.directive("bSpan", function () {
return {
restrict: 'E',
scope: {
data: "=",
},
template: '<span style=\"background-color:gray;color:orange\">{{data}}</span>',
require: "^aArea",
link: function ($scope, $element, $attrs, controller) {
var data = "abc";
}
}
});
2. ModuleB COde
var moduleB = angular.module("moduleB", []);
moduleB.directive("myItem", function () {
return {
restrict: 'E',
scope: {
item: "=",
itemTemplate: '='
},
priority: 1000,
terminal:false,
template: '<ng-include src=\"itemTemplate\"/>',
controller: function ($scope, $element, $attrs) {
var data = "";
}
}
})
3. ModuleC Code
var moduleC = angular.module("moduleC", ["moduleA", "moduleB"]);
moduleC.controller("Ctr", function ($scope) {
$scope.item = {};
$scope.item.dataAmount = 1000;
$scope.item.templateUrl = "item-template.html";
})
4. Html Code
<body>
<div ng-app="moduleC">
<div ng-controller="Ctr">
<a-area>
<my-item item="item" item-template="item.templateUrl"></my-item>
</a-area>
</div>
</div>
</body>
5. template code
<div>
<span style="display:block">hello every one</span>
<b-span data="item.dataAmount"></b-span>
</div>
You should not use the transclude function (that you called linker) of the compile function - it is deprecated.
From $compile documentation:
Note: The transclude function that is passed to the compile function is deprecated, as it e.g. does not know about the right outer scope. Please use the transclude function that is passed to the link function instead.
Following this guidance (and a few other minor changes for the better), change the aArea directive as follows:
compile: function(tElement, tAttrs) {
// don't use the template element
//var parentElement = tElement.parent();
return function(scope, element, attrs, ctrls, transclude) {
transclude(function(clone, scope) {
element.after(clone);
});
transclude(function(clone, scope) {
element.after(clone);
});
transclude(function(clone, scope) {
element.after(clone);
});
};
}
In fact, you don't even need the transclude function at all and you don't need to do transclude: "element". You could just change to transclude: true and use <div ng-transclude> 3 times in the template.
I have a view that contains a template specified in a custom directive. The template that is used in the custom directive depends on 'dynamicTemplate':
View:
<div ng-controller="MyController">
<custom-dir dynamicTemplate="dynamicTemplateType"></custom-dir>
<button ng-click="ok()">Ok</button>
</div>
View's Controller:
myModule
.controller('MyController', ['$scope', function ($scope) {
$scope.dynamicTemplateType= 'input';
$scope.myValue = "";
$scope.ok = function()
{
// Problem! Here I check for 'myValue' but it is never updated!
}
Custom Directive:
myModule.directive("customDir", function ($compile) {
var inputTemplate = '<input ng-model="$parent.myValue"></input>';
var getTemplate = function (templateType) {
// Some logic to return one of several possible templates
if( templateType == 'input' )
{
return inputTemplate;
}
}
var linker = function (scope, element, attrs) {
scope.$watch('dynamicTemplate', function (val) {
element.html(getTemplate(scope.dynamicTemplate)).show();
});
$compile(element.contents())(scope);
}
return {
restrict: 'AEC',
link: linker,
scope: {
dynamicTemplate: '='
}
}
});
In this above example, I want 'myValue' that is in MyController to be bound to the template that is in my custom directive, but this does not happen.
I noticed that if I removed the dynamic templating (i.e. the contents in my directive's linker) and returned a hardcoded template instead, then the binding worked fine:
// If directive is changed to the following then binding works as desired
// but it is no longer a dynamic template:
return {
restrict: 'AEC',
link: linker,
scope: {
dynamicTemplate: '='
},
template: '<input ng-model="$parent.myValue"></input>'
}
I don't understand why this doesn't work for the dynamic template?
I am using AngularJS 1.3.0.
Maybe you should pass that value into your directives scope, instead of only dynamicTemplate, i think it should work.
You have a good answer about directives scope here: How to access parent scope from within a custom directive *with own scope* in AngularJS?
Hope I was of any help.
js directive :
angular.module('directive')
.directive('myDirective', function ($compile) {
var tpl1 ='<div class="template1">{{data.title}}</div>';
var tpl2 ='<div class="template2">Hi</div>';
var getTemplate = function (data) {
if (data.title == 'hello') {
template = tpl1;
}
else {
template = tpl2;
}
return template;
};
var linker = function (scope, element, attrs, ngModelCtrl) {
ngModelCtrl.$render = function () {
// wait for data from the ng-model, particulary if you are loading the data from an http request
if (scope.data != null) {
element.html(getTemplate(scope.data));
$compile(element.contents())(scope);
}
};
};
return {
restrict: "E",
require: 'ngModel',
link: linker,
scope: {
data: '=ngModel'
}
};
});
html :
<my-directive
ng-model="myData">
</my-directive>
js controller:
$scope.myData = {title:'hello'};
I define $items array in a page controller:
$scope.items = [{id:1,type:apple},{id:2,type:banana},{id:3,type:mango}]
Then I iterate over this array from a page template:
<div ng-repeat="item in items">
<my-item item-type="{{item.type}}"></my-item>
</div>
myItem is a directive defined as follows:
function () {
function resolveTemplate(element, attrs) {
var itemType = '';
if (itemType === 'apple') {
return 'apple.html';
} else {
return 'default.html';
}
}
return {
restrict: 'E',
templateUrl: resolveTemplate,
link: function (scope, element, attrs) {
// nothing here
}
};
}
I've tried to use:
scope: {
'itemType': '#'
}
But it appears that I can call attrs.itemType only from link() and not from resolveTemplate() function (where the unprocessed {{item.type}} is returned).
So what will be a correct way to dynamically choose a template in this situation?
From angularjs docs: https://docs.angularjs.org/guide/directive
Note: You do not currently have the ability to access scope variables
from the templateUrl function, since the template is requested before
the scope is initialized.
You can however have access to some angularjs service in your resolveTemplate function. So if possible, you can pull in the itemType from the service instead. Your directive would look something like below:
app.directive('myDirective', function(templateResolveService){
function resolveTemplate(element, attrs) {
var itemType = templateResolveService.getItemType();
if (itemType === 'apple') {
return 'apple.html';
} else {
return 'default.html';
}
}
return {
restrict: 'E',
templateUrl: resolveTemplate,
link: function (scope, element, attrs) {
// nothing here
}
};
}
I have a setup like this:
<controller>
<directive>
in my controller that has a function that returns an html string. How can I get my directive to render this by accessing the controllers scope?
Or maybe I should just put the controller in the directive?
app.controller('controller', ['$scope', 'DataService', function ($scope, DataService) {
$scope.parseJson = function () {
//returns the html
};
}]);
directive
app.directive('Output', function () {
return {
restrict: 'A',
replace: true,
template: '<need html from controller>',
link: function(scope, element, attr) {
//render
//scope.parseJson();
}
};
});
You should use the isolated scope: '&' option
app.directive('output', ['$sce', function ($sce) {
return {
restrict: 'A',
replace: true,
template: "<div ng-bind-html='parsed'></div>",
scope:{
output: "&"
},
link: function(scope){
scope.parsed = $sce.trustAsHtml(scope.output());
}
};
}]);
Template:
<div output="parseJson()"></div>
The directive and the controller should be sharing the scope already. Don't bother using a template for the directive, just get the HTML string in you linking function (you already have the method call in there) and modify the element directly using element.html(). Take a look at the element docs for more info.
app.directive('Output', function ($compile) {
return {
restrict: 'A',
link: function(scope, element, attr) {
var templateString = scope.parseJson();
var compiledTemplate = $compile(templateString)(scope);
compiledTemplate.appendTo("TheElementYouWishtoAppendYourDirectiveTo");
}
};
});