I have a directive. I want to use an attribute value of that directive in my directive's controller. I attempt to do this by binding the attribute value to my isolate scope. However I run into a problem in that the attribute value seems not to be immediately bound to the isolate scope.
Consider the following code:
angular.module('startup.directives.decision', [])
.directive('decisionMaker', [function () {
return{
restrict: 'E',
templateUrl: 'views/directives/decision.html',
scope: {
decisionType:"#",
},
controller: ['$scope', 'Decisions', function ($scope, Decisions){
//this prints undefined
console.log($scope.decisionType);
//this prints the proper value when called a couple seconds after page load
$scope.getDecisionType = function(){
console.log($scope.decisionType);
};
//this is my motivation for wanting $scope.decisionType to be bound immediately
if($scope.decisionType==='hire'){
//should do some stuff here
}
}]
};
}]);
I call my directive like this:
<decision-maker decision-type="investment"></decision-maker>
<decision-maker decision-type="hire"></decision-maker>
You're going to want to use the $observe function. See the Attributes section of the Directives documentation.
So, something like this:
controller: ['$scope', '$attrs', 'Decisions', function ($scope, $attrs, Decisions){
//this prints undefined
console.log($scope.decisionType);
//this prints the proper value when called a couple seconds after page load
$scope.getDecisionType = function(){
console.log($scope.decisionType);
};
$attrs.$observe('decisionType', function(value) {
//this is my motivation for wanting $scope.decisionType to be bound immediately
if($scope.decisionType==='hire'){
//should do some stuff here
}
});
}]
Instead of trying to access my attributes by binding them with the scope, I can access them more directly via the $attrs object!
angular.module('startup.directives.decision', [])
.directive('decisionMaker', [function () {
return{
restrict: 'E',
templateUrl: 'views/directives/decision.html',
scope: {},
controller: ['$scope', '$attrs', 'Decisions', function ($scope, $attrs, Decisions){
//this prints the correct value
console.log($attrs.decisionType);
if($attrs.decisionType==='hire'){
//should do some stuff here
}
}]
};
}]);
Related
Still new in Angular world and I have a categoriesController wrapping a custom directive categories-select like this:
<div ng-controller="categoriesController" ng-init="init()">
<categories-select></categories-select>
</div>
angular.module('app')
.directive('categoriesSelect', [function () {
return {
restrict: "E",
templateUrl: 'categoriesSelectTemplate',
controller: ['$scope', function ($scope) {
console.log($scope)
console.log($scope.categories)
}],
}
}])
The categoriesController has an array of categories.
The strange behavior I get inside the directive is:
So, whenever I try to get the categories array to do something with it, I find that it's empty, but it's -at the same time- populated in the $scope!
I tried also to isolate the directive scope and pass the array object to it, but I got the same strange behavior, but anyway, I want to know why this simple code doesn't work.
I always pass necessary parameters inside directive, never share parent scope.
So looking on each directive you see what is required, never got unknown behaviour.
so Angular directive:
angular.module('app')
.directive('categoriesSelect', [function () {
return {
restrict: "E",
scope: {
categories: '='
},
templateUrl: 'categoriesSelectTemplate',
controller: ['$scope', function ($scope) {
console.log($scope)
console.log($scope.categories)
}],
}
}])
and HTML:
<categories-select categories="categories"></categories-select>
Edited:
I think your directive controller is getting initialised before categories coming from parent scope. try to draw categories on directive HTML and you will get them, or use $scope.$watchCollection inside directive controller
$scope.$watchCollection('categories', function (newVal, oldVal) {
console.log(newVal);
});
Hi I am trying to learn AngularJS Directives and I came really close but would like to extend my learning by cleaning and de-coupling my directive code.
Directive:
app.directive('ngSparkline', function () {
var url = "http://api.openweathermap.org/data/2.5/forecast/daily?mode=json&units=imperial&cnt=14&callback=JSON_CALLBACK&q=";
return {
restrict: 'A',
require: '^ngCity',
transclude: true,
scope: {
ngCity: '#'
},
templateUrl: 'app/partials/weatherTemplate.html',
controller: ['$scope', '$http', function($scope, $http) {
$scope.getTemp = function(city) {}
}],
link: function (scope, iElement, iAttrs) {
scope.getTemp(iAttrs.ngCity);
scope.$watch('weather', function (newVal) {
if (newVal) {
var highs = [];
angular.forEach(scope.weather, function (value) {
highs.push(value.temp.max);
});
chartGraph(iElement, highs, iAttrs);
}
});
}
};
});
As you can see I am not trying to write inline template rather use templateUrl. Now the problem is for the controller when I try using a .js controller instead of writing the controller code inline, I receive an error. How do I achieve this.
I tried:
I tried passing
controller: '#',
name: 'ctrl'
and I pass the 'ctrl' as:
<div ng-sparkline ng-city="San Francisco" ctrl="weatherController"></div>
it gives me controller not found. My project structure is something like below.
What am I doing wrong?
Is there a better/correct way of doing this?
Please suggest.
Note: I am learning this exercise from "http://www.ng-newsletter.com/posts/directives.html"
Why not just specify ng-controller on your element? If WeatherController is defined somewhere else then it doesn’t affect your directive definition, just leave the controller out of there.
<div ng-sparkline ng-city="San Francisco" ng-controller="WeatherController"></div>
Provided somewhere you do have the controller defined like
app.controller('WeatherController', ['$scope', '$http', function ($scope, $http) {
$scope.getTemp = // …
}]);
(BTW, if you noticed, the AngularJS convention is to name controllers in UpperCase fashion.)
If a directive is using a controller directly, why is calling a method on the controller by referring the controller by its alias, not doing anything?
Imagine we have the following piece of code:
var app = angular.module('App', []);
app.controller('MyController', ['$scope', function($scope) {
$scope.doAction = function() {
alert("controller action");
}
this.doAction2 = function() {
alert("controller action 2");
}
}]);
app.directive('myDirective', [function() {
return {
restrict: 'E',
scope: {},
controller: 'MyController',
controllerAs: 'myCtrl',
bindToController: true,
template: "<a href='#' ng-click='myCtrl.doAction()'>Click it!<a><br><a href='#' ng-click='myCtrl.doAction2()'>Click it #2!<a> " ,
link: function($scope, element, attrs, controller) {
console.log($scope);
}
}
}]);
While the first link won't work, the second will. To make the the first one work, I'd have to drop the alias, i.e. instead of calling the action by ng-click='myCtrl.doAction()' to call it as: ng-click='doAction()'
Shouldn't it work using the alias too? I mean, you are much more likely to find and reuse a controller, where the developers have attached actions to the $scope object and not to this
ControllerAs exposes the controller instance on the scope under $scope[alias].
In your example, the scope looks (conceptually) like this:
$scope = {
$id: 5,
myCtrl: {
doAction2: function(){...}
},
doAction: function(){...}
}
So, you can see why ng-click="myCtrl.doAction()" doesn't work.
The Controller-As approach has some benefits over directly exposing properties on the scope - one is that it does not pollute the scope (and its descendants) with properties that they may not need. It also inherently provides the dot-approach (.) to work properly with ng-model. You can find more information in this SO question/answer.
I am trying to pass the controller scope of parent controller and parent directive into a child directive but facing an error saying that the controller is not available. Here is a plunk for that
http://plnkr.co/edit/aahgOK9oFFjcP2y5VkVa?p=preview
HTML:
<div ng-controller="MainCtrl as mc">
<new-dir>
<data-customer-details customer="mc.customers[0]" logger="mc.logger()" version-age="{{mc.age}}"></data-customer-details>
</new-dir>
</div>
OK, so I tinkered with your plunker a bit. I couldn't get it working using Controller As...I had to change it over to $scope injection on the main controller. Then I created a new scope on newDir by setting scope: true.
You don't actually need to require the MainCtrl because these directives are automatically children of that scope anyway.
I changed your 'MainCtrl' to this:
angular.module('plunker').controller('MainCtrl', function ($scope) {
$scope.name = 'World';
$scope.customers = [{
"name": "angularjs 1.4",
"version": "1.4"
}, {
"name": "angularjs 1.3",
"version": "1.3"
}, {
"name": "angularjs 1.2",
"version": "1.2"
}];
$scope.age = 30;
$scope.logger = function() {
console.log('clicked');
}
$scope.ctrlScopeVariable = 'im in controller scope';
})
Minor change to newDir:
function newDir (){
return {
scope: true, //you need this
controller: function($scope){
$scope.val= 'someval';
console.log($scope.$parent.ctrlScopeVariable)
},
link: function(scope, el, attr, ctrl) {
console.log(scope.$parent.name)
}
}
}
And the last directive:
function CustomerDetails() {
var directive = {
scope: {
customer: '=',
logger: '&',
myNewAge: '#versionAge'
},
restrict: 'EA',
require: ['^newDir'],
controllerAs: 'cd',
templateUrl: 'customer-details.html',
link: linkFunction,
controller: function($scope){
console.log($scope.$parent.$parent.ctrlScopeVariable);
var cd = this;
cd.newval = 'new val';
}
};
function linkFunction(scope, elem, attributes, controllers, transclude) {
console.dir(controllers);
scope.fromMainCtrl = scope.$parent.$parent.ctrlScopeVariable
}
return directive;
}
The Plunker
I added a binding to the customer details template that passes in the $scope.ctrlScopeVariable from the main controller, so you can see the MainCtrl scope is accessible form the child directive.
In regards to require, the relevant documentation is here, I think:
If it is necessary to reference the controller or any functions bound
to the controller's scope in the template, you can use the option
controllerAs to specify the name of the controller as an alias. The
directive needs to define a scope for this configuration to be used.
This is particularly useful in the case when the directive is used as
a component.
Looking back at myPane's definition, notice the last argument in its
link function: tabsCtrl. When a directive requires a controller, it
receives that controller as the fourth argument of its link function.
Taking advantage of this, myPane can call the addPane function of
myTabs.
Essentially, you can use it to reference a parent controller on which you need to access some functions or something. Notably, it becomes available under whatever alias you give it as the fourth argument of your link function.
EDIT:
In this Plunker I added a function to the controller of newDir, required newDir in the CustomerDetail directive, and then called that function in the CustomerDetail link function:
CustomerDetails directive:
//some stuff
require: '^newDir',
//some stuff
link: function(scope, el, attr, newDirCtrl) {
console.log(newDirCtrl.doubleNum(100));
}
newDir controller:
controller: function($scope){
this.doubleNum = function(num) {
return num*2
}
// some stuff
}
First you need to declare a variable as callback function:
var MainCtrlFn = function() { .... }
Then, you can set it as parameter to angularJS:
angular.module('plunker').controller('MainCtrl', MainCtrlFn);
I have yet another issue with minification. This time it's because of the $scope service passed to the directive's controller. See below code:
angular.module('person.directives').
directive("person", ['$dialog', function($dialog) {
return {
restrict: "E",
templateUrl: "person/views/person.html",
replace: true,
scope: {
myPerson: '='
},
controller: function ($scope)
{
$scope.test = 3;
}
}
}]);
If I comment out the controller part, then it works fine.
As you can see, I've used the array declaration for the directive, so the $dialog service is known to Angular even after minification. But how am I supposed to do it for the $scope service on the controller ?
You need to declare a controller as follows:
controller: ['$scope', function ($scope)
{
$scope.test = 3;
}]
Full example here:
angular.module('person.directives').
directive("person", ['$dialog', function($dialog) {
return {
restrict: "E",
templateUrl: "person/views/person.html",
replace: true,
scope: {
myPerson: '='
},
controller: ['$scope', function ($scope)
{
$scope.test = 3;
}]
}
}]);
A solution provided by #Sam would work to but it would mean exposing directive's controller to the whole application which is unnecessary.
ok, I ended up creating the controller in a separate file :
angular.module('person.controllers').controller('personCtrl', ['$scope', function ($scope) {
$scope.test = 3;
}]);
then in the directive, I assign the controller by name:
controller: 'personCtrl'
Not sure it's the best way. It looks clean though. What do you think ?