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
Related
I have created an directive with isolated scope named <student>, everything is working fine am able to pass different data to my directive using isolated scope "=". But am not able to save the changes in the data.
code:
<div ng-app="mainApp" >
<div layout="column" flex ng-controller="StudentController">
<student operation="productadd"></student>
<button type="submit" ng-click="submit()">click</button>
<div style="float:left;background:#ccc;">
<!--<student operation="productmodify"></student>-->
</div>
</div>
</div>
app.js
var mainApp = angular.module("mainApp", ['ngMaterial']);
mainApp.controller('StudentController', ['$scope', '$mdDialog', function ($scope, $mdDialog) {
$scope.productadd = {};
$scope.productadd.colors = ['#a55466', '#8298a6', '#586a75', '#af1e2c', '#b3995d', '#7fc7af',
'#afb4bb', '#b9adad', '#899a9f', '#7d7d7d', '#525252', '#636c6f',
'#347d7b', '#b1b1b1', '#53748c', '#927f92', '#695769', '#a07c7c', '#6f7d86',
'#94b1c4', '#24122a', '#6c6169', '#6d5656', '#d19898', '#ac7c7c', '#aaaaaa',
'#bbcccc', '#999999', '#ccbbbb', '#779999', '#cdc1c5', '#8b8386'];
$scope.productadd.uomList = ['oz1', 'mm2', 'meter3'];
$scope.productadd.merchList = ['hand', 'tray', 'case', 'basket'];
$scope.productadd.nameList = ['Unit', 'Case', 'TRAY', 'Pallet 4 layers', 'Half Pallet 3 layers', ' Half Pallet 4 layers', ' Cut Case 24', 'Pallet 3 layers', 'Cut Case 12'];
$scope.productadd.orientationList = ['Front End', 'Front Side', 'Side End', 'Side Front', 'End Front', 'End Side'];
$scope.productadd.distributerTypeList = ['WHS', 'DSP', 'DSB', 'DSA', 'DSS', 'DSD'];
$scope.productmodify = {};
$scope.productmodify.colors = ['#a55466', '#8298a6'];
$scope.productmodify.uomList = ['oz', 'mm', 'meter'];
$scope.productmodify.merchList = ['hand', 'tray', 'case', 'basket'];
$scope.productmodify.nameList = ['Unit', 'Case', 'TRAY', 'Pallet 4 layers', 'Half Pallet 3 layers', ' Half Pallet 4 layers', ' Cut Case 24', 'Pallet 3 layers', 'Cut Case 12'];
$scope.productmodify.orientationList = ['Front End', 'Front Side', 'Side End', 'Side Front', 'End Front', 'End Side'];
$scope.productmodify.distributerTypeList = ['WHS', 'DSP', 'DSB', 'DSA', 'DSS', 'DSD'];
$scope.submit = function () {
console.log($scope.product);
}
}]);
mainApp.directive('student', function () {
return {
restrict: 'E',
templateUrl:'temp.html',
scope: {
productedit: "=operation"
},
}
});
examplecode
I have added an submit and am trying to save my model named product. But am getting undefined.
Please guide me how to solve this problem.
The way you have currently have your html structure, you have placed your submit form button outside the directive template. For solving this issue, basically you are just passing the value of dropdowns. So i'd suggest you to pass the value of product object which would be initially {} & that will be modified by the directive template.
HTML
<student operation="productadd" product="product"></student>
Controller
$scope.product = {}; //initially blank, filled up by directive.
Directive
mainApp.directive('student', function() {
return {
restrict: 'E',
templateUrl: 'temp.html',
scope: {
productedit: "=operation",
product: '=' //pass product object here
},
}
});
Demo Plunkr
$scope.submit = function () {
console.log($scope.product);
};
It logs an undefined scope property so it will never save any changes anywhere
mainApp.directive('student', function () {
return {
restrict: 'E',
templateUrl: 'temp.html',
scope: {
productedit: "=operation"
},
}
});
Should not have , after scope property (it's the last property on that object),
Where is your controller/link function?
You should move productadd to a service.
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
};
});
What is the preferred way to link/bind two directives together? I have a controller with two directives, first directive is a select element, after selecting option, second directive should process selected item value.
App code:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function() {
var sharedData = { selectedId: '' };
var vm = this;
vm.sharedData = sharedData;
});
app.directive('directiveA', ['$compile', function($compile) {
return {
restrict: 'E',
scope: {
selectedId: '='
},
template: '<select data-ng-model="vm.sharedData.selectedId" data-ng-options="currentSelect.Id as currentSelect.Name for currentSelect in vm.sharedData.availableSelects track by currentSelect.Id"><option value="">Select option</option></select><p>Directive A, selected ID: {{vm.sharedData.selectedId}}</p>',
bindToController: true,
controllerAs: 'vm',
controller: function() {
vm = this;
vm.sharedData = {
availableSelects: [
{Id:1, Name: 'Option 1'},
{Id:2, Name: 'Option 2'},
{Id:3, Name: 'Option 3'},
{Id:4, Name: 'Option 4'}
]
}
vm.logMessage = logMessage;
function logMessage(selectedId) {
console.log('directiveA: ' + selectedId);
}
},
link: function($scope, elem, attr, ctrl) {
attr.$observe('selectedId', function(selectedId) {
ctrl.logMessage(selectedId);
});
}
};
}]);
app.directive('directiveB', ['$compile', function($compile) {
return {
restrict: 'E',
scope: {
selectedId: '='
},
template: '<p>Directive B, selected ID: {{vm.sharedData.selectedId}}</p>',
bindToController: true,
controllerAs: 'vm',
controller: function() {
vm = this;
vm.logMessage = logMessage;
function logMessage(selectedId) {
console.log('directiveB: ' + selectedId);
}
},
link: function($scope, elem, attr, ctrl) {
attr.$observe('selectedId', function(selectedId) {
ctrl.logMessage(selectedId);
});
}
};
}]);
HTML code:
<!DOCTYPE html>
<html data-ng-app="plunker" data-ng-strict-di>
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link href="style.css" rel="stylesheet" />
<script data-semver="1.4.1" src="https://code.angularjs.org/1.4.1/angular.js" data-require="angular.js#1.4.x"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl as vm">
<p>MainCtrl, selected ID: {{vm.sharedData.selectedId}}</p>
<directive-a data-selected-id="vm.sharedData.selectedId"></directive-a>
<directive-b data-selected-id="vm.sharedData.selectedId"></directive-b>
</body>
</html>
Here is a Plunker example:
http://plnkr.co/edit/KVMGb8uAjUwD9eOsv72z?p=preview
What I'm doing wrong?
Best Regards,
The key issue revolves around your use of isolated scopes:
scope: {
selectedId: '='
},
With controllerAs binding:
controllerAs: 'vm',
What this essentially does, to put it basically, is it places the view model onto the directives scope, accessed through the alias you assign in the controllerAs. So basically in your html when you go:
<directive-a data-selected-id="vm.sharedData.selectedId"></directive-a>
You are actually accessing the directive-a view model, NOT the MainCtrl view model. BECAUSE you set directive-a as having an isolate scope... which is a new scope, isolated from the MainCtrl.
What you need to do is more along the following lines:
http://plnkr.co/edit/wU709MPdqn5m2fF8gX23?p=preview
EDIT
TLDR: I would recommend having unique view model aliases (controllerAs) when working with isolated scopes to properly reflect the fact that they are not the same view model.
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>
I would like to create "components" dynamically, based on data received from my backend. The goal is to display parts of my application, without using server side templating : instead of displaying components server-side, the server sends JSON data containing which components should be displayed.
Here is what I've got so far :
var module = angular.module('testApp', []);
module.controller('Ctrl1', ['$scope', function ($scope) {
$scope.test = "test 1";
}])
.controller('Ctrl2', ['$scope', function ($scope) {
$scope.test = "test 2";
}])
.controller('ComponentsController', ['$scope', function ($scope) {
// this is JSON returned by backend
$scope.components = [{
name: "Wd1",
controller: "Ctrl1",
}, {
name: "Wd2",
controller: "Ctrl2",
}];
$scope.test = "test";
}]);
And my view :
<div ng-app="testApp">
<div ng-controller="ComponentsController">
<div ng-repeat="component in components">
<p>{{component.name}} - {{component.controller}}</p>
</div>
<div ng-repeat="component in components">
<p ng-controller="component.controller">{{test}}</p>
</div>
</div>
</div>
However, I get the following error :
Error: Argument 'component.controller' is not a function, got string
I tried to write a directive, assigning controller names during compile, but as it is done during compile, it doesn't work with binding...
Here is a fiddle : http://jsfiddle.net/mathieu/bTQA5/
Just do it controllers name, not string:
function ComponentsController($scope) {
$scope.components = [{
name: "Wd1",
controller: Ctrl1,
}, {
name: "Wd2",
controller: Ctrl2,
}];
$scope.test = "test";
}
Bind the functions to the data in scope:
function ComponentsController($scope) {
$scope.Ctrl1 = function () {
$scope.test = "test 1";
}
$scope.Ctrl2 = function () {
$scope.test = "test 2";
}
$scope.components = [{
name: "Wd1",
controller: $scope.Ctrl1
}, {
name: "Wd2",
controller: $scope.Ctrl2
}];
$scope.test = "test";
}
And here's the updated jsFiddle.
Note that there's still a logical issue here; The {{ test }} binding will be evaluated as soon as the controller does, so the resulted text (on every binding) will be the last evaluation made, i.e. "Test 2", in our case.
You may want to tie logic to handlers instead, e.g. ng-click, to be executed on demand:
<div ng-repeat="component in components">
<button ng-controller="component.controller"
ng-click="component.controller()">
Call {{ component.name }} controller
</button>
</div>
And here it is demonstrated live as well.
This answer is based on the first version of this question, which included the following code:
function ComponentsController($scope) {
$scope.components = [{
name: "Wd1",
controller: "Ctrl1",
}, {
name: "Wd2",
controller: "Ctrl2",
}];
$scope.test = "test";
}
function Ctrl1($scope) {
$scope.test = "test 1";
}
function Ctrl2($scope) {
$scope.test = "test 2";
}
You can use the following directive which will insert a controller based on a name:
var module = angular.module('testApp', []);
module
.directive('dynamicController', ['$controller', function($controller) {
return {
restrict: 'A',
scope: true,
link: function (scope, element, attrs) {
var locals = {
$scope: scope,
$element: element,
$attrs: attrs
};
element.data('$Controller', $controller(scope.$eval(attrs.dynamicController), locals));
}
};
}
])
var module = angular.module('testApp', []);
module
.directive('dynamicController', ['$controller',
function($controller) {
return {
restrict: 'A',
scope: true,
link: function(scope, element, attrs) {
var locals = {
$scope: scope,
$element: element,
$attrs: attrs
};
element.data('$Controller', $controller(scope.$eval(attrs.dynamicController), locals));
}
};
}
])
.controller('Ctrl1', ['$scope',
function($scope) {
$scope.test = "test 1";
}
])
.controller('Ctrl2', ['$scope',
function($scope) {
$scope.test = "test 2";
}
])
.controller('ComponentsController', ['$scope',
function($scope) {
$scope.components = [{
name: "Wd1",
controller: "Ctrl1",
}, {
name: "Wd2",
controller: "Ctrl2",
}];
$scope.test = "test";
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.2/angular.min.js"></script>
<div ng-app="testApp">
<div ng-controller="ComponentsController">
<div ng-repeat="component in components" dynamic-controller="component.controller">
<p><span>{{test}}</span>
</p>
</div>
</div>
</div>