I want dynamically set attributes for child directive.
But I can not do it.
In the example I want to set attribute name with value John.
UPD: In this case, I can not determine the exact set of attributes. And I need to define them dynamically. For example, this is part of a third-party library.
angular.module('app', []);
angular.module('app')
.directive('wrapper', wrapper);
angular.module('app')
.directive('inWrapper', inWrapper);
angular.bootstrap(
document.getElementById('root'), ['app']
);
function wrapper() {
return {
transclude: true,
template: '<div ng-transclude></div>',
link: function linkFn(scope, element, attrs, ctrl, transcludeFn) {
transcludeFn(
scope,
function(clone) {
clone.find('in-wrapper').attr('name', 'John');
}
);
}
}
}
function inWrapper() {
return {
scope: {
name: '#'
},
template: 'Hello, {{name}}',
link: function linkFunc(scope) {
if (!scope.name) {
scope.name = 'Empty';
}
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<div id="root">
<wrapper>
<in-wrapper></in-wrapper>
</wrapper>
</div>
I tried to set attributes dynamically but failed.
I suggest instead providing data for attributes in the item area directly.
In the code examples, I modified elements inside the transclude function. And provided data for attribute elements in the controller. Code not ideal. Only for demonstration idea.
P.S. Thanks #CodeMonkey for criticizing the original idea
angular.module("app", []);
angular.module("app").directive("wrapper", wrapper);
angular.module("app").directive("inWrapper", inWrapper);
angular.bootstrap(document.getElementById("root"), ["app"]);
function wrapper() {
return {
restrict: "E",
transclude: true,
controller: function linkFn($scope, $element, $attrs, $transclude) {
var transcludedContent, transclusionScope,
statuses = [
'danger',
'success',
'info'
];
status = 0;
$transclude(function(clone, scope) {
$element.append(clone);
transcludedContent = clone;
transclusionScope = scope;
for (var i = 0, ii = clone.length; i < ii; i++) {
var node = clone[i];
if (node.nodeType !== 3 ||
node.nodeValue.trim()) {
node.classList.add(statuses[status]);
status++;
}
}
});
// find scope for angulars element and provide data
var elements = $element.find("in-wrapper");
for (var i = 0, ii = elements.length; i < ii; i++) {
var isolateScope = angular.element(elements[i]).isolateScope();
isolateScope.name = 'World' + isolateScope.$id;
}
}
};
}
function inWrapper() {
return {
scope: {
name: "#"
},
template: '<div>Hello, <span ng-bind="name"></span></div>'
};
}
.danger {
color: #ff0000;
}
.success {
color: #00ff00;
}
.info {
color: #0000ff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<div id="root">
<wrapper>
<in-wrapper></in-wrapper>
<in-wrapper></in-wrapper>
<in-wrapper></in-wrapper>
</wrapper>
</div>
You should be able to link the attribute to a scope variable. If you are talking about designing the directive yourself, you can define the controller's scope variables as "=" and they will link bidirectionally to the value passed to the attribute. Check the AngularJS: Developer Guide for more info.
You might also be able to assign the value of the attribute using {{...}} annotation to inject a scope variable directly into the markup.
Related
I have several angular directives.
I want to include them in an accordion directive with an ng-repeat depending on the type.
I want it to be reusable, so I don't want the accordion to have knowledge of the type of directives it is rendering.
That's why I don't want to use an ng-switch directive inside the accordion.
Here is a very simplified demo. In reality those directives will have their own attributes for angular to compile.
var testApp = angular.module('testApp', []);
(function() {
function Element1() {
return {
template: '<span>hello</span>',
restrict: 'E',
replace: true
}
}
testApp.directive('elementOne', Element1);
}());
(function() {
function Element2() {
return {
template: '<span>world</span>',
restrict: 'E',
replace: true
}
}
testApp.directive('elementTwo', Element2);
}());
(function() {
function Accordion() {
return {
template: '<ul><li ng-repeat="element in elements"><button>Toggle</button> Here should be the directive</li></ul>',
restrict: 'E',
replace: true,
controller: function($scope) {
$scope.elements = [{
type: 1
}, {
type: 2
}];
}
}
}
testApp.directive('elementAccordion', Accordion);
}());
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="testApp">
<element-accordion></element-accordion>
</div>
Thanks!
I've built something very similar a few weeks ago. I wanted to render a list of directives and select the type of directive based on the type of an element in an array (just like you). I tried to avoid ngSwitch because I've really many directives and want to add new directives without changing the ngSwitch statement every time. Let me show a simplified version of what I did. I hope this will help you to build your own version.
First, we need a global directory of element diretives and a helper function to register new directives. In my case, the helper function even creates the directive, but I'll skip this to keep it simple:
angular.module("app").provider('elementDirectory', function() {
return {
elements: [],
$get: function () {
return {
elements: this.elements
}
}
};
});
function registerElementDirective(type, directiveName) {
angular.module("app").config(['elementDirectoryProvider', function (elementDirectoryProvider) {
elementDirectoryProvider.elements.push({
type : type,
directive : directiveName
});
}]);
}
Now, we can create some directives like this:
angular.module("app").directive('elementA', function() {
return {
template: '<span>ElementA</span>',
}
});
registerElementDirective('A', 'element-a');
The most interesting part is a directive I called elementSwitch. It takes an array of elements and dynamically adds an angular.element for each element. Therefore, it creates prototypes for each element in the elementDirectoy and uses the clone() method on changes. (I think we could skip this part, it was an optimization).
angular.module('app').directive('elementSwitch', elementSwitch);
elementSwitch.$inject = ['$compile', 'elementDirectory'];
function elementSwitch($compile, elementDirectory) {
var prototypes = {};
elementDirectory.elements.forEach(function (e) {
var directiveElem = angular.element('<' + e.directive + '>');
prototypes[e.type] = directiveElem;
});
return {
scope: {
elements: '=elementSwitch'
},
link: function (scope, elem, attr) {
var childScopes = [];
scope.$watchCollection('elements', function (newValue, oldValue) {
childScopes.forEach(function (s) {
s.$destroy();
});
childScopes = [];
elem.empty();
newValue.forEach(function (element, index) {
var childScope = scope.$new();
childScopes.push(childScope);
var directiveElem = prototypes[element].clone();
directiveElem.attr('element', '_elements[' + index + ']');
$compile(directiveElem)(childScope);
elem.append(directiveElem);
});
scope._elements = newValue;
});
}
}
}
Here is a complete example: https://codepen.io/hansmaad/pen/oLvRmj
It doesn't do the same thing you want, but you should get an idea how you could achieve your goal.
I'm a bit stuck on an directive which add attributes and recompile the element.
If I had a scope on the directive ng-change is not triggered anymore (without it it works). I based my test on this answer
The HTML
<div ng-app="myApp">
<div ng-controller='testController'>
<div ng-repeat="field in fields">
<input type="text" ng-model="ngModel[field.fieldName]" property="{{formParams.getProperties(field.fieldName)}}" update-attr ng-change="test()" />
</div>
</div>
</div>
The directive:
angular.module('myApp', [])
.controller('testController', function ($scope) {
$scope.properties = {
"something": {
"style": "float:left;"
},
"something2": {
"style": "float:right;"
}
};
$scope.ngModel = {};
$scope.fields = [{
fieldName: 'something'
}, {
fieldName: 'something2'
}];
$scope.test = function () {
alert('i dont get triggered');
};
$scope.formParams = {
getProperties: function (fieldName) {
return $scope.properties[fieldName];
}
};
})
.directive('updateAttr', function ($compile) {
return {
restrict: 'A',
replace: true,
terminate: true,
scope: {
ngModel : '='
},
link: function (scope, elem, attrs) {
if (angular.isDefined(attrs['property']) && attrs['property'].lenght != 0) {
var json = JSON.parse(attrs['property']);
angular.forEach(json, function (value, key) {
elem.attr(key, value);
});
elem.removeAttr('property');
var $e = $compile(elem[0].outerHTML)(scope);
elem.replaceWith($e);
}
}
};
});
Here a fork of the fiddle to test with a scope on the directive: fiddle
Do you have any suggestion ?
I found why ng-change was not trigger so I share the answer:
When we add scope attribute on the directive, a new scope is created. So we have to use $scope.$parent for the compilation. I have updated the fiddle with the correction.
Lets assume I have a AngularJS directive looking like this:
app.directive('psDIR', [
function() {
return {
template: "<div style='padding: 5px; border: 1px solid red; margin-bottom: 10px;'><p>This is a direcive:</p> <textarea rows='5' cols='50' ng-model='md'></textarea></div>",
restrict: 'AEC',
scope: {}
}
}
]);
I am using this directive number of times on a single page. How do I get a value of every directive instance/scope of the ng-model="md" in my MainCtrl (i.e. I want to save this value in the add()) :
app.controller('MainCtrl', ['$scope',
function($scope) {
console.log("init");
$scope.add = function() {
console.log($scope);
}
}
]);
Plunker demo: http://embed.plnkr.co/Q5bw6CBxPYeNe7q6vPsk/preview
Any suggestions much appreciated.
Since you are creating isolated scope and otherwise too you cannot access the child scope from parent scope.
The way out it to pass the model as parameter from parent like
<div class="psDIR" model='field2'></div>
<div class="psDIR" model='field1'></div>
Then in the directive update them with attribute binding. See update plunkr
http://plnkr.co/edit/GKZHSDe5J0eCzqlgaf4g?p=preview
Usually the right thing to do when you want to communicate between scopes (i.e. directives and other controllers) is to use a service.
You can take a look at a simple plunker here: http://plnkr.co/edit/8Al31Bq9BfoazUCpqhWy?p=preview
The psDirs service keeps a registry of the directives:
app.service('psDirs', function() {
var psDirs = {
dirs: [],
register: function (dir) {
psDirs.dirs.push(dir);
}
};
return psDirs;
});
And the directives register themselves and update the value when they change:
link: function (scope, elm, attr) {
var dir = { val: "" };
psDirs.register(dir);
scope.$watch('md', function (n, o) {
if (n !== o) {
dir.val = n;
}
});
}
Then your controller can inject the psDirs service and access the directive registry as needed. This prevents brittle scope relationships, and allows the the directive registry service to be used elsewhere, in multiple controllers and sections of your application.
A possible solution is with require:
app.directive('psDIR', [
function() {
return {
...,
require: "ngController",
link: function(scope, elem, attrs, ngCtrl) {
ngCtrl.hook(scope);
}
}
}
]);
And the necessary change to the controller:
app.controller('MainCtrl', ['$scope',
function($scope) {
console.log("init");
$scope.add = function() {
var i;
for( i=0; i < psDirs.length; i++ ) {
console.log(i + " -> " + psDirs[i].md);
}
}
var psDirs = [];
this.hook = function(scope) {
psDirs.push(scope);
};
}
]);
Try it out here: http://plnkr.co/edit/zCZ1TOm3aK8V4piCAY6u?p=preview
If you do decide to go with this solution, I'd suggest implementing the "wrapper" controller in a specialized wrapper directive, so as to avoid situations where ng-controller may be set in some other element in the hierarchy. In this case, just require: "wrapperDirective".
EDIT: The HTML for the wrapper directive case would simply look like:
<div wrapper-directive>
<div class="psDIR"></div>
<div class="psDIR"></div>
<button ng-click="add()">Add</button>
</div>
And the directive itself uses the code of the previous ng-controller:
app.directive('wrapperDirective', function() {
return {
restrict: "A",
// YOU MAY WANT ISOLATED SCOPE, JUST ADD scope: {},
controller: ["$scope", function($scope) {
// SAME CODE AS BEFORE
console.log("init");
$scope.add = function() {
var i;
for( i=0; i < psDirs.length; i++ ) {
console.log(i + " -> " + psDirs[i].md);
}
}
var psDirs = [];
this.hook = function(scope) {
psDirs.push(scope);
};
}]
};
});
And of course change the require configuration:
app.directive('psDIR', [
...
require: "^wrapperDirective",
link: function(scope, elem, attrs, wrapperDirectiveCtrl) {
wrapperDirectiveCtrl.hook(scope);
}
I have a directive, here is the code :
.directive('map', function() {
return {
restrict: 'E',
replace: true,
template: '<div></div>',
link: function($scope, element, attrs) {
var center = new google.maps.LatLng(50.1, 14.4);
$scope.map_options = {
zoom: 14,
center: center,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
// create map
var map = new google.maps.Map(document.getElementById(attrs.id), $scope.map_options);
var dirService= new google.maps.DirectionsService();
var dirRenderer= new google.maps.DirectionsRenderer()
var showDirections = function(dirResult, dirStatus) {
if (dirStatus != google.maps.DirectionsStatus.OK) {
alert('Directions failed: ' + dirStatus);
return;
}
// Show directions
dirRenderer.setMap(map);
//$scope.dirRenderer.setPanel(Demo.dirContainer);
dirRenderer.setDirections(dirResult);
};
// Watch
var updateMap = function(){
dirService.route($scope.dirRequest, showDirections);
};
$scope.$watch('dirRequest.origin', updateMap);
google.maps.event.addListener(map, 'zoom_changed', function() {
$scope.map_options.zoom = map.getZoom();
});
dirService.route($scope.dirRequest, showDirections);
}
}
})
I would like to call updateMap() on a user action. The action button is not on the directive.
What is the best way to call updateMap() from a controller?
If you want to use isolated scopes you can pass a control object using bi-directional binding = of a variable from the controller scope. You can also control also several instances of the same directive on a page with the same control object.
angular.module('directiveControlDemo', [])
.controller('MainCtrl', function($scope) {
$scope.focusinControl = {};
})
.directive('focusin', function factory() {
return {
restrict: 'E',
replace: true,
template: '<div>A:{{internalControl}}</div>',
scope: {
control: '='
},
link: function(scope, element, attrs) {
scope.internalControl = scope.control || {};
scope.internalControl.takenTablets = 0;
scope.internalControl.takeTablet = function() {
scope.internalControl.takenTablets += 1;
}
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="directiveControlDemo">
<div ng-controller="MainCtrl">
<button ng-click="focusinControl.takeTablet()">Call directive function</button>
<p>
<b>In controller scope:</b>
{{focusinControl}}
</p>
<p>
<b>In directive scope:</b>
<focusin control="focusinControl"></focusin>
</p>
<p>
<b>Without control object:</b>
<focusin></focusin>
</p>
</div>
</div>
Assuming that the action button uses the same controller $scope as the directive, just define function updateMap on $scope inside the link function. Your controller can then call that function when the action button is clicked.
<div ng-controller="MyCtrl">
<map></map>
<button ng-click="updateMap()">call updateMap()</button>
</div>
app.directive('map', function() {
return {
restrict: 'E',
replace: true,
template: '<div></div>',
link: function($scope, element, attrs) {
$scope.updateMap = function() {
alert('inside updateMap()');
}
}
}
});
fiddle
As per #FlorianF's comment, if the directive uses an isolated scope, things are more complicated. Here's one way to make it work: add a set-fn attribute to the map directive which will register the directive function with the controller:
<map set-fn="setDirectiveFn(theDirFn)"></map>
<button ng-click="directiveFn()">call directive function</button>
scope: { setFn: '&' },
link: function(scope, element, attrs) {
scope.updateMap = function() {
alert('inside updateMap()');
}
scope.setFn({theDirFn: scope.updateMap});
}
function MyCtrl($scope) {
$scope.setDirectiveFn = function(directiveFn) {
$scope.directiveFn = directiveFn;
};
}
fiddle
Although it might be tempting to expose an object on the isolated scope of a directive to facilitate communicating with it, doing can lead to confusing "spaghetti" code, especially if you need to chain this communication through a couple levels (controller, to directive, to nested directive, etc.)
We originally went down this path but after some more research found that it made more sense and resulted in both more maintainable and readable code to expose events and properties that a directive will use for communication via a service then using $watch on that service's properties in the directive or any other controls that would need to react to those changes for communication.
This abstraction works very nicely with AngularJS's dependency injection framework as you can inject the service into any items that need to react to those events. If you look at the Angular.js file, you'll see that the directives in there also use services and $watch in this manner, they don't expose events over the isolated scope.
Lastly, in the case that you need to communicate between directives that are dependent on one another, I would recommend sharing a controller between those directives as the means of communication.
AngularJS's Wiki for Best Practices also mentions this:
Only use .$broadcast(), .$emit() and .$on() for atomic events
Events that are relevant globally across the entire app (such as a user authenticating or the app closing). If you want events specific to modules, services or widgets you should consider Services, Directive Controllers, or 3rd Party Libs
$scope.$watch() should replace the need for events
Injecting services and calling methods directly is also useful for direct communication
Directives are able to directly communicate with each other through directive-controllers
Building on Oliver's answer - you might not always need to access a directive's inner methods, and in those cases you probably don't want to have to create a blank object and add a control attr to the directive just to prevent it from throwing an error (cannot set property 'takeTablet' of undefined).
You also might want to use the method in other places within the directive.
I would add a check to make sure scope.control exists, and set methods to it in a similar fashion to the revealing module pattern
app.directive('focusin', function factory() {
return {
restrict: 'E',
replace: true,
template: '<div>A:{{control}}</div>',
scope: {
control: '='
},
link : function (scope, element, attrs) {
var takenTablets = 0;
var takeTablet = function() {
takenTablets += 1;
}
if (scope.control) {
scope.control = {
takeTablet: takeTablet
};
}
}
};
});
To be honest, I was not really convinced with any of the answers in this thread. So, here's are my solutions:
Directive Handler(Manager) Approach
This method is agnostic to whether the directive's $scope is a shared one or isolated one
A factory to register the directive instances
angular.module('myModule').factory('MyDirectiveHandler', function() {
var instance_map = {};
var service = {
registerDirective: registerDirective,
getDirective: getDirective,
deregisterDirective: deregisterDirective
};
return service;
function registerDirective(name, ctrl) {
instance_map[name] = ctrl;
}
function getDirective(name) {
return instance_map[name];
}
function deregisterDirective(name) {
instance_map[name] = null;
}
});
The directive code, I usually put all the logic that doesn't deal with DOM inside directive controller. And registering the controller instance inside our handler
angular.module('myModule').directive('myDirective', function(MyDirectiveHandler) {
var directive = {
link: link,
controller: controller
};
return directive;
function link() {
//link fn code
}
function controller($scope, $attrs) {
var name = $attrs.name;
this.updateMap = function() {
//some code
};
MyDirectiveHandler.registerDirective(name, this);
$scope.$on('destroy', function() {
MyDirectiveHandler.deregisterDirective(name);
});
}
})
template code
<div my-directive name="foo"></div>
Access the controller instance using the factory & run the publicly exposed methods
angular.module('myModule').controller('MyController', function(MyDirectiveHandler, $scope) {
$scope.someFn = function() {
MyDirectiveHandler.get('foo').updateMap();
};
});
Angular's approach
Taking a leaf out of angular's book on how they deal with
<form name="my_form"></form>
using $parse and registering controller on $parent scope. This technique doesn't work on isolated $scope directives.
angular.module('myModule').directive('myDirective', function($parse) {
var directive = {
link: link,
controller: controller,
scope: true
};
return directive;
function link() {
//link fn code
}
function controller($scope, $attrs) {
$parse($attrs.name).assign($scope.$parent, this);
this.updateMap = function() {
//some code
};
}
})
Access it inside controller using $scope.foo
angular.module('myModule').controller('MyController', function($scope) {
$scope.someFn = function() {
$scope.foo.updateMap();
};
});
A bit late, but this is a solution with the isolated scope and "events" to call a function in the directive. This solution is inspired by this SO post by satchmorun and adds a module and an API.
//Create module
var MapModule = angular.module('MapModule', []);
//Load dependency dynamically
angular.module('app').requires.push('MapModule');
Create an API to communicate with the directive. The addUpdateEvent adds an event to the event array and updateMap calls every event function.
MapModule.factory('MapApi', function () {
return {
events: [],
addUpdateEvent: function (func) {
this.events.push(func);
},
updateMap: function () {
this.events.forEach(function (func) {
func.call();
});
}
}
});
(Maybe you have to add functionality to remove event.)
In the directive set a reference to the MapAPI and add $scope.updateMap as an event when MapApi.updateMap is called.
app.directive('map', function () {
return {
restrict: 'E',
scope: {},
templateUrl: '....',
controller: function ($scope, $http, $attrs, MapApi) {
$scope.api = MapApi;
$scope.updateMap = function () {
//Update the map
};
//Add event
$scope.api.addUpdateEvent($scope.updateMap);
}
}
});
In the "main" controller add a reference to the MapApi and just call MapApi.updateMap() to update the map.
app.controller('mainController', function ($scope, MapApi) {
$scope.updateMapButtonClick = function() {
MapApi.updateMap();
};
}
You can specify a DOM attribute that can be used to allow the directive to define a function on the parent scope. The parent scope can then call this method like any other. Here's a plunker. And below is the relevant code.
clearfn is an attribute on the directive element into which the parent scope can pass a scope property which the directive can then set to a function that accomplish's the desired behavior.
<!DOCTYPE html>
<html ng-app="myapp">
<head>
<script data-require="angular.js#*" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<style>
my-box{
display:block;
border:solid 1px #aaa;
min-width:50px;
min-height:50px;
padding:.5em;
margin:1em;
outline:0px;
box-shadow:inset 0px 0px .4em #aaa;
}
</style>
</head>
<body ng-controller="mycontroller">
<h1>Call method on directive</h1>
<button ng-click="clear()">Clear</button>
<my-box clearfn="clear" contentEditable=true></my-box>
<script>
var app = angular.module('myapp', []);
app.controller('mycontroller', function($scope){
});
app.directive('myBox', function(){
return {
restrict: 'E',
scope: {
clearFn: '=clearfn'
},
template: '',
link: function(scope, element, attrs){
element.html('Hello World!');
scope.clearFn = function(){
element.html('');
};
}
}
});
</script>
</body>
</html>
Just use scope.$parent to associate function called to directive function
angular.module('myApp', [])
.controller('MyCtrl',['$scope',function($scope) {
}])
.directive('mydirective',function(){
function link(scope, el, attr){
//use scope.$parent to associate the function called to directive function
scope.$parent.myfunction = function directivefunction(parameter){
//do something
}
}
return {
link: link,
restrict: 'E'
};
});
in HTML
<div ng-controller="MyCtrl">
<mydirective></mydirective>
<button ng-click="myfunction(parameter)">call()</button>
</div>
You can tell the method name to directive to define which you want to call from controller but without isolate scope,
angular.module("app", [])
.directive("palyer", [
function() {
return {
restrict: "A",
template:'<div class="player"><span ng-bind="text"></span></div>',
link: function($scope, element, attr) {
if (attr.toPlay) {
$scope[attr.toPlay] = function(name) {
$scope.text = name + " playing...";
}
}
}
};
}
])
.controller("playerController", ["$scope",
function($scope) {
$scope.clickPlay = function() {
$scope.play('AR Song');
};
}
]);
.player{
border:1px solid;
padding: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="playerController">
<p>Click play button to play
<p>
<p palyer="" to-play="play"></p>
<button ng-click="clickPlay()">Play</button>
</div>
</div>
TESTED
Hope this helps someone.
My simple approach (Think tags as your original code)
<html>
<div ng-click="myfuncion">
<my-dir callfunction="myfunction">
</html>
<directive "my-dir">
callfunction:"=callfunction"
link : function(scope,element,attr) {
scope.callfunction = function() {
/// your code
}
}
</directive>
Maybe this is not the best choice, but you can do angular.element("#element").isolateScope() or $("#element").isolateScope() to access the scope and/or the controller of your directive.
How to get a directive's controller in a page controller:
write a custom directive to get the reference to the directive controller from the DOM element:
angular.module('myApp')
.directive('controller', controller);
controller.$inject = ['$parse'];
function controller($parse) {
var directive = {
restrict: 'A',
link: linkFunction
};
return directive;
function linkFunction(scope, el, attrs) {
var directiveName = attrs.$normalize(el.prop("tagName").toLowerCase());
var directiveController = el.controller(directiveName);
var model = $parse(attrs.controller);
model.assign(scope, directiveController);
}
}
use it in the page controller's html:
<my-directive controller="vm.myDirectiveController"></my-directive>
Use the directive controller in the page controller:
vm.myDirectiveController.callSomeMethod();
Note: the given solution works only for element directives' controllers (tag name is used to get the name of the wanted directive).
Below solution will be useful when, you are having controllers (both parent and directive (isolated)) in 'controller As' format
someone might find this useful,
directive :
var directive = {
link: link,
restrict: 'E',
replace: true,
scope: {
clearFilters: '='
},
templateUrl: "/temp.html",
bindToController: true,
controller: ProjectCustomAttributesController,
controllerAs: 'vmd'
};
return directive;
function link(scope, element, attrs) {
scope.vmd.clearFilters = scope.vmd.SetFitlersToDefaultValue;
}
}
directive Controller :
function DirectiveController($location, dbConnection, uiUtility) {
vmd.SetFitlersToDefaultValue = SetFitlersToDefaultValue;
function SetFitlersToDefaultValue() {
//your logic
}
}
html code :
<Test-directive clear-filters="vm.ClearFilters"></Test-directive>
<a class="pull-right" style="cursor: pointer" ng-click="vm.ClearFilters()"><u>Clear</u></a>
//this button is from parent controller which will call directive controller function
I am trying to dynamically add different directives in an ng-repeat however the output is not being interpreted as directives.
I've added a simple example here: http://plnkr.co/edit/6pREpoqvmcnJJWzhZZKq
Controller:
$scope.colors = [{name:"red"}, {name: "blue"}, {name:"yellow"}];
Directive:
app.directive("red", function () {
return {
restrict: 'C',
template: "RED directive"
}
});
Html:
<ul>
<li ng-repeat="color in colors">
<span class="{{color.name}}"></span>
</li>
</ul>
How do I make angular pick up the directive specified in the class that is output via ng-repeat?
I know this is an old question, but google brought me here, and I didn't like the answers here... They seemed really complicated for something that should be simple. So I created this directive:
***** NEW CONTENT *****
I've since made this directive more generic, supporting a parsed (the typical angular value) "attributes" attribute.
/**
* Author: Eric Ferreira <http://stackoverflow.com/users/2954747/eric-ferreira> ©2016
*
* This directive takes an attribute object or string and adds it to the element
* before compilation is done. It doesn't remove any attributes, so all
* pre-added attributes will remain.
*
* #param {Object<String, String>?} attributes - object of attributes and values
*/
.directive('attributes', function attributesDirective($compile, $parse) {
'use strict';
return {
priority: 999,
terminal: true,
restrict: 'A',
compile: function attributesCompile() {
return function attributesLink($scope, element, attributes) {
function parseAttr(key, value) {
function convertToDashes(match) {
return match[0] + '-' + match[1].toLowerCase();
}
attributes.$set(key.replace(/([a-z][A-Z])/g, convertToDashes), value !== undefined && value !== null ? value : '');
}
var passedAttributes = $parse(attributes.attributes)($scope);
if (passedAttributes !== null && passedAttributes !== undefined) {
if (typeof passedAttributes === 'object') {
for (var subkey in passedAttributes) {
parseAttr(subkey, passedAttributes[subkey]);
}
} else if (typeof passedAttributes === 'string') {
parseAttr(passedAttributes, null);
}
}
$compile(element, null, 999)($scope);
};
}
};
});
For the OP's use case, you could do:
<li ng-repeat="color in colors">
<span attributes="{'class': color.name}"></span>
</li>
Or to use it as an attribute directive:
<li ng-repeat="color in colors">
<span attributes="color.name"></span>
</li>
***** END NEW CONTENT ******
/**
* Author: Eric Ferreira <http://stackoverflow.com/users/2954747/eric-ferreira> ©2015
*
* This directive will simply take a string directive name and do a simple compilation.
* For anything more complex, more work is needed.
*/
angular.module('attributes', [])
.directive('directive', function($compile, $interpolate) {
return {
template: '',
link: function($scope, element, attributes) {
element.append($compile('<div ' + attributes.directive + '></div>')($scope));
}
};
})
;
For the specific case in this question, one can just rewrite the directive a bit to make it apply the directive to a span by class, as so:
angular.module('attributes', [])
.directive('directive', function($compile, $interpolate) {
return {
template: '',
link: function($scope, element, attributes) {
element.replaceWith($compile('<span class=\"' + attributes.directive + '\"></span>')($scope));
}
};
})
;
Then you can use this anywhere and select a directive by name dynamically. Use it like so:
<li ng-repeat="color in colors">
<span directive="{{color.name}}"></span>
</li>
I purposely kept this directive simple and straightforward. You may (and probably will) have to reword it to fit your needs.
i faced with the same problem in one of my projects and you can see how i solve this problem on jsfiddle
HTML:
<div class="page-wrapper" ng-controller="mainCtrl">
<div class="page">
<h3>Page</h3>
<ul>
<li ng-repeat="widget in widgets"><div proxy="widget" proxy-value="{{widget}}"></div></li>
</ul>
</div>
JS:
var app = angular.module('app',[]);
app.controller('mainCtrl', ['$scope', '$q', 'widgetAPI', function($scope, $q, widgetAPI) {
$scope.widgets = [];
widgetAPI.get().then(
function(data) {
$scope.widgets = data;
},
function(err) {
console.log("error", err);
}
);}])
.service('widgetAPI', ['$q', function($q) {
var api = {};
api.get = function() {
//here will be $http in real app
return $q.when(
[
{
component: 'wgtitle',
title: "Hello world",
color: '#DB1F1F',
backgroundColor: '#c1c1c1',
fontSize: '32px'
},
{
component: 'wgimage',
src: "http://cs425622.vk.me/v425622209/79c5/JgEUtAic8QA.jpg",
width: '100px'
},
{
component: 'wgimage',
src: "http://cs425622.vk.me/v425622209/79cf/S5F71ZMh8d0.jpg",
width: '400px'
}
]
);
};
return api;}])
.directive('proxy', ['$parse', '$injector', '$compile', function ($parse, $injector, $compile) {
return {
replace: true,
link: function (scope, element, attrs) {
var nameGetter = $parse(attrs.proxy);
var name = nameGetter(scope);
var value = undefined;
if (attrs.proxyValue) {
var valueGetter = $parse(attrs.proxyValue);
value = valueGetter(scope);
}
var directive = $injector.get(name.component + 'Directive')[0];
if (value !== undefined) {
attrs[name.component] = value;
}
var a = $compile(directive.template)(scope);
element.replaceWith(a);
}
}}])
.directive('wgtitle', function() {
return {
restrict: 'A',
scope: true,
replace: true,
template: '<h1 style="color:{{widget.color}}; font-size:{{widget.fontSize}}; background:{{widget.backgroundColor}}" >{{widget.title}}</h1>',
link: function(scope, element, attrs) {
}
}})
.directive('wgimage', function() {
return {
restrict: 'A',
scope: true,
replace: true,
template: '<img style="width:{{widget.width}}" src="{{widget.src}}"/>',
link: function(scope, element, attrs) {
}
}});
I hope it will usefull.
I don't think you'll be able to just assign the directive as a class name - you would need to run this through $compile again, which would be heading down the path towards recursion errors.
One possible solution is outlined at: AngularJS - how to have a directive with a dynamic sub-directive
If it works for your use case, you can use templates instead:
<div ng-repeat='template in inner' ng-include='template'></div>