I have a directive that is watching something on $scope, and it is getting the wrong $scope.
so I have a state setup that specifies a controller:
.state('app.mystate', {
url: 'search/:searchText',
views: {
'mainPane#': {
templateUrl: 'views/content/search.html',
controller: 'ABCController'
}
},
resolve: {
searchPromise: ['$http', '$stateParams', function ($http, $stateParams) {
console.log($stateParams.searchText);
return $http.get(...blah...blah).then(blahblah);
}]
}
})
When this state gets activated, it goes to this view:
<ul ...>
<div ng-repeat="...">
<li ...>
<div ng-include="'views/widgets/somewidget.html'"></div>
</li>
</div>
</ul>
the ng-include loads this, which has a specific controller specified. And a directive.
<div ng-controller="XYZController">
<my-chart chart="chart" ...></my-chart>
</div>
Here is my directive:
angular.module('app.directive')
.directive('myChart', function () {
return {
template: '<div></div>',
scope: {
chart: '='
},
// wrong scope!
scope.$watch('chart', function (chart) { }
I figured that the <div> that contains the my-chart directive, that directive would get the $scope that goes into XYZController.
But the directive is getting the scope that is injected into ABCController, not XYZController as I want/expected. I can see XYZContoller getting activated.
How do I get the $scope that is injected into XYZController be the same scope that the my-chart directive sees?
The sample code for you directive seems to have been truncated/broken during copy/paste so it's hard to see where that scope.$watch call is at. If it is in the directive definition, after the return block, then it's never getting called but I would expect it to be in the controller.
Having said that, you can specify the controller to be used by the directive in the directive attributes which would seem appropriate here, unless you want to use the directive with different scopes..
<div>
<my-chart chart="chart"></my-chart>
</div>
angular.module('app.directive')
.directive('myChart', function () {
return {
controller: "XYZController",
bindToController: true
template: '<div></div>',
scope: {
chart: '='
}
})
.controller('XYZController', function($scope){
$scope.$watch('chart', function (chart) { });
});
scope: true will create a scope that is prototypically inerited from the parent scope, so you should be able to $watch a property on the parent scope.
angular.module('app.directive')
.directive('myChart', function () {
return {
template: '<div></div>',
scope: true,
link: function(scope){
scope.$watch('chart', function (chart) { });
});
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 angular ui for bootstrap for its modals:
http://angular-ui.github.io/bootstrap/#/modal
I am opening a modal with a controller and templateUrl with:
var modalInstance = $uibModal.open({
animation: true,
templateUrl: $scope.templateUrl,
controller: $scope.controller,
size: 'lg',
resolve: {
formModel: item
}
});
where formModel is the model I will use in the modal.
Here is the controller for the modal:
app.controller('commentCtrl', ['$scope', '$modalInstance', 'formModel', function ($scope, $modalInstance, formModel) {
$scope.formModel = {};
var loadFormModel = function () {
if (formModel !== undefined) {
$scope.formModel = formModel;
}
};
loadFormModel();
}]);
This modal has child directives and needs to pass properties of formModel to them
template:
<div>
<child model="formModel.Comment"></child>
</div>
but child is created before the modal's controller has loaded formModel. Inside the child I want to use model as:
app.directive('child', function () {
return {
restrict: 'E',
template: '<textarea ng-model="model"></textarea>',
link: linkFn,
controller: controllerFn,
scope: {
model: '='
}
};
});
Edit:
I've found that I can do:
<div>
<child model="formModel" property="Comment"></child>
</div>
...
app.directive('child', function () {
return {
restrict: 'E',
template: '<textarea ng-model="model[property]"></textarea>',
link: linkFn,
controller: controllerFn,
scope: {
model: '=',
property: '#'
}
};
});
Is there a better way to do this without the extra attribute?
Edit 2:
I have found where the bug is:
http://plnkr.co/edit/kUWYDvjR8YArdqtQRHhi?p=preview
See fItem.html for some reason having any ng-if causes the binding to stop working. I have put a contrived ng-if='1===1' in for demonstration
This happens because ng-if directive creates new inherited scope, so the bug is just a common scope prototypal inheritance pitfall.
The most concise way to get around it is to use controllerAs syntax in conjunction with bindToController, the avoidance of undesirable scope inheritance side effects is the most common use case for them. So it will be
app.directive('fItem', function () {
return {
restrict: 'E',
replace: true,
templateUrl: 'fItem.html',
controller: ['$scope', function ($scope) {
}],
controllerAs: 'vm',
bindToController: true,
scope: {
model: '='
}
};
});
and
<div class="input-group">
<textarea ng-if='1===1' ng-model="vm.model" class="form-control"></textarea>
</div>
Not sure what you are trying to do. If you need just to wait until variable is resolved, you need just use promise: (Here is simple one using $timeout, if you use i.e. $http - you ofc dont need $q and $timeout)
resolve: {
something: function () {
return $q(function(resolve, reject) {
setTimeout(function() {
resolve('Hello, world!');
}, 3000);
});
Then modal will be opened only after promise is resolved.
http://plnkr.co/edit/DW4MzIO4ej0JorWRgWIK?p=preview
Also keep in mind that you can wrap you object, so if in scope you have $scope.object = {smth : 'somevalue'} :
resolve: {
object: function () {
return $scope.object;
});
And in modal controller:
$scope.object = object;
Now object in initial controller scope and object in modal scope point to same javascript object, so any time you change one - another changes. You are free to use object.smth in modal template as usual property. And as soon as it will change you will see changes.
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.
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).
I have 2 simple directives...
the parent directive:
.directive('modal', [function () {
return {
replace: true,
scope: {
/* attributes */
},
templateUrl: 'modal.tpl.html',
transclude: true,
link: function (scope) {
/* code */
}
};
the child directive
.directive('keypad', function () {
'use strict';
return {
templateUrl: 'keypad.tpl.html',
scope: {
value: '=',
},
link: function (scope, element, attrs) {
/* code */
}
};
and finally the controller:
.controller('ctrl', ['$scope', '$timeout', function ($scope, $timeout) {
$scope.$watch('howMuch', function(){
console.log('wont work ;-(');
});
}
and my template looks like this:
<div modal>
<div keypad class="keypad" value="howMuch"></div>
</div>
Any idea why the child directive can't change the howMuch value on the controller?
The same code but WITHOUT the parent directive works PERFECT.
The parent directive has an isolate scope. You didn't show the scope attributes, but howMuch needs to be accessed via a property in your isolated scope in order to update the value in the controller's scope.