I am stuck with this for quite a long time.
In a directive, I would like to create another directive on the fly, based on a function. Instead of having 4 directive declarations, I would prefer to create a new directive in each 'tab' directive, that is to say each time a tab attribute is set in a DOM element.
Here is a part of the code (config is a factory that is use to configure some stuff) :
.directive('tab', function(config) {
return {
require: '^panelHandler',
restrict: 'A',
scope: true,
link: function(scope, elem, attrs, ctrl) {
ctrl.addPane(scope);
scope.select = function() {
ctrl.select(scope);
};
},
};
})
.directive('page1', directiveConfigurer('page1.html'))
.directive('page2', directiveConfigurer('page2.html'))
.directive('page3', directiveConfigurer('page3.html'))
.directive('page4', directiveConfigurer('page4.html'));
function directiveConfigurer(fileName) {
newDirective.$inject = ['config'];
return newDirective;
function newDirective(config) {
var directive = {
restrict: 'E',
scope: true,
templateUrl: config.filesDirectory + fileName,
};
return directive;
}
}
Thanks for your help.
EDIT :
Config...
angular.module('appLogic', ['socket-factory', 'data-factory', 'panelHandler-module'])
.factory('config', function() {
return {
filesDirectory : '../../templates/pages/',
fieldsNumber : 5,
};
});
and what I need...
link: function(scope, elem, attrs, ctrl) {
ctrl.addPane(scope);
//.directive('page' + number, directiveConfigurer(name))
scope.select = function() {
ctrl.select(scope);
};
},
If the directives are essentially the same, except for the template url, then you can just create a single directive and provide the concrete url path as an attribute:
<page src="page1.html">
To do that, use a function for templateUrl property of the directive definition object:
.directive("page", function(){
return {
templateUrl: function(tElem, tAttr){
return "/base/path/" + tAttr.src;
},
//...
};
});
Related
i made this directive that i call in my html like
<my-datepicker />
what i want to do now, is to expend it, so that i can do something like this
<my-datepicker >
<my-day>{{date}}</my-day>
<my-month>{{moth}}</my-month>
<my-year>{{year}}</my-year>
</my-datepicker>
resulting in my-datepicker directive can get the child elements.
how can you do this? do you need to make the childs like standanlone directive, an how can you get the value from them in the parent(my-datepicker) directive
You can access parent directive controller like this:
app.directive('myDay', function () {
return {
restrict: 'E',
require: '^myDatepicker',
link: function (scope, element, attrs, parentCtrl) {
parentCtrl.doSomething();
}
};
});
In parent:
app.directive('myDatepicker', function () {
return {
restrict: 'E',
controller: function () {
this.doSomething = function () {
alert('Do something in parent');
};
}
};
});
I think the best way to extend your directive is by passing parameters (date month ec... ) as attributes.
Your html should be somethig like this
<my-datepicker day="day" month="month" year="year"/>
Inside your directive you should be able to retrieve those variables by declaring them in the scope.
function myDatepicker() {
var directive = {
templateUrl: 'yourtemplate.html',
restrict: 'E',
link: link,
scope: {
date: '=',
month: '=',
year: '='
}
}
return directive;
function link(scope, element, attrs) {
// use here scope.date scope.month and scope.year
}
};
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
can we create a custom directive set as an element with required parameters , so that if these params are not provided by who ever would like to use it... then the directive must not work ???
**JS:**
app.directive('customDirective', function() {
return {
restrict: 'E',
scope : {
data : "=data", ...
} ,
templateUrl: function(element, attr) {
// HTML file path
},
....
}
});
the case is now even if these params are not passed , the directive still works and injects the html in the view .
This is a generic question about directive not related to this specific case only .
You could add your own validation in the link function
app.directive('customDirective', function() {
return {
restrict: 'E',
scope : {
data : "=data", ...
} ,
templateUrl: function(element, attr) {
// HTML file path
},
link: function(scope, element, attrs){
if(!data){
element.remove();
}
}
}
});
I'm not sure if there's a more official way though
I am trying to create a proxy directive like so:
<x-field x-purpose="choice" x-values="data.countries" ng-model="model.country"/>
Where the field directive forwards this to another directive, causing the following replacement:
<x-choiceField x-values="data.countries" ng-model="model.country"/>
[note:] the ng-model could be replaced by a reference to some new isolated scope.
The "field purpose" directive decides which implementation to use (e.g. drop-down/listbox/autocomplete?) based on how many values there are to choose from, client device size, etc - ultimately resulting in something like this:
<select ng-model="model.country" ng-options="data.countries">
This design is largely out of curiosity rather than for any practical reason, I am interested in how to achieve it rather than whether it is actually a good idea from a performance/simplicity point of view...
After reading [https://stackoverflow.com/a/18895441/1156377], I have something like this:
function proxyDirective($injector, $parse, element) {
return function (scope, element, attrs) {
var target = element.camelCase(attrs.name + '-field');
var model = attrs.ngModel;
var value = $parse(model);
var directive = $injector.get(target);
/* Bind ngModel to new isolated scope "value" property */
scope.$watch(model, function () {
???
});
/* Generate new directive element */
var pElement = angular.element.html('');
var pAttrs = {
value: ???
};
/* Forward to new directive */
return directive.compile(element, attrs, null)(scope, element, attrs);
};
}
function alphaFieldDirective() {
return {
replace: 'true',
template: '<input type="text" ng-value="forwarded value?">'
};
}
function betaFieldDirective() {
return {
replace: 'true',
template: '<textarea attributes? >{{ value }}</textarea>'
};
}
But I'm not sure how to achieve the forwarding or binding. This is my first forage into Angular directives, and it doesn't seem to be a particularly popular way of using them!
The purpose of this is to separate the purpose of a form field from its appearance/implementation, and to provide one simple directive for instantiating fields.
I implemented this via a service which proxies directives:
Fiddle: http://jsfiddle.net/HB7LU/7779/
HTML:
<body ng-app="myApp">
<h1>Directive proxying</h1>
<proxy target="bold" text="Bold text"></proxy>
<h1>Attribute forwarding</h1>
<proxy target="italic" style="color: red;" text="Red, italic text"></proxy>
</body>
Javascript:
angular.module('myApp', [])
.factory('directiveProxyService', directiveProxyService)
.directive('proxy', dirProxy)
.directive('bold', boldDirective)
.directive('italic', italicDirective)
;
function directiveProxyService($compile) {
return function (target, scope, element, attrs, ignoreAttrs) {
var forward = angular.element('<' + target + '/>');
/* Move attributes over */
_(attrs).chain()
.omit(ignoreAttrs || [])
.omit('class', 'id')
.omit(function (val, key) { return key.charAt(0) === '$'; })
.each(function (val, key) {
element.removeAttr(attrs.$attr[key]);
forward.attr(attrs.$attr[key], val);
});
$compile(forward)(scope);
element.append(forward);
return forward;
};
}
function dirProxy(directiveProxyService) {
return {
restrict: 'E',
terminal: true,
priority: 1000000,
replace: true,
template: '<span></span>',
link: function (scope, element, attrs) {
directiveProxyService(attrs.target, scope, element, attrs, ['target']);
}
};
}
function boldDirective() {
return {
restrict: 'E',
replace: true,
template: '<i>{{ text }}</i>',
scope: { text: '#' }
};
}
function italicDirective() {
return {
restrict: 'E',
replace: true,
template: '<i>{{ text }}</i>',
scope: { text: '#' }
};
}
I have a situation where I have to create a container directive with some bells and whistles.. and then I have a list of directives that can be wrapped into that container directive.
Something like this:
<the-container foo="bell" bar="whistle">
<some-directive></some-directive>
</the-container>
<the-container foo="potato" bar="tomato">
<some-other-directive></some-other-directive>
</the-container>
Is there any way in which I can make <some-directive> and <some-other-directive> aware of the foo and/or bar attributes values of the directive they are transcluded in?
Basic theContainer directive
.directive("theContainer", function() {
return {
restrict: "E",
replace: true,
transclude: true,
scope: true,
templateUrl: "theContainer.html",
compile: function(element, attrs) {
return function(scope, element, attrs) {
scope.containerAttrs = {
foo: attrs.foo,
bar: attrs.bar
};
};
}
}
});
Let's presume that these two directives have different and unrelated function all together
someDirective
.directive("someDirective", function() {
return {
restrict: "E",
scope: true,
templateUrl: "someDirective.html",
controller: function($scope, $element, $attrs) {},
compile: function(element, attrs) {
return function(scope, element, attrs) {
// I need the value from theContainer foo and bar here
// or anywhere in this directive for starters
foo = 'bells';
bar = 'whistles';
};
}
}
});
someOtherDirective
.directive("someOtherDirective", function() {
return {
restrict: "E",
scope: true,
templateUrl: "someOtherDirective.html",
controller: function($scope, $element, $attrs) {
// I need the value from theContainer foo and bar here
// or anywhere in this directive for starters
foo = 'potato';
bar = 'tomato';
}
}
});
Scopes in angular by default inherit from parent scope. Unfortunately, with angular default transclusion, there is no child/parent relationship between container and transcluded content.
You could try custom transclusion.
compile: function(element, attrs, linker) {
return function(scope, element, attrs) {
scope.containerAttrs = {
foo: attrs.foo,
bar: attrs.bar
};
linker(scope, function(clone) { //bind scope anyway you want
element.append(clone); // add to DOM anywhere you want
});
};
}
Note: remember to remove ng-transclude in your template when you do custom transclusion,
DEMO