I have a controller used to add tasks. On that page a user needs to select a group to act upon. I have written a directive that is used to allow a user to pick groups (folders)
My page controller
function AddTaskController($scope) {
var vm = this;
vm.group = { whatsit: true };
$scope.$watch("vm.group", function () {
console.log("controller watch", vm.group);
},true);
}
The page html where the directive is used
<em-group-selection group="vm.group"></em-group-selection>
The directive configuration
function GroupSelectionDirective() {
return {
scope: {
group: '='
},
controller: GroupSelectionDirectiveController,
controllerAs: 'vm',
templateUrl: '/views/templates/common/folderselection.html'
};
}
The directive controller:
function GroupSelectionDirectiveController($scope) {
var vm = this;
$scope.$watch("group", function () { console.log("yo1", vm.group); }, true)
$scope.$watch("vm.group", function () { console.log("yo2", vm.group); }, true)
}
Now when this fires, both console.log() calls in the directive fire once, with undefined. They never fire again. If in the controller I set vm.group to something else the $watch in the AddTaskController never gets fired.
Why isnt the data binding working?
Update:
I notice that if, in the directive, I change the init() function in my directive to use $scope it works! Can I not, as Fedaykin suggests, use controllerAs with two way data binding?
function init() {
$timeout(function () {
$scope.group.shizzy = 'timeout hit';
}, 200);
}
Turns out that if you use isolate scopes and controlelrAs syntax you need to also use bindToController : true. Without this you will not be able to only use vm and will have to use $scope for the isolate scope variables
More information can be found in the John Papa style guide and this SO answer
The final directive setup is as so:
function GroupSelectionDirective() {
return {
scope: {
group: '='
},
controller: GroupSelectionDirectiveController,
controllerAs: 'vm',
bindToController: true,
templateUrl: '/views/templates/common/folderselection.html'
};
}
Related
I am using Angular 1.5.7 version.
I have a directive that takes in the controller name and view name as strings to then invoke the controller respectively.
I am not able to bind username to the invoking controller from the previous controller, I see the value available in my previous controller.
Please can you advise what could be the issue?
myApp.directive("pendingRequests", function() {
return {
restrict: 'E',
controller: "#",
name: "controllerName",
controllerAs: 'pendingReqCtrl',
scope: {},
bindToController: {
username: '=username'
},
templateUrl: function(tElement, tAttrs) {
return tAttrs.templateUrl;
}
};
});
Thank you #westor you saved my day. I just updated my angular and started getting this issue, spent a lot of time fixing it, then I found this. So thought of giving some examples.
Using $onInit in your controller function for binding scope
controller: function () {
this.$onInit = function () {
// your business logic
};
};
In my app, I was binding through scope
myApp.directive('pendingRequests', function () {
return {
scope: {
item:'='
},
bindToController: true,
controller: controller,
controllerAs: 'vm',
controller: function ($scope) {
console.log(this.item); // doesn't logs
this.$onInit = function () {
console.log(this.item); // logs your other directives object
};
}
}
});
using require
myApp.directive('pendingRequests', function () {
return {
scope: {},
bindToController: {},
controller: controller,
controllerAs: 'pendingReqCtrl',
require: {
parent: '^otherDirective'
},
controller: function ($scope) {
console.log(this.parent); // doesn't logs
this.$onInit = function () {
console.log(this.parent); // logs your item object
};
}
}
});
Did you try $onInit to initialize your code?
angularjs doc says this:
Deprecation warning: although bindings for non-ES6 class controllers
are currently bound to this before the controller constructor is
called, this use is now deprecated. Please place initialization code
that relies upon bindings inside a $onInit method on the controller,
instead.
I updated to 1.6.2 and I had to put my code into $onInit method to access the bindings.
What is the better approach to get access to scope created by ng-if in my directive (without $parent.params.text):
<span ng-if="params" uib-tooltip="{{params.text}}"></span>
.directive('myDirective', functions(){
return {
templateUrl: 'template.html',
replace: true,
$scope: {
data: '='
},
controller: function(){
if (data) { //some logic
$scope.params.text = 'text'
}
}
}
})
I've noticed that I don't have to use $parent if my variable is nested inside an object.
For example:
controller
$scope.params = { ... }
view ng-if="params"
Won't work, but:
controller
$scope.something_here = {};
$scope.something_here.params = { ... }
view ng-if="something_here.params"
would work. I believe Angular preserves the scope if the key you're trying to access is part of an object. Give it a try!
You could use the controllerAs syntax.
add
controllerAs: "vm",
bindToController: true
as properties in your directive definition and replace $scope with vm.
Then refer to vm.params.text inside the ng-if
Using Angular I created a directive like this:
angular
.module('my-module', [])
.directive('myDirective', function () {
return {
restrict: 'E',
templateUrl: currentScriptPath.replace('.js', '.html'),
scope: {
scenarios: '='
},
controller: MyDirectiveController,
controllerAs: 'vm',
bindToController: true,
replace: true
}
});
MyDirectiveController:
MyDirectiveController.$inject = ['$scope'];
function MyDirectiveController($scope) {
var vm = this;
vm.scenarios = $scope.scenarios;
}
My directive HTML template is this:
<div>{{vm.scenarios[0].name}}</div>
In my parent view HTML I'm using the directive this way:
<my-directive scenarios="vm.scenarios"></my-directive>
The parent controller has a property:
vm.scenarios = [] // could be [{ name : "test"}]
As the vm.scenarios of the parent controller gets set after an $http call it is not available when the vm.scenarios of the directive controller is bound to the $scope.scenarios and it doesn't get updated when the parents controller vm.scenarios gets populated eventually.
When adding this to my directives controller, it works but the solution seems wrong to me:
$scope.$watch('scenarios', function(newValue) {
if (newValue !== undefined) {
vm.scenarios = $scope.scenarios;
}
});
This is how you should define your directive controller:
MyDirectiveController.$inject = [];
function MyDirectiveController() {
// nothing here
}
You don't need to use $scope because you already bind to controller instance this. It means that scope config
scope: {
scenarios: '='
},
populates controller instance this object, not $scope object, and hence $scope.scenarios is undefined. With vm.scenarios = $scope.scenarios; in controller you just overwrite correct binding with undefined value.
Demo: http://plnkr.co/edit/lYg15Xpb3CsbQGIb37ya?p=preview
I have a directive that only loads a template like this:
app.directive("sidebar", function(RESOURCE_URL) {
return {
templateUrl: RESOURCE_URL + 'sidebar.html'
};
});
The HTML looks like this:
<sidebar ng-controller="sidebarCtrl" ng-show="ready"></sidebar>
And the controller:
app.controller('sidebarCtrl', function($scope, authService) {
$scope.service = authService;
$scope.ready = false;
$scope.user = {};
$scope.$watch('service.getUser()', function(value){
$scope.user = value;
$scope.ready = true;
});
});
Is there a way to simply make the directive use the controller's scope variables? Or what's the common method to use in this case?
There are 2 main ways to access some scope variables inside your directive,
the first one is to enable scope inheritance inside of your directive by using scope: true
app.directive("sidebar", function(RESOURCE_URL) {
return {
scope: true,
templateUrl: RESOURCE_URL + 'sidebar.html'
};
});
which allows you to inherit outer scope inside of your directive, the other way is to attach the controller to your directive by using controller: sideBarCtrl:
app.directive("sidebar", function(RESOURCE_URL) {
return {
controller: 'sideBarCtrl',
templateUrl: RESOURCE_URL + 'sidebar.html'
};
});
or, you can write a service to hold your scope variables and this will allow you access from different parts of your code to the same instance of the variable.
https://docs.angularjs.org/guide/services
In angular.js, can a directive controller access data in a page controller that loaded it?
/**
* Profile directive
*/
.directive('profile', function () {
return {
restrict: 'E',
replace: true,
templateUrl: '/partials/users/_profile.html',
scope: {
user: '=',
show: '=?'
},
controller: function($scope, $rootScope){
$scope.show = angular.isDefined($scope.show) ? $scope.show : { follow: true, link: true };
$scope.currentUser = $rootScope.currentUser;
//do stuff here and then set data in UserShowCtrl
}
};
});
The <profile user="user"></profile> method is called from ./users/show.html which uses the UserShowCtrl controller.
Is there anyway I can use scope on the profile directive with its own controller and still be able to pass data to the UserShowCtrl?
Even though the profile can be isolated to its own functionality, it still needs to set some data on the page level in the UserShowCtrl controller.
Here is where _user.html is loading the <profile> directive. The data for the page is served by the UserShowCtrl and has some collections that get updated when things happen, like following a user.
<ol class="following" ng-show="showConnections == 'following'">
<li ng-repeat="following in user.following">
<profile user="connections[following]"></profile>
</li>
</ol>
Right now there is an ng-click="follow(user)"> that is happening in the _profile.html. I would like to be able to have the directive handle this but also update the collections in the UserShowCtrl.
Edit: here is a plunker demonstrating what I'm trying to do:
http://plnkr.co/edit/9a5dxMVg9cKLptxnNfX3
You need to use a service in order to share any information between controllers, directives, services
something like
angular.module('myapp',[]).
service('myservice',function(){
return {a:'A',b:'B'}
}).
controller('mycontroller',['myservice',function(myservice){
//do someting with myservice
}]).
directive('mydirective',['myservice',function(myservice){
//do someting with myservice
}]);
there controller and directive access the same data through the service
You can access the parent scope from your directive with $scope.$parent.myvar.
myvar will be resolved in parent scope, which means prototypical scope inheritance is used to resolve the variable.
However, this does not guarantee that myvar is coming from the same scope as UserShowCtrl since its possible that any scope in between the 'profile' directive and UserShowCtrl's scope may override 'myvar'.
A better solution would be to use directive-to-directive communication. There are generally two ways for directives to communicate:
Through attributes passed into your directive. You've already used this method to import 'user' and 'show' from parent scope into your directive's isolated scope.
Requiring another directive. When you use 'require: ^UserShow', you are specifying that your 'profile' directive requires another directive as a dependency. The '^' means that it will search for the directive on the current element, or any parent element further up the DOM tree. UserShow's controller is then passed to your link function:
.directive('UserShow', function () {
return {
restrict: 'E',
controller: function($scope){
$scope.myvar = 'test';
this.setMyVar = function(var) {
$scope.myvar = var;
}
}
};
});
.directive('profile', function () {
return {
restrict: 'E',
replace: true,
templateUrl: '/partials/users/_profile.html',
require: '^UserShow',
scope: {
user: '=',
show: '=?'
},
controller: function($scope, $rootScope){
},
link: function(scope, element, attr, UserShowCtrl) {
UserShowCtrl.setMyVar('hello world!);
}
};
});
HTML:
<user-show>
<profile>...</profile>
</user-show>
I am not quite sure what your after.
You are already having 2 two-way data bindings, which means that if you change user in your directive, that will also flow to the outside scope.
So you already have a solution in front of you...
So if that is not "good enough", there is something missing in your question.
Here is an illustration: http://plnkr.co/edit/qEH2Pr1Pv7MTdXjHd4bD?p=preview
However, if you use something in your outside template that creates a child scope, binding it as "value" there is NOT enough, you need to have a . in there.
But that is where there is missing something to the question, if you share your show.html I may be able to find where the scope breaks apart and explain why...
Relevant Source from demo.js:
app.directive('profile', function () {
return {
restrict: 'E',
replace: true,
template: '<div><input type="text" ng-model="user"></input></div>',
scope: { //defines an isolate scope.
user: '=',
show: '=?'
},
controller: function($scope, $rootScope){
$scope.show = angular.isDefined($scope.show) ? $scope.show : { follow: true, link: true };
$scope.currentUser = $rootScope.currentUser;
$scope.user = "Changed by scope!";
//do stuff here and then set data in UserShowCtrl
}
};
});
app.controller('UserShowCtrl', function($scope) {
$scope.value = "Value set outside!";
$scope.alertValue = function() {
alert($scope.value);
}
});
Relevant Source from home.html:
<div ng-controller="UserShowCtrl">
{{ value }}
<profile user="value"></profile>
<button ng-click="alertValue()">ALERT!</button>
</div>