Error: Expression 'undefined' used with directive is non-assignable
I'm getting the above error when trying to change a controller scope variable from my directive. Directive:
<mydir selUser="selectedUser"></mydir>
My controller:
angular.module('myApp')
.controller('MainCtrl', function ($scope, $http) {
$scope.selectedUser = {};
$scope.$watch('selectedUser', function() {
console.log('user changed');
});
});
And finally my directive:
angular.module('myApp')
.directive('mydir', function() {
return {
restrict: 'E',
templateUrl: 'app/mydir/mydir.html',
scope: {
selUser: '='
},
link: function(scope) {
scope.changeUser = function(user) {
console.log('user: ' + JSON.stringify(user));
scope.selUser = user;
};
}
};
});
As you can see, I am trying to change the selected user in the parent (controller) scope on the click of a button. But then the click happens, the error is thrown.
I have seen several combinations of how to define the scope in the directive and on the html declaring the directive but none of these seemed to work. Is there something else I'm missing?
Related
I'm having an issue with injecting/inheriting a scope from a controller to a directive. Importantly, the module is not separated into its own var.
angular.module('articles').controller('ArticlesController', ['$scope, ...
]).directive('comments', ['$scope' ... //does not work!
You don't inject the scope into a directive as a dependency. Should be like this:
.directive([function() {
return {
"link": function($scope, $element, $attrs) {
//code here
}
}
}]);
The best way is to think about directives as black boxes. You provide it with some data and it updates/displays it. You can read all required info about directives here and declare directive's inputs and outputs like this:
.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
myParameter: '=' //this declares directive's interface (2 way binded)
},
link: function (scope, element) {
console.log(scope.myParameter);
}
};
});
and then you can use this directive as:
<my-directive my-parameter='variable_declared_in_controller'></my-directive>
I just bypassed $scope entirely with a couple of other scopes:
.directive('comments', ['$stateParams', '$location', 'Articles',
function ( $stateParams, $location, Articles) {
if ($stateParams.articleId != null){
var article = Articles.get({
articleId: $stateParams.articleId
});
var comments = {articleId: article.articleId, created: Date.now, comment:"Hello, World!", user: "admin" };
return{
restrict: "A",
scope: true,
template: "<div></div>"
};
}
}
]);
I am working to add modals to my directives using ui-bootstrap and did so fine on the previous directive. I don't believe I am doing anything differently in this one but I get the ReferenceError: milestoneController is not defined when I run the edit() function from within the directive.
milestone.html (this is the template HTML for the directive below):
<div ng-controller = "milestoneController"></div>
milestone directive:
angular.module('ireg').directive('milestone', function (milestoneFactory,$modal) {
return {
restrict:'E',
scope: {
objectid:'#objectid'
},
templateUrl: '/ireg/components/milestone/milestone.html',
link: function ($scope, element, attrs) {
$scope.edit = function(data) {
milestoneController.editMilestoneDialog(data);
};
}
}
});
angular.module('ireg').controller('milestoneController', function ($scope, $modal){
$scope.editMilestonesDialog = function (objectid) {
//fun
}
});
EDIT: I allso felt I should mention that the milestone directive is repeated in a ng-repeat loop. Thanks!
ok you're going to want to use a transcluded scope in your directive to pass a controller function to the directive. Your directive now becomes:
angular.module('ireg').directive('milestone', function (milestoneFactory,$modal) {
return {
restrict:'E',
scope: {
objectid:'#objectid',
editMilestoneDialog:'&'
},
templateUrl: '/ireg/components/milestone/milestone.html',
link: function ($scope, element, attrs) {
$scope.edit = function(data) {
$scope.editMilestoneDialog(data);
};
}
}
and your markup becomes:
<milestone edit-milestone-dialog="editMilestoneDialog"></milestone>
I have an angular directive that loads data from service,
BUT
it loads the data with a variable he go from a controller that it was loaded as well from a service.
code:
directive:
app.directive("posts", ['Posts', function(Posts) {
return {
restrict: 'E',
template: '' +
'<div ng-repeat="post in posts"></div>',
scope: {
showLoading: '&',
hideLoading: '&',
spot: '#'
},
controller: ['$scope', function ($scope) {
}],
link: function(scope, element, attrs) {
$scope.load = function () {
Posts.loadPostsBySpot(scope.spot)
};
}
};
}]);
Controller
app.controller('spotPageController', ['$scope', 'Spots', function ($scope, $Spots) {
doit = function () {
Spots.getSpot($)
.success(function (data) {
$scope.spotId = data.data;
console.log($scope.spot);
}).error(function (data) {
console.log('error');
});
};
}]);
html inside
<posts spot="{{spotId}}" showLoading="showLoading()" hideLoading="hideLoading()"></posts>
but when the directive is loaded the "spot" is not yet set,
so how do I make the directive load only after the spot is set.
Use ng-if.
<posts ng-if="spotId" spot="{{spotId}}" showLoading="showLoading()" hideLoading="hideLoading()"></posts>
This element will be rendered only after the spotId is initialized. Therefore, your directive will not be called before that.
If you want to encapsulate this behavior in directive, you should watch for changes of the scopeId. See the fiddle.
Just wondering what's the best way to communication from controller to directive function, i have got an ng-click on one of the button, but the function sit in the directive, is there a way i can call the function within the controller (which sits in directive). i understand u can apply double binding with scope, is there any better way of doing so?
Cheers
app.controller('leadsListing', ['$scope', function($scope){
$scope.filterresultcount = 0;
$scope.records = [];
$scope.filtertotal = '';
$scope.$watch('filtertotal', function(){
$scope.filterresultcount = parseInt($scope.filtertotal / 20);
});
$scope.moreFilterResult = function(){
if($scope.filterresultcount > 0){
$scope.filterresultcount--;
}
$scope.heyJoe(); // It's in diretive
};
}]);
app.directive('recordfilter', ['$http', 'filterService', function($http, filterService){
return {
scope: {
names : '#names',
model : '#model',
records : '=records',
filtertotal : '=filtertotal',
filterresultcount : '=filterresultcount'
},
restrict: 'A',
replace: true,
link: function($scope, iElm, iAttrs, controller) {
$scope.heyJoe()
}
}
}
I believe the best way to implement this kind of controller --> directive communication is to use $scope.$broadcast from the controller, and $scope.$on in the directive's controller/ linking function.
Controller:
app.controller('leadsListing', ['$scope', function($scope){
// ...
$scope.moreFilterResult = function(){
if($scope.filterresultcount > 0){
$scope.filterresultcount--;
}
$scope.$broadcast('joeCalled');
};
}]);
Directive:
app.directive('recordfilter', ['$http', 'filterService', function($http, filterService){
return {
scope: {
names : '#names',
model : '#model',
records : '=records',
filtertotal : '=filtertotal',
filterresultcount : '=filterresultcount'
},
restrict: 'A',
replace: true,
link: function(scope, iElm, iAttrs, controller) {
scope.$on('joeCalled', function(){
// Do something...
});
});
};
}
Edit:
Created a working example of this technique:
http://jsfiddle.net/9p3eyy5h/2/
Calling a function directly in the directive from the controller could be done by placing an empty object on the controller scope, binding it to the directive's scope with '=', and attaching a function to it in the directive's linking function/ controller, which could later be called by the wrapping controller.
Controller:
app.controller('leadsListing', ['$scope', function($scope){
// ...
$scope.directiveFuncs = {};
$scope.moreFilterResult = function(){
if($scope.filterresultcount > 0){
$scope.filterresultcount--;
}
$scope.directiveFuncs.heyJoe();
};
}]);
Directive:
app.directive('recordfilter', ['$http', 'filterService', function($http, filterService){
return {
scope: {
names : '#names',
model : '#model',
records : '=records',
filtertotal : '=filtertotal',
filterresultcount : '=filterresultcount',
// Binding to the controller's func obj
funcs: '='
},
restrict: 'A',
replace: true,
link: function(scope, iElm, iAttrs, controller) {
scope.funcs.heyJoe = function(){
// Do something...
}
});
};
}
HTML:
<div ng-controller="leadsListing">
<div recordfilter funcs="directiveFuncs"></div>
</div>
I would however advise to use my other approach, as it prevents direct dependency between the controller and the directive, and therefor, more robust, so it won't throw an error if the directive is missing or changes.
Working example:
http://jsfiddle.net/9pm3zg5s/1
Not sure if I am misunderstanding how directives are created here. Say for example I have a controller such as:
angular.module('myApp.controllers').controller('MyController', ['$scope', 'MyService', function($scope, MyService) {
$scope.restangularService = MyService;
}
I then have a directive such as:
angular.module('myApp.directives').directive('myGrid', function() {
return {
restrict: 'A',
templateUrl: 'some/path/here.html',
scope: {
restangularService: '&'
},
controller: ['$scope', function($scope) {
//access $scope.restangularService to run some queries
}
};
});
I then use my directive as such:
<div data-my-grid data-restangular-service='restangularService'></div>
I would expect that in my directive I could access $scope.restangularService and make calls however it's not being populated correctly. Am I doing this totally wrong? Any input? I have a feeling I need to be using the ngModel directive somehow.
The "&" prefix of an isolate scope value in a directive provides "one-way binding" which makes available a getter function in the directive's scope.
Any changes you make to the object will not make their way back up to the parent controller of the directive (it is "read-only"). So you can't access your 'restangularService' variable as you would in the controller's scope, without calling the getter function:
angular.module('myApp.directives', []).directive('myGrid', function() {
return {
restrict: 'A',
templateUrl: 'some/path/here.html',
scope: {
restangularService: '&'
},
controller: ['$scope', function($scope) {
console.log($scope.restangularService()); // outputs service value
}]
};
})
Alternatively, you could use "=", which would allow you directly access the scope object you pass in:
angular.module('myApp.directives', []).directive('myGrid', function() {
return {
restrict: 'A',
templateUrl: 'some/path/here.html',
scope: {
restangularService: '='
},
controller: ['$scope', function($scope) {
console.log($scope.restangularService); //outputs service value
}]
};
})
Plunk demonstrating both types