I'm trying to access the scope of my parent controller (that's using controller as syntax) from my child controller (that's also using controller as).
I've tried using bindToController: true as well as simply declaring scope: true in the directive declaration, but I can't seem to get access to it.
angular.module("testapp").directive("testdirective", function () {
return {
restrict: 'E',
templateUrl: '../Scripts/AngularJS/Partials/testdirective.html',
scope: true,
bindToController: true
};
});
But when printing this from the controller I can't see anything from the parent scope:
function testcontroller() {
var self = this;
console.log(self);
}
I know that using $scope could be an easy solution, however this goes against all correct practices I've read and I don't want to mix the two ways of doing things.
The point of bindToController: true is to use it with isolated scope, because its binds the properties of the scope that was passed to the isolated controller, so you wouldn't need to be using '$scope' to access the passed in values.
how about using require?
Todd Motto explains it excellent .
Related
In trying to get a grasp on creating my own AngularJS directives, I have an example that does everything I need, but realize that in borrowing from various examples, I now can create functionality for the directive's view in both the controller as well as the link.
It seems that I could get rid of the controller all together and just put everything into link, or is there something that I can do with the controller that I can't do with link?
http://jsfiddle.net/edwardtanguay/gxr49h96/6
.directive('itemMenu', function () {
var controller = function ($scope) {
var vm = this;
vm.addItem = function () {
$scope.add();
vm.items.push({
'kind': 'undefined',
'firstName': 'Joe',
'lastName': 'Newton',
'age': Math.floor(Math.random() * 60) + 20
});
};
// DOES THE SAME AS THE FUNCTION DEFINED BELOW IN LINK
// $scope.convertToInternal = function(item) {
// item.internalcode = 'X0000';
// item.kind = 'internal';
// };
};
return {
restrict: 'A',
scope: {
item: '=',
add: '&'
},
controller: controller,
controllerAs: 'vm',
bindToController: true,
template: '<div ng-include="getTemplateUrl()"></div>',
link: function (scope, element, attrs) {
scope.getTemplateUrl = function () {
switch (scope.item.kind) {
case 'external':
return 'itemMenuTemplateExternal';
case 'internal':
return 'itemMenuTemplateInternal';
default:
return 'itemMenuTemplateUndefined';
}
};
scope.convertToInternal = function(item) {
item.internalcode = 'X0000';
item.kind = 'internal';
};
},
};
})
You may find a lot of watery rants about controller vs link, most of them contain the information from $compile service documentation.
Answering the question directly,
controllers from other modules/files can be plugged into the directive via Angular DI with controller: 'Controller'
controller can be injected with dependencies while link has fixed arguments list of and gets by with directive's dependencies
controller kicks in before link, so it can prepare the scope for linking or recompile the element on some condition ASAP
controller function has this, its code appearance complies to other OOP-like ES5 code, and the methods can be easily transferred between other code parts, e.g. service service or third-party code
as the result, controllers are suited to be defined as ES2015 or TS classes.
directive's controller can be required by child directive and provides convenient one-way interaction between those two
controller makes use of bindToController: true + controllerAs: 'vm' and implements $scope.vm recipe (particularly useful to fight JS prototypal inheritance) while keeping this syntax
bindToController object value provides attribute bindings for inherited scope: true scope, no more $attr.$observe
bindToController object value provides further granularity for isolated scope. If there are certain attributes that should be bound to the controller and accessed with require, it can be done now
Which code goes to controller and which to link is more delicate question.
It is conventional to use Angular controllers as view models, as in MVVM (hence the controllerAs: 'vm' convention), so if there is a job for controllers (i.e. binding the services to scope values or setting up scope watchers) give it to them, leave the rest to link.
Since $attrs, $element and $transclude local dependencies should be injected into controller explicitly, the one may consider it a sign to skip them ($scope should be injected too for $scope.$ methods, but we will just ignore this fact).
There are some non-religious concerns about the job that should be done by link and not by controller. required controllers aren't available in controller itself, so this kind of directive interaction takes place in link. Since controller launches at earlier compilation phase than link, bound attribute values won't be interpolated yet, so the code that depends on these scope values goes to link. The same applies to other DOM-related code, it goes to link for a reason.
It is mostly the matter of proper code style and not real necessity. As of the code in the example, all scope stuff is controller-friendly, I don't think that link should be there at all.
I have a form with a ton of duplicate functionality in 2 different Controllers, there are slight differences and some major ones in both.
The form sits at the top of a products view controller, but also inside of the products modal controller.
Test plunker: http://plnkr.co/edit/EIW6xoBzQpD26Wwqwwap?p=preview
^ how would you change the string in the console.log and the color of the button based on parent scope?
At first I was going to create a new Controller just for the form, but also the HTML was being duplicated, so decided to put that into a Directive, and just add the Controller code there.
My question now is this: How would I determine which parent scope the form-directive is currently being viewed in? Because depending on the parent scope the functions/methods behave differently.
So far I've come up with this:
.directive('productForm', function() {
return {
templateUrl: "views/products/productForm.html",
restrict: "E",
controller: function($scope) {
console.log('controller for productForm');
console.log($scope);
console.log($scope.$parent);
/*
If parent scope is the page, then this...
If parent scope is the modal then this instead...
*/
}
}
});
However it's giving me back $parent id's that look like 002 or 00p. Not very easy to put in if / else statements based on that information.
Have you guys run into this issue before?
You can define 'saveThis' in your controller and pass it to directive using '&'
scope: {
user: '=',
saveThis : '&'
},
please see demo here http://plnkr.co/edit/sOY8XZtEXLORLmelWssS?p=preview
That gives you more flexibility, in future if you want to use saveThis in another controller you can define it inside controller instead adding additional if statement to directive.
You could add two way binding variables in the directive scope, this allows you to specify which Ctrl variable gets bound to which directive variable
<my-directive shared="scopeVariable">
this way you achieve two way binding of the scopeVariable with the shared directive variable
you can learn more here
I advice against this practice and suggest you to isolate common logics and behaviours in services or factories rather than in directives
This is an example of a directive that has isolated scope and shares the 'title' variable with the outer scope.
You could declare this directive this way:
now inside the directive you can discriminate the location where the directive is defined; just replace the title variable with a location variable and chose better names.
.directive('myPane', function() {
return {
restrict: 'E',
scope: {
title: '#'
},
link: function(scope, element, attrs, tabsCtrl) {
},
templateUrl: 'my-pane.html'
};
});
I'm now creating a nested controller, with the parent controller passing a variable in, like this:
app.directive('entity', function() {
return {
restrict: 'E',
controller: 'EntityShowController',
templateUrl: '/show.html',
controllerAs: 'showCtrl',
scope: {
entity: '=' // two way binding parent/child
}
};
})
And in the template I have:
<entity entity="parent.getSelected()"></entity>
Now in the child controller I can do this:
app.controller('EntityShowController', function($scope) {
// this is what I should do to access the passed in two-way sync entity
console.log($scope.entity);
// this is what I like to achieve
this.entity = $scope.entity;
$scope.entity=null;
}]);
Is it possible to set a controller local data (this property) to track the parent controller data (the $scope property)?
I know that I can implement a setEntity method combined (e.g.) with ng-click but this is not exactly what I'm trying to achieve.
Provided you are using 1.3.x version of angular you can set bindToController flag on the directive settings to say bind the 2-way bound scope properties to the controller instance, if you are below 1.3.x this options is not available and you would need to either directly work on scope or you would need to establish a synchronization mechanism to sync between controller instance and scope property.
.directive('entity', function() {
return {
restrict: 'E',
controller: 'EntityShowController',
templateUrl: '/show.html',
controllerAs: 'showCtrl',
bindToController:true,
scope: {
entity: '=' // two way binding parent/child
}
};
})
The problem
Given the following directive
angular.module('sandbox.directive.controllers', [])
.directive('simpleDirective', function() {
return {
restrict: 'E',
replace: true,
scope: {user: '='},
controller: 'simpleController',
templateUrl: 'directive-controllers/templates/simpleDirective.html'
};
})
.controller('simpleController', function($scope, $element) {
$scope.title = "Hello from the controller!";
});
Why does this test fail?
it('should allow us update when we change the title', function() {
element.scope().title = "Marco Polo!";
element.scope().$digest();
expect(element.html()).toContain('Marco Polo!');
});
My full implementation can be seen here and the tests here.
My understanding
I am currently trying to get a better understanding of scope within directives. I have written a few tests which illustrate most of the use cases quite clearly. However I am having issues with how a directives controller relates to the isolated scope.
My understanding was that the scope accessible within the controller is a reference to the scopes directive.
Please help
I understand the issue with two-way bindings on primitives, however this is within the isolated scope and not the parent scope. Any help to get a better understanding of this would be appreciated.
There appears to be an element.isolateScope() property.
The two-way binding assigned for a user was passing as the parent scope was returned from element.scope(), therefore when user was changed the binding was in affect and the value was updated within the isolated scope.
The element.isolateScope() returns the scope when it is isolated(as it is in this case) and allows me to update this value correctly.
The element.scope() and element.isolateScope() functions are explained here.
How do I make controller functions visible to a directive? Do I attach the methods to the scope, and inject the scope into the directive? Is it a good idea in the first place? I want to manipulate model data from within the UI.
It really dependes on what you want to do.
Since you want to access the controller's scope from the directive, I suggest you declare your directive with it's scope shared with the parent controller by setting it's scope prop to false:
app.directive('directiveName', function() {
scope: false,
link: function(scope) {
// access foo from the controler's scope
scope.foo;
}
});
This is a nice example with how directives can be hooked up to a controller
http://jsfiddle.net/simpulton/GeAAB/
DIRECTIVE
myModule.directive('myComponent', function(mySharedService) {
return {
restrict: 'E',
controller: function($scope, $attrs, mySharedService) {
$scope.$on('handleBroadcast', function() {
$scope.message = 'Directive: ' + mySharedService.message;
});
},
replace: true,
template: '<input>'
};
});
HOOKUP IN CONTROLLER
sharedService.broadcastItem = function() {
$rootScope.$broadcast('handleBroadcast');
};
VIEW
<my-component ng-model="message"></my-component>
Adding to #grion_13 answer, scope:true would also work since it creates a new scope that is child of the parent scope so has access to parent scope data.
But a true reusable directive is one which get it input data using isolated scope. This way as long as your html+ controller can provide the right arguments to the directive isolated scope, you can use the directive in any view.