I have a problem with my directive and controller. The variable item in scope is undefined in directive, even though I passed it in html. This is my code:
app.js:
var app = angular.module("app", ["ngRoute"]);
app.config(["$routeProvider", function($routeProvider) {
$routeProvider
.when("/", {
templateUrl: "views/main.html",
controller: "MainCtrl"
});
}]);
controller.js:
app.controller("MainCtrl", function($scope) {
$scope.item = "x";
});
directive.js:
app.directive("exampleDirective", function() {
return {
restrict: "A",
scope: {
item: "="
},
templateUrl: "views/item.html"
};
});
index.html:
<div ng-view></div>
main.html:
<div example-directive item="item"></div>
item.html:
<div>{{ item }}</div>
UPDATE
I changed my code to:
app.directive("exampleDirective", function() {
return {
restrict: "A",
scope: true,
templateUrl: "views/item.html"
};
});
and now there is "x" in scope.$parent.item. But why it isn't present inside directive?
Although it seems to work just fine with latest Angular stable http://plnkr.co/edit/6oXDIF6P04FXZB335voR?p=preview maybe you are trying to use templateUrl thats pointing to somewhere it doesn't exist.
Another thing, use primitives only when strictly needed. In case you ever need to modify value of item, you won't be able to do so since you are using a primitive. Plus, if you need "more info" to go inside your isolated scopes, and to avoid attribute soup (attr-this="that", attr-that="boop", my-otherstuff="anotheritem.member", etc) you can pass the two-way bind of an object that handle more data.
Or if you need to share state through multiple controllers, directives, etc, use a service instead and use dependency injection, and there's no need to pass in objects/primitives to your isolated scope, and you can assure state, and that's best practice "the Angular way".
This fiddle works: http://jsfiddle.net/HB7LU/2844/ which is essentially the same thing just without the route information.
var myApp = angular.module('myApp',[]);
myApp.directive("exampleDirective", function() {
return {
restrict: "A",
scope: {
item: "="
},
template: "<div>{{ item }}</div>"
};
});
function MyCtrl($scope) {
$scope.item = 'Superhero';
}
With the view:
<div ng-controller="MyCtrl">
<div example-directive item="item"></div>
</div>
This leads me to believe it could be a scope issue. So try encapsulating the controller scope variable in a container:
$scope.cont = { item: 'x' };
And in the view
<div example-directive item="cont.item"></div>
Related
I have two directives, Isolated and Shared, the Isolated directive pass the two-way binding directly to the Shared directive but the Shared directive is not using the Isolated scope, is creating its own.
The objective is that the Isolated directive should respond to changes in the two-way bindings when the Shared directive changes them.
<body ng-app="app">
<div ng-controller="main as $ctrl">
<h3>Main data: {{$ctrl.data.bind}}</h3>
<isolated bind="$ctrl.data.bind"></isolated>
</div>
</body>
angular.module("app", [])
.controller("main", function() {
this.data = {
bind: 123
}
})
.directive("isolated", function() {
return {
scope: {
bind: '='
},
bindToController: true,
template: '<div><h3>Parent directive data: {{$ctrl.bind}}</h3> </div>'
+ '<input type="text" shared ng-model="$ctrl.bind" />',
controller: function() {
this.changed = function() {
console.log('Data changed: ' + this.bind);
}
},
controllerAs: '$ctrl',
link: {
pre: function($scope) {
console.log("Parent data: " + $scope.$ctrl.bind);
}
}
}
})
.directive("shared", function() {
return {
restrict: 'A',
require: {
ngModel: '^'
},
bindToController: true,
link: function($scope) {
console.log('Current data in shared: ' + $scope.$ctrl.bind)
},
controller: function() {
this.$postLink = function() {
this.ngModel.$modelValue = 321;
}
},
controllerAs: '$ctrl'
}
});
Here I have a Plunker
Gourav Garg is correct. Due to the shared scope, the second directive declaration is overriding the $scope.$ctrl field. The controllerAs property in the second declaration is unneeded anyways, as you never access the controllers properties within the template. If you do end up needing the second directives controller information within your template, you need to declare it's name as something other than $ctrl, or, better yet, use require syntax to require the second directive on the first directive. That will bind the second directive's controller to a property on the first directive's controller.
For more information on require, see the "Creating Directives That Communicate" section of the angular directive guide here.
Best of luck!
I am using UI-Router and trying to access my web app's current state from within a directive, using the following:
footer.directive.js
(function () {
'use strict';
angular
.module('app')
.directive('myFooter', myFooter);
myFooter.$inject = ['$cookies', 'userFactory', '$state', '$log', '$rootScope'];
function myFooter($cookies, userFactory, $state, $log, $rootScope) {
var directive = {
restrict: 'E',
templateUrl: 'app/components/footer/footer.html',
controller: FooterController,
controllerAs: 'vm',
bindToController: true
};
return directive;
function FooterController($state) {
var vm = this;
vm.currentState = $state;
}
}
})();
footer.html
<div class="footer">
<p>{{ vm.currentState.current.name }}</p>
</div>
When I run $log.log($state) it posts an object in my console that has a current object with a name attribute that is equal to the state name that I need, but when I try to reference the $state.current.name, either on my view or by logging it to the console, it displays as an empty string.
I'm a bit new to Angular, so if someone could explain to me what is going on here or at the least how to fix this so that I can display what I want properly, that would be a huge help. Thanks!
Edit: Two other questions that I looked at before posting this one are:
This one which seems to deal more with changing a class name based on state name, and this one, which doesn't quite address my problem either (and doesn't look like it could possibly be the right way to do this.)
First of all, there is a naming bug in your posted code! (amaiFooter)
The second thing is, if you log an object, it's bound by a call by reference
So the moment you log it, you log the reference. That means when you look at it, it can contain other values than when you have logged it
But when you logged the state name and it was undefined, that was the right output!
You can try to wrap it in a $timeout function with 0 delay, just to add your code to the end of the current digest cycle, that should solve your problem
You need inject the state service on the controller as follows
(function () {
'use strict';
angular
.module('app')
.directive('myFooter', myFooter);
myFooter.$inject = ['$cookies', 'userFactory', '$state', '$log', '$rootScope'];
function myFooter($cookies, userFactory, $state, $log, $rootScope) {
var directive = {
restrict: 'E',
templateUrl: 'app/components/footer/footer.html',
controller: FooterController,
controllerAs: 'vm',
bindToController: true
};
return directive;
}
FooterController.$inject = ['$state'];
function FooterController($state) {
var vm = this;
vm.currentState = $state;
}
})();
I have an angular project using angularjs 1.3.7 and cannot figure out why an object I'm passing with an attribute from a parent to a child (each with their own isolated scopes), will not pass properly. Logging to the console from the child directive will show this object as undefined whereas logging to the console from the parent will show the object as intended. Below is a simplified version of what I'm currently working with.
Main View Template:
<parent-directive>
Parent Directive:
HTML:
<div>
<div ng-repeat="foo in parentCtrl.foos">
<child-directive foo="foo" bar="parentCtrl.bar"></child-directive>
</div>
</div>
javascript:
angular
.module('parentDirectiveModule', [])
.directive('parentDirective', function() {
var parentDirectiveCtrl = ['$scope', function($scope){
var parentCtrl = this;
parentCtrl.foos = [
{'name': 'foo1', 'id': '1'},
{'name': 'foo2', 'id': '2'}
];
parentCtrl.bar = {'property': 'name'};
return {
scope: {},
templateUrl: '../parentDirective.html',
restrict: 'E',
replace: false,
controller: parentDirectiveCtrl,
controllerAs: 'parentCtrl',
bindToController: true
}
}];
Child Directive:
HTML:
<div>
<span>{{childCtrl.foo}}</span>
<button ng-click="childCtrl.editBar()">{{childCtrl.bar.property}}</button>
</div>
javascript:
angular
.module('childDirectiveModule', [])
.directive('childDirective', function() {
var childDirectiveCtrl = ['$scope', function($scope){
var childCtrl = this;
console.log(childCtrl.foo);
console.log(childCtrl.bar);
childCtrl.editBar = function() {
// update bar and reflect in the parent controller
};
return {
scope: {
foo: '=',
bar: '='
},
templateUrl: '../childDirective.html',
restrict: 'E',
replace: false,
controller: childDirectiveCtrl,
controllerAs: 'childCtrl',
bindToController: true
}
}];
Using the above code, the console log on childCtrl.foo returns the foo object as expected, but the childCtrl.bar returns as undefined.
Thanks in advance
EDIT: fixed a spelling
EDIT EDIT: closed an open " and changed bar to parentCtrl.bar on
Try changing the bar="bar" assignment to this:
<div>
<div ng-repeat="foo in parentCtrl.foos">
<child-directive foo="foo" bar="parentCtrl.bar"></child-directive>
</div>
</div>
At least with a quick glance that might be the cause for your problem, bar being genuinely undefined.
Thanks everyone for your help. So it looks like my issue is actually related to the way angular handles camel-case conversions when copying an attribute into an isolated scope. On the child-directive, I have the property bar="parentCtrl.bar". In actuality, I have a camel case name for this attribute which is something more akin to fooBar="parentCtrl.bar". In the declaration of the isolated scope, angular will pass this attibute as foobar and not as the fooBar I was expecting. As a result, every time I console logged childCtrl.fooBar, I would get undefined.
To fix this problem, I changed the attribute name to foo-bar. Angular did not need to do any more conversions and the object passed through as expected.
I have a question, code like this:
HTML:
<div class="overflow-hidden ag-center" world-data info="target"></div>
js:
.directive('worldData', ['$interval', function($interval) {
return {
scope: {
chart: '=info'
},
template: '<div>{{chart.aaa}}</div>',
link: function($scope, element, attrs) {
$scope.target = {'aaa': 'aaa'};
aaa = $scope.chart;
}
}
}])
The chart value is undefined, and template no value, but when I declare $scope.target within controller, the code works, why?
This should be generally the pattern:
.controller('myController', function($scope){
$scope.target = {'aaa': 'aaa'}; //In reality, you'd normally load this up via some other method, like $http.
})
.directive('worldData', [function() {
return {
scope: {
chart: '=info'
},
template: '<div>{{chart.aaa}}</div>'
}
}])
--
<div ng-controller="myController">
<div class="overflow-hidden ag-center" world-data info="target"></div>
</div>
Alternatively, the directive could be responsible for going and fetching the data, and not pass in anything to it. You'd only want to consider that if you don't need the data in multiple places.
I understand how Angular dependency injection works with directives but wanted clarification on something. I have a dummy test directive as follows:
app.directive("test", [
function() {
return {
restrict: "E",
scope: {},
controller: ["$scope", "$filter",
function($scope, $filter) {
var food = ["Apple pie", "Apple cobler", "Banana Split", "Cherry Pie", "Applesauce"];
$scope.favorites = $filter('filter')(food, "Apple");
}
],
template: "<div>{{favorites}}</div>"
}
}
]);
This works fine and will filter the food array as expected. However I noticed if I inject the $filter service in the directive as follows, it still works:
app.directive("test", ["$filter",
function($filter) {
return {
restrict: "E",
scope: {},
controller: ["$scope",function($scope) {...
My question: Is it better practice to inject services into a directive in the declaration line like so:
app.directive("test", ["$filter", function($filter) {
or in the controller line like this:
controller: ["$scope", "$filter", function($scope, $filter) {?
Is there no difference? Here is a Plunker of the directive code.
In this case, you're receiving the same service, so it likely doesn't matter too much. I personally prefer to keep them as localized as possible; if you don't need $filter in the link function or something like that, I'd just inject it into the controller.
In certain cases, this may also make it easier to mock dependencies during testing.
You can do this also. Much better way by splitting directive and its controller in a single file. Or you can write in separate files. But, better understand
app.directive('throbberDirective',
[
function(){
return {
restrict: "EA",
templateUrl: "common/utils/throbbers/throbber.html",
controller: throbberController
}
}
]);
app.controller('throbberController', throbberController);
throbberController.$inject = ['$scope', '_$ajax'];
function throbberController($scope){
$scope.throbber = _$ajax.getThrobberConfigs();
$scope.throbber.templateName = $scope.throbber.templateName;
}