I am having an issue updating the scope in a controller from a directive. Below are the basics of what is going on.
myApp.directive('myDirective', ['$document', function($document) {
return {
link: function(scope, element, attr, controller) {
angular.element(element).bind('mousedown', function(event){
scope.name = 'test';
});
}
}
}
myApp.controller('myController', ['$scope', function($scope){
$scope.name = 'something';
}]);
The html:
<p>{{name}}</p>
<p my-directive>Click me</p>
The result above is always something that was set in my controller, and is never updated to test.
I have tried a $watch in my controller but it never updates, so i have to be just missing something...
Since you are using jQuery .bind functionality you have to use scope.$apply to notify Angular that you have updated a scope property.
angular.module('app', [])
.directive('myDirective', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
angular.element(element).bind('mousedown', function(event) {
scope.$apply(function() {
scope.name = 'test';
});
});
}
}
})
.controller('ctrl', function($scope) {
$scope.name = 'something';
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<p>{{name}}</p>
<p my-directive>Click Me</p>
</div>
Directives have their own isolated scope. If you want to share something between a directive's scope and a controller's scope, you need to include it in the directive definition like this:
return {
scope: {
name: '='
},
link: function(scope, element, attr, controller) {
angular.element(element).bind('mousedown', function(event){
scope.name = 'test';
});
}
}
This tells Angular that you want to share the scope variable name between the directive and whatever scope calls that directive. Using the equal sign (=) defines it as two-way data binding.
Related
I'm trying to pass a boolean value from my controller into my isolated scope directive. When I console.log(attrs) from the directive's link function, the someBoolean attribute is a string, rendering the actual text "main.bool" instead of a true or false value. When I toggle the boolean value from the outer controller, I want it to be updated in the directive.
https://plnkr.co/edit/80cvLKhFvljnFL6g7fg9?p=preview
app.directive('myDirective', function() {
return {
restrict: 'E',
replace: true,
scope: {
someBoolean: '='
},
templateUrl: 'myDirective.html',
link: function(scope, element, attrs) {
console.log(scope);
console.log(attrs);
},
controller: function($scope, $element, $attrs) {
console.log(this);
},
controllerAs: 'directiveCtrl',
bindToController: true
};
});
Controller
app.controller('MainCtrl', function($scope) {
var vm = this;
vm.bool = true;
vm.change = function() {
vm.bool = !vm.bool;
}
});
The template
<div>
Inside directive: {{someBoolean}}
</div>
As you have attached your directive Controller to directiveCtrl instead of mainCtrl, you'll access the variable someBoolean using directiveCtrl.someBoolean.
In this case, change the HTML to:
<div>
Inside directive: {{directiveCtrl.someBoolean}}
</div>
Plunker.
Another solution would be to remove the bindToController property inside your directive. With this, you don't need to use the controller name before the variable. Working Plunker.
Read more about this bindToController feature here.
Let us say I have this html:
<div ng-controller="MyCtrl">
<br>
<my-directive my-name="name">Hello, {{name}}!</my-directive>
</div>
with this simple controller:
myApp.controller('MyCtrl', function ($scope) {
$scope.name = 'Superhero';
});
And I have a directive in which I want to change the 'name' using require like this:
myApp.directive('myDirective', function($timeout) {
var controller = ['$scope', function ($scope) {
$scope.name = "Steve";
}];
return {
restrict: 'EA',
require: 'myName',
controller: controller,
link: function(scope, element, attrs, TheCtrl) {
TheCtrl.$render = function() {
$timeout(function() {
TheCtrl.$setViewValue('StackOverflow');
}, 2000);
};
}
};
});
But throws:
Error: No controller: myName
Here is the fiddle
But if I implement it using ng-model, works. Look here in this other fiddle
I have read that if you use 'require' in a directive, you need to have a controller for it.
So:
What I'm doing is wrong? It is not in this way? I need to do any other thing?
Well finally I got it.
Essencially what I'm trying to do is something called: 'Communication between directives using controllers'. I have found an article explaining this, and helped me a lot:
The view:
<div ng-controller="MyCtrl">
<br>
<my-directive my-name>Hello, {{name}}!</my-directive>
</div>
As you see above, there are two directives: my-directive and my-name. I will call inside my-directive a function from the controller of my-name directive using require.
myDirective:
myApp.directive('myDirective', function($timeout) {
return {
require: 'myName',
link: function(scope, element, attrs, myNameCtrl) {
$timeout(function() {
myNameCtrl.setName("Steve");
}, 9000);
} // End of link
}; // return
});
myName:
myApp.directive('myName', function($timeout) {
var controller = ['$scope', function ($scope) {
// As I tried, this function can be only accessed from 'link' inside this directive
$scope.setName = function(name) {
$scope.name = name;
console.log("Inside $scope.setName defined in the directive myName");
};
// As I tried, this function can accessed from inside/outside of this directive
this.setName = function(name) {
$scope.name = name;
console.log("Inside this.setName defined in the directive myName");
};
}];
return {
controller: controller,
link: function(scope, element, attrs, localCtrl) {
$timeout(function() {
localCtrl.setName("Charles");
}, 3000);
$timeout(function() {
scope.setName("David");
}, 6000);
} // End of link function
};
});
Interesting and works like a charm. Here is the fiddle if you want to try it out.
Also, you can get communication between directives using events. Read this answer here on SO.
I have a controller:
function myController($scope) {
$scope.clicked = false;
}
and a directive:
function myDirective() {
return {
restrict: 'E',
link: function(scope, elem, attrs) {
elem.bind('click', function() {
// need to update controller $scope.clicked value
});
},
template: '<div>click me</div>';
replace: true;
}
}
and I´m using it like this:
<div ng-controller="myController">
<my-directive></my-directive>
</div>
How can I change the controller value of $scope.clicked ?
thanks!
As you don't use isolated scope in your directive, you can use scope.$parent.clicked to access the parent scope property.
link: function(scope, elem, attrs) {
elem.bind('click', function() {
scope.$parent.clicked = ...
});
},
I would not recommend using scope.$parent to update or access the parent scope values, you can two way bind the controller variable that needs to be updated into your directive, so your directive becomes:
function myDirective() {
return {
restrict: 'E',
scope: {
clicked: '='
},
link: function(scope, elem, attrs) {
elem.bind('click', function() {
// need to update controller $scope.clicked value
$scope.clicked = !$scope.clicked;
});
},
template: '<div>click me</div>';
replace: true;
}
}
now pass this clicked from parent:
<div ng-controller="myController as parentVm">
<my-directive clicked="parentVm.clicked"></my-directive>
</div>
function myController() {
var parentVm = this;
parentVm.clicked = false;
}
I would recommend reading up on using controllerAs syntax for your controller as that would really solidify the concept of using two way binding here.
I like to use $scope.$emit for such purposes. It allows to send data from directive to the controller.
You should create custom listener in your controller:
$scope.$on('cliked-from-directive', function(event, data){
console.log(data)
})
As you can see, now you have full access to your controller scope and you can do whatever you want. And in your directive just to use scope.$emit
link: function(scope, elem, attrs) {
elem.bind('click', function() {
scope.$emit('cliked-from-directive', {a:10})
});
Here I've created jsfiddle for you
I am still learning AngularJS and I'm having some issues with scopes in my implementation.
The following is my controller and directive definition:
angular.module('myModule', [])
.controller('myController', ['$scope', function($scope) {
$scope.parentVariable = "Test";
}]);
.directive('myDirective', [function(){
return {
restrict:'AEC',
link: function(scope, element, attrs) {
var child = scope.$eval(attrs.myDirective);
}
}
}]);
The following is my HTML:
<div ng-controller="myController"
<div my-directive="parentVariable"></div>
</div>
I am trying to retrieve the value of parentValue, however when I run the code child is undefined.
You had a few typos, once you clean them, it all works. Also, if you want to use a variable in the DOM, you need to make it a scope property. See below:
angular.module('myModule', [])
.controller('myController', ['$scope', function($scope) {
$scope.parentVariable = "Test";
}])
.directive('myDirective', [function(){
return {
restrict:'AEC',
template: '<p>{{child}}</p>',
link: function(scope, element, attrs) {
scope.child = scope.$eval(attrs.myDirective);
}
}
}])
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myModule" ng-controller="myController">
<div my-directive="parentVariable"></div>
</div>
Is it possible to call the method defined inside the directive controller from outside.
<div ng-controller="MyCtrl">
<map></map>
<button ng-click="updateMap()">call updateMap()</button>
</div>
app.directive('map', function() {
return {
restrict: 'E',
replace: true,
template: '<div></div>',
controller: function(){
$scope.updateMap = function(){
//ajax call here.
}
},
link: function($scope, element, attrs) {
$scope.updateMap();
//do some dom transformation
}
}
});
I want to call the method updateMap() function from my view.
If you expose the function on the controller, instead of the scope, you can expose the controller on the parent scope, such as:
controller: function($scope, $element, $attrs){
// Verify this. The controller has to be added to the parent scope, if the directive itself is creating a scope
$scope.$parent[$attrs["name"]]=this;
this.updateMap = function(){
//ajax call here.
}
},
Now in the main controller you will be able to access the controller:
<button ng-click="myMap.updateMap()">call updateMap()</button>
This is similar to how ng-model exposes its controller. Think of the controller as an API to your directive.
It would be a bad practice to access a function from the controller as you want. But still you can bind the updateMap function to $rootScope thus it can be used globally and still pass the current scope as a parameter to it.
Eg,
$rootScope.updateMap = function($scope) {
// use the scope to do the manipulation
}
<div ng-controller="MyCtrl">
<map></map>
<button ng-click="updateMap(this)">call updateMap()</button>
</div>
Here passing 'this' in updateMap function will refer to the scope in which the element is wrapped. In the above example, 'this' will refer to the MyCtrl's $scope
I would suggest two options. One simple option is to use events:
<div ng-controller="MyCtrl">
<map></map>
<button ng-click="updateMap()">call updateMap()</button>
</div>
app.directive('map', function() {
return {
restrict: 'E',
replace: true,
template: '<div></div>',
controller: function(){
$scope.updateMap = function(){
//ajax call here.
}
},
link: function($scope, element, attrs) {
$scope.$on('my.update.map.event', $scope.updateMap);
}
}
});
app.controller('MyCtrl', function ($scope) {
$scope.updateMap = function () {
$scope.$broadcast('my.update.map.event');
};
});
This isn't a bad solution. You're not polluting the root scope (#Krishna's answer) and your map directive isn't adding an arbitrary value to your controller's scope (#Chandermani's answer).
Another option, if you want to avoid events, is to to use the controllerAs syntax to expose your map directive's controller.
<div ng-controller="MyCtrl">
<map controller="mapController"></map>
<button ng-click="mapController.updateMap()">call updateMap()</button>
</div>
app.directive('map', function() {
return {
restrict: 'E',
replace: true,
scope: {
'controller': '=?'
},
template: '<div></div>',
//controllerAs creates a property named 'controller' on this
//directive's scope, which is then exposed by the
//'scope' object above
controllerAs: 'controller',
controller: function(){
this.updateMap = function(){
//ajax call here.
}
},
link: function($scope, element, attrs, ctrl) {
ctrl.updateMap();
}
}
});
This is similar to #Chandermani's answer, but the coupling between your controller and your directive is much more explicit. I can tell from the view that the map directive is exposing its controller and that it will be called mapController in MyCtrl's scope.
(I found this idea here).