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.
Related
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 .
just reading a write up from this link http://weblogs.asp.net/dwahlin/creating-custom-angularjs-directives-part-6-using-controllers
it was hard me like novice in ng to understand their code sample. just tell me a example where people would write directive without controller ?
their code
(function() {
var app = angular.module('directivesModule');
app.directive('isolateScopeWithController', function () {
var controller = ['$scope', function ($scope) {
function init() {
$scope.items = angular.copy($scope.datasource);
}
init();
$scope.addItem = function () {
$scope.add();
//Add new customer to directive scope
$scope.items.push({
name: 'New Directive Controller Item'
});
};
}],
template = '<button ng-click="addItem()">Add Item</button><ul>' +
'<li ng-repeat="item in items">{{ ::item.name }}</li></ul>';
return {
restrict: 'EA', //Default in 1.3+
scope: {
datasource: '=',
add: '&',
},
controller: controller,
template: template
};
});
}());
Directive usage:
Attribute: <div isolate-scope-with-controller datasource="customers" add="addCustomer()"></div>
Element: <isolate-scope-with-controller datasource="customers" add="addCustomer()"></isolate-scope-with-controller>
How we can pass customer data directly to directive. basically we have model in controller and populate model and then pass that model data to directive via isolated scope or directive use controller scope. I am very confused the how above code can work, please help me to understand. thanks
The scenario that is being considered implies that the directive will be used in a part of the application, that already has a declared controller, the scope of which contains the properties datasource and add. In turn, new controllers will be instantiated for each of the instances of the directive and will have their own isolate scope.
In reality, it is much more common to create directives that do not have a controller, but rather use the link function. These directives can either rely on the parent controller, sometimes perform DOM manipulation, bind to JS events or simply serve as means to encapsulate part of your application.
You can find a good example of a directive that does not create its own controller here. It is taken from the Angular docs. You will find that it does not even belong to a parent scope in this case meaning that no controller is involved. In reality, an element like this would most probably report to a parent controller, which would then do something with the position.
You can read more about directives, the link function, and how directives work with controllers here.
I am learning angular and I am from jQuery background and facing problem to get hold on angular. so i often stumble to understand many things in angular code.
Just seen the code below and I do not understand what scope is doing in below directives?
But if I remove the scope from below directive then what will not work?
so please help me to understand the usage of scope and its importance with a example if possible. Thanks
<li my-directive price="item.price" ng-repeat="item in products">{{item.name}} — {{item.price}}</li>
myApp.directive('myDirective', function(){
return {
scope: { price: '=' },
require: 'ngModel',
link : function(scope){
console.log(scope.price)
},
controller: function(scope, element, attrs, ngModel){
console.log(ngModel.price);
console.log(scope.price);
}
}
});
In that sample you have scope: { price: '=' }, states that internal scope variable price of the directive is exactly binded to parent's scope. It has access to it and i value changes in parent scope, directive's value of price will change as well.
Remove this line from your directive scope: { ... } and then your directive will not create a new scope. But it will still work - meaning it won't create an error. There are use cases when you need or need not to have isolated scopes.
To figure out better how things work with angular and scopes - please check the following great resources:
angular vs jQuery -
"Thinking in AngularJS" if I have a jQuery background?
$scope itself in angular -
How does data binding work in AngularJS?
various types of scope variables in directives -
What is the difference between '#' and '=' in directive scope in AngularJS?
If you don't mention the scope configuration object your directive will use is parent's scope, but when you mention the scope object it creates its own isolated scope.
So if you don't mention the scope object you will have access to variables from your parent controller directly.
ref : http://www.undefinednull.com/2014/02/11/mastering-the-scope-of-a-directive-in-angularjs/
Consider
angular.module('App').directive('errors',function() {
return {
restrict: 'A',
controller:function() {
var self = this;
self.closeErrors = function() {
self.errors = [];
self.hasErrors = false;
}
},
controllerAs: 'errorsCtrl',
templateUrl: 'errors.html'
}
when called with
<div errors="otherCtrl.errors"></div>
the object errors comes from another controller.
I know i can add
scope: {errors:"="},
and then access it in my controller via
$scope.errors;
but when I assign it to
self.errors = $scope.errors.
self.errors never gets updated when it is changed in the parent.
So my question is, how can I let this work that whenerver my parentcontroller changes the errors object it is also changed in the errorsCtrl.
(Also I do know I can access errors directly in my template without the controller, but I simply want to use my errorsCtrl)
Add bindToController: true to your directive.
http://blog.thoughtram.io/angularjs/2015/01/02/exploring-angular-1.3-bindToController.html
Angular 1.3 introduces a new property to the directive definition
object called bindToController, which does exactly what it says. When
set to true in a directive with isolated scope that uses controllerAs,
the component’s properties are bound to the controller rather than to
the scope.
That means, Angular makes sure that, when the controller is
instantiated, the initial values of the isolated scope bindings are
available on this, and future changes are also automatically
available.
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'
};
});