I am building a dropdown menu directive which allows you to optionally attach a function to each item in the list. I know how to pass one function per attribute into the directive, but I'm hoping that there is a way to pass multiple functions.
<dropdown items="['item1', 'item2']" actions="['action1()', 'action2()']"></dropdown>
or better yet:
<dropdown items="[{'item1':action1()}, {'item2':action2()}]"></dropdown>
which could be used to generate:
<dropdown items="['item1', 'item2']" actions="['action1()', 'action2()']">
<a ng-click="action1()">item1</a>
<a ng-click="action2()">item2</a>
</dropdown>
You can use the = object notation for your scope in accepting an array of objects with properties that you can assign to your directive.
DEMO
Controller
.controller('Ctrl', function($scope) {
var action = function() {
window.alert(this.label);
};
$scope.items = [{
label: 'Item 1',
action: action
}, {
label: 'Item 2',
action: action
}, {
label: 'Item 3',
action: action
}, {
label: 'Item 4',
action: action
}];
})
Directive
.directive('dropdown', function() {
return {
restrict: 'E',
scope: {
items: '='
},
template:
'<div ng-repeat="item in items track by $index" ng-click="item.action()">' +
'<a ng-bind="item.label"></a>' +
'</div>'
};
});
index.html
<body ng-controller="Ctrl">
<dropdown items="items"></dropdown>
</body>
to pass any form of function to a directive that does same thing in thesense ike a call back from the directive to execute the function , the method should be as below
First of all use the return scope to contain the functionName : '&' as it is used in passing functions
Then returning it back should look like this from ur template ng-click='functionName({params:values[,params2:value2]})'
as the above would send the param as argument to the calling controller calling the directive
var app = angular.module('testApp', []);
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.items=['value1','value2','value3'];
$scope.items2=['value12','value22','value32'];
$scope.clicked='';
$scope.alert=function(val){
$scope.clicked=val;
}
$scope.alerti=function(val){
$scope.clicked=val+"-Second";
}
});
app.directive('direct', function(){
return {
restrict: 'E',
scope : {
actionTest:'&',
tests:'='
},
// controller: 'ctrl',
template: '<ul><li ng-repeat="test in tests" ng-click="actionTest({val:test})">{{test}} </li></ul>'
}
});
/*
app.controller('ctrl', function($scope){
});*/
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="testApp">
<div ng-controller="MainCtrl">
<p>Hello {{name}}!</p>
<p>CLicked : {{clicked}}</p>
<direct tests='items' action-test='alert(val)'></direct>
<!--with a different action function-->
<direct tests='items2' action-test='alerti(val)'></direct>
</div>
</div>
Related
I have the following code, which is trying to add a variable to the $rootScope through a directive and trying to get that value and show it inside a controller element.
But when I click the links inside the directive, it doesn't display the new $rootScope variable.
https://plnkr.co/edit/P7Lq7h13RmXryFC0uBMi?p=info
var mod = angular.module('myApp', []);
mod.directive('mainMenu', menuDirec);
function menuDirec($rootScope){
return {
templateUrl: 'menu.html',
restrict: 'A',
controller: menuControl,
controllerAs: 'vm'
};
function menuControl(){
var vm = this;
vm.menu = {
'one': 'Link 1',
'two': 'Link 2',
'three': 'Link 3',
'four': 'Link 4'
};
vm.clickMenu = function(slug){
$rootScope.active = slug;
console.log($rootScope);
console.log(slug);
}
}
}
mod.controller('content', contentControl);
function contentControl($scope, $rootScope){
$scope.title = $rootScope.active;
}
Change the HTML to access the title as a function
<div ng-controller="content">
<h1>{{ title() }}</h1>
</div>
In your JS make it a function
mod.controller('content', contentControl);
function contentControl($scope, $rootScope){
$scope.title = function () {
return $scope.active;
};
}
Then on each digest cycle, the $watch attached to the {{title()}} will execute the function and update the DOM as the value of $scope.active changes.
The UPDATE on PLNKR.
Best practice
Avoid cluttering $rootScope with variables. Instead use isolate scope and two-way binding. For more information, see AngularJS Comprehensive Directive API - scope.
As example
mod.directive('mainMenu', menuDirec);
function menuDirec(){
return {
templateUrl: 'menu.html',
restrict: 'A',
controller: menuControl,
controllerAs: 'vm',
scope: { active: '=' },
bindToController: true
};
function menuControl(){
var vm = this;
vm.menu = {
'one': 'Link 1',
'two': 'Link 2',
'three': 'Link 3',
'four': 'Link 4'
};
vm.clickMenu = function(slug){
vm.active = slug;
console.log(slug);
}
}
}
HTML
<body ng-app="myApp">
<nav main-menu active="title"></nav>
<div ng-controller="content">
<h1>{{ title }}</h1>
</div>
</body>
The DEMO on PLNKR
I have a directive that controls a personalized multiselect. Sometimes from the main controller I'd like to clear all multiselects. I have the multiselect value filling a "filter" bidirectional variable, and I am able to remove content from there, but when doing that I also have to change some styles and other content. In other words: I have to call a method belonging to the directive from a button belonging to the controller. Is that even posible with this data structure?:
(By the way, I found other questions and examples but their directives didn't have their own scope.)
function MultiselectDirective($http, $sce) {
return {
restrict: 'E',
replace: true,
templateUrl: 'temp.html',
scope: {
filter: "=",
name: "#",
url: "#"
},
link: function(scope, element, attrs){
//do stuff
scope.function_i_need_to_call = function(){
//updates directtive template styles
}
}
}
}
The best solution and the angular way - use event.
Live example on jsfiddle.
angular.module('ExampleApp', [])
.controller('ExampleOneController', function($scope) {
$scope.raise = function(val){
$scope.$broadcast('raise.event',val);
};
})
.controller('ExampleTwoController', function($scope) {
$scope.raise = function(val){
$scope.$broadcast('raise.event',val);
};
})
.directive('simple', function() {
return {
restrict: 'A',
scope: {
},
link: function(scope) {
scope.$on('raise.event',function(event,val){
console.log('i`m from '+val);
});
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="ExampleApp">
<div ng-controller="ExampleOneController">
<h3>
ExampleOneController
</h3>
<form name="ExampleForm" id="ExampleForm">
<button ng-click="raise(1)" simple>
Raise 1
</button>
</form>
</div>
<div ng-controller="ExampleTwoController">
<h3>
ExampleTwoController
</h3>
<form name="ExampleForm" id="ExampleForm">
<button ng-click="raise(2)" simple>
Raise 2
</button>
</form>
</div>
</body>
I think better solution to link from controller to directives is this one:
// in directive
return {
scope: {
controller: "=",
},
controller: function($scope){
$scope.mode = $scope.controller.mode;
$scope.controller.function_i_need_to_call = function(){}
$scope.controller.currentState = state;
}
}
// in controller
function testCtrl($scope){
// config directive
$scope.multiselectDirectiveController = {
mode: 'test',
};
// call directive methods
$scope.multiselectDirectiveController.function_i_need_to_call();
// get directive property
$scope.multiselectDirectiveController.currentState;
}
// in template
<Multiselect-directive controller="multiselectDirectiveController"></Multiselect-directive>
OK, so I have a directive which takes attributes and reads it (and writes it out).
Here is the plunker: http://embed.plnkr.co/IkKPLahPc9yqeHWEQUG3/
I think it's because of the controller: ctrl inside main-directive.js which has nothing whereas the actual action is happening inside the isolated directive's controller controller.
Here is the main-directive.js:
var app = angular.module('testapp.directive.main', ['main']);
app.directive('myCustomer', function() {
var controller = ['$scope', function($scope) {
$scope.dan = { 'name': 'Dan', 'nationality': 'ESP' };
// scope from here obv...
}];
var template = 'Getting attribute value of =getInfo... {{getInfo.name}} from {{getInfo.nationality}}';
return {
restrict: 'E',
controller: controller,
scope: {
getInfo: "=info"
},
template: template
};
});
app.controller('ctrl', function($scope) {
})
and here's my template:
<div ng-controller="ctrl">
<my-customer info="dan">
</my-customer>
</div>
Why is my directive not reading the attribute of info?
You're right, the $scope.dan object needs to be in the ‘ctrl’ controller scope and pulled out of the isolate directives controller scope.
app.controller('ctrl', function($scope) {
$scope.dan = { 'name': 'Dan', 'nationality': 'ESP' };
})
This is applicable to the method of two-way data binding that you have set up for getInfo used by "=info"
The way that is coded, it is expecting the ctrl controller to have a property called "dan" on its scope. If you are just passing in the string 'dan', you want to change your directive to use # instead of =
app.directive('myCustomer', function () {
var controller = ['$scope', function ($scope) {
$scope.dan = {'name': 'Dan', 'nationality': 'ESP'};
// scope from here obv...
}];
var template = 'Getting attribute value of =getInfo... {{getInfo.name}} from {{getInfo.nationality}}';
return {
restrict: 'E',
controller: controller,
scope: {
getInfo: "#info" //<--NOTE THE CHANGE HERE
},
template: template
};
});
I have trouble getting my transcluding directive to work. I want to do the following: Create a directive that outputs a list where the content of each item is defined by transcluded content. E.g:
<op-list items="myItems">
<span class="item">{{item.title}}</span>
</op-list>
so I would use ng-repeat inside op-list's template and must be able to access the scope created by ng-repeat inside the transcluded content.
This is what I've done so far:
var myApp = angular.module('myApp', []);
myApp.controller('MyCtrl', ['$scope', function ($scope) {
$scope.myModel = {
name: 'Superhero',
items: [{
title: 'item 1'
}, {
title: 'item 2'
}]
};
}]);
myApp.directive('opList', function () {
return {
template: '<div>' +
'<div>items ({{items.length}}):</div>' +
'<div ng-transclude ng-repeat="item in items"></div>' +
'</div>',
restrict: 'E',
replace: true,
transclude: true,
scope: {
items: '='
}
};
});
<html ng-app="myApp">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-controller="MyCtrl">
<div>Hello, {{myModel.name}}!</div>
<op-list items="myModel.items">
<span>title: {{item.title}}|{{$scope}}|{{scope}}|{{items}}</span>
</op-list>
</div>
</html>
Check if this works:
myApp.directive('opList', ['$scope', function ($scope) {
return {
template: '<div>' +
'<div>items ({{model.items.length}}):</div>' +
'<ng-transclude ng-repeat="item in model.items"></ng-transclude>' +
'</div>',
restrict: 'E',
replace: true,
transclude: true,
scope: {
items: '='
}
};
}]);
If it doesn't then try to inspect $scope in console and see if you're able to access your model.
What is the most Angular recommended way to use a dynamic tag name in a template?
I have a drop-down containing h1-h6 tags. A user can choose any of these and the content will change to being wrapped by the chosen header tag (which is stored on the $scope). The content is bound to the model i.e. within {{ }}.
To persist the binding I can change the markup and use $compile. However, this does not work because it gets appended (obviously) before Angular replaces the {{ }} with model values. It's h3 on page load.
Example:
<div id="root">
<h3 id="elementToReplace">{{ modelData }}</h3>
</div>
When re-compiling I have tried using a string as follows:
<{{ tag }} id="elementToReplace">{{ modelData }}</{{ tag }}>
Any ideas?
Demo Plunker Here
Define a scope variable named 'tag' and bind it to both your select list and custom directive.
HTML:
<select ng-model="tag" ng-init="tag='H1'">
<option ng-value="H1">H1</option>
<option ng-value="H2">H2</option>
<option ng-value="H3">H3</option>
<option ng-value="H4">H4</option>
<option ng-value="H5">H5</option>
</select>
<tag tag-name="tag">Hey There</tag>
Next, pass the tag scope model into your directive using two-way model binding:
var app = angular.module('app',[]);
app.directive('tag', function($interpolate) {
return {
restrict: 'E',
scope: {
tagName: '='
},
link: function($scope, $element, $attr) {
var content = $element.html();
$scope.$watch('tagName', function(newVal) {
$element.contents().remove();
var tag = $interpolate('<{{tagName}}>{{content}}</{{tagName}}>')
({tagName: $scope.tagName, content: content});
var e = angular.element(tag);
$element.append(e);
});
}
}
});
Notice that in the custom directive, we are using the $interpolate service to generate the HTML element based on the Tag that was selected in the select list. A $watch function is used to watch for changes to the tag model, and when it changes, the new element is appended to the DOM.
Here is one I knocked up, even though you said you didn't want it ;)
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<script src="../lib/jquery.js"></script>
<script src="../lib/angular.js"></script>
<script>
var app = angular.module('app', []);
app.controller('ctrl', ['$scope', function ($scope) {
$scope.modelData = "<h1>Model Data</h1>" +
"<p id='replace'>This is the text inside the changing tags!</p>";
$scope.tags = ["h1", "h2", "h3", "h4", "p"];
$scope.selectedTag = "p";
}]);
app.directive("tagSelector", function(){
return {
resrict: 'A',
scope: {
modelData: '#',
selectedTag: '#'
},
link: function(scope, el, attrs){
scope.$watch("selectedTag", updateText);
el.prepend(scope.modelData);
function updateText(){
var tagStart = "<" + scope.selectedTag + " id='replace'>";
var tagEnd = "</" + scope.selectedTag + ">";
$("#replace").replaceWith(tagStart + $("#replace").html() + tagEnd);
}
}
}
});
</script>
</head>
<body ng-app="app">
<div ng-controller="ctrl">
<select ng-model="selectedTag" ng-options="tag for tag in tags"></select>
<div tag-selector selected-tag="{{selectedTag}}" model-data="{{modelData}}"></div>
</div>
</body>
</html>
More kosher version. Work good with ng-repeat and nested directives.
Working example here.
angular.module('myApp', [])
.directive('tag', function($interpolate, $compile) {
return {
priority: 500,
restrict: 'AE',
terminal: true,
scope: {
tagName: '='
},
link: function($scope, $element) {
$scope.$on('$destroy', function(){
$scope.$destroy();
$element.empty();
});
$scope.$parent.$watch($scope.tagName, function(value) {
$compile($element.contents())($scope.$parent, function(compiled) {
$element.contents().detach();
var tagName = value || 'div';
var root = angular.element(
$element[0].outerHTML
.replace(/^<\w+/, '<' + tagName)
.replace(/\w+>$/, tagName + '>'));
root.append(compiled);
$element.replaceWith(root);
$element = root;
});
});
}
}
})
.controller('MyCtrl', function($scope) {
$scope.items = [{
name: 'One',
tagName: 'a'
}, {
name: 'Two',
tagName: 'span'
}, {
name: 'Three',
}, {
name: 'Four',
}];
});
Usages:
<div ng-app="myApp">
<div ng-controller="MyCtrl">
<tag class="item" tag-name="'item.tagName'" ng-repeat="item in items">
{{item.name}}
</tag>
</div>
</div>