Here is my Code:
// HTML
<body>
<h1>{{foo.name}}</h1>
<my-directive></my-directive>
</body>
// Scripts
app.directive('myDirective', function() {
return {
restrict: 'E',
replace: true,
scope: true, //**********
template: '<h4>{{foo.name}}</h4>',
controllerAs: 'foo',
controller: fooCtrl,
link: function(scope) {
console.log(scope);
}
}
});
var fooCtrl = function() {
this.name = 'FOO';
}
My Question:
If I use controllerAs syntax and don't set scope: true in myDirective, the controller will become global, so the controller will insert foo.name into Tag. But once I set the scope as true, the controller will only apply on myDirective.
How could this happened? Does a controller inside directive create a new scope that inherits from the surrounding scope? Why it will apply on global?
UPDATE
This is a very silly question, as I always use isolate scope before, so forget about the usage of scope inside directive, read the docs and clearly understand. Thanks for answer
I guess your are asking regarding the scope property in Angular Directives. Also I presume you mean $rootScope by term 'global'. As explained in this guide, In a directive, scope property behaves as follows,
scope.false
The default option which does not create a new scope for a directive
but shares the scope with its parent
scope.true
Creates a new scope but prototypically inherits from the parent scope.
scope: ‘isolate’
Creates an isolated scope which does not prototypically inherit from
the parent scope but you can access parent scope using scope.$parent
Check out the directive documentation and scroll down a bit to the part about the scope option. Setting scope to true creates a new scope that the controller is attached to. Not setting scope defaults it to false, which causes the directive to use the parent scope. That's why the controller is being attached to the parent scope.
Related
"use strict";
angular.module("foo")
.directive("breadcrumbs", function($rootScope, $timeout) {
return {
replace: true,
scope: false,
templateUrl: '/js/shared/directives/breadcrumbs.tpl.html',
link: function(scope, element, attrs, controller, transcludeFn){
console.debug(scope.parent);
...
Sometimes scope.parent is the scope of the current controller, sometimes it's $rootScope. I've discovered that when my custom directive is used like this:
<breadcrumbs></breadcrumbs>
it receives the controller scope, but when used with ng-if:
<breadcrumbs ng-if="searchData"></breadcrumbs>
it receives a new scope nested within the controller scope.
How do I write my directive to always receive the controller scope without resorting to any hackery (such as adding ng-if="true")?
Update: I've corrected the description of the problem. The issue is that I get scopes at different levels of nesting depending on whether ng-if is used, regardless of whether scope: false or scope: true was used.
First of all, if the directive is in context of a controller scope, you'll never receive $rootScope as the scope of directive.
When you use ng-if, you are receiving a prototypically inherited child scope (of the immediate parent scope, in this case the controller) created by ng-if becuase your directive has scope: false (which means pass in the closest scope instead of creating one)
You can always request a prototypically inherited child scope by setting scope:true or an isolated scope via scope:{}.
What ng-if=true dose is create a new scope for your element.
You could achive that by changing the parameter
From
scope: false
To
scope: true
I have been working with isolated scope directives for a little time and a question came in mind watching it's behavior:
Why can't i bind variables that i define inside the directive inherited scope directly to the view?
Let me show an example on this code pen:
http://codepen.io/anon/pen/VLKjrv
When i create a new $scope variable inside the directive controller and i try to bind it on the view, it does not works.
By the other hand, when i bind that variable on a html that comes from the template directive attribute, it does works.
Check out the code:
<body ng-app="isolated-test-app">
<section ng-controller="isolatedTestCtrl">
<article>
<h1>test1</h1>
<div isolated-directive binding-from-attr="test">
<span ng-bind="test"></span>
<span ng-bind="test2"></span>
</div>
<h1>test2</h1>
<div isolated-directive-number-two binding-from-attr="test">
</div>
</article>
</section>
angular.module('isolated-test-app', [])
.controller('isolatedTestCtrl', function isolatedTestCtrl($scope){
$scope.test = 'Binded from parent controller';
})
.directive('isolatedDirective', function isolatedDirective(){
var directive = {
scope: {
bindingFromAttr: '=',
},
controller: function directiveController($scope){
$scope.test2 = 'Binded from directive controller!';
},
};
return directive;
})
.directive('isolatedDirectiveNumberTwo', function isolatedDirective2(){
var directive = {
scope: {
bindingFromAttr: '=',
},
template:'<span ng-bind="bindingFromAttr"></span>\
<span ng-bind="test2"></span>',
controller: function directiveController($scope){
$scope.test2 = 'Binded from directive controller!';
},
};
return directive;
})
test1
Binded from parent controller
test2
Binded from parent controller
Binded from directive controller!
I was expecting the result of test2 on test1.
Why does that happens?
There is a difference between directive template and the directive's element's contents with regards to what scope applies.
In isolate scope (scope: {}) directives, the isolate scope applies to the template, but not to the contents. The contents have the same scope as the directive's parent. Also, note, that the contents would be replaced by the template, if the template is defined. To use the contents in addition to the template requires "transcluding" (transclude: true) (this is, however, outside of scope for this answer).
If you are confused, you could always check $scope.$id to see which scope applies:
<div>parent scope: {{$id}} (will be 2)</div>
<isolate-scope>
contents of isolate scope directive: {{$id}} (will also be 2)
</isolate-scope>
<isolate-scope-with-template>
contents will be replaced with the template
</isolate-scope-with-template>
.directive("isolateScopeWithTemplate", function(){
return {
scope: {},
template: "template: {{$id}} (will NOT be 2)"
}
})
(of course, the actual $id could be different)
In child scope (scope: true) directives, the scope that applies to the content is actually the same that would have applied to the template (same here - the template would replace the contents if it exists, unless you transclude).
Now, to answer your question:
The first <span ng-bind="test2"></span> binds to a non-existent $scope.test2 in the parent scope and so it is empty.
But the <span ng-bind="test2"></span> in the template of isolatedDirectiveNumberTwo binds to the isolate scope of that directive, which defines $scope.test2 = 'Binded from directive controller!'.
This is my guess base on experiment in http://codepen.io/anon/pen/MwjjBw
so for test 1, the directive scope doesnt have test/test2 since the dom object is belong to controller. Hence in order to update it you have to use
$scope.$parent.test2 = "" ;
and for test 2, as the template is created as part of directive hence the dom object is belong to directive and also accessible by controller ( I guess $compile adding this into controller scope/watch).
You can also see that test1 doesnt have any watcher as there is no binding happen.
Now i get the whole picture, as New Dev's answer stated on his answer
In isolate scope (scope: {}) directives, the isolate scope applies to the template, but not to the contents. The contents have the same scope as the directive's parent.
So i have learned that there is a difference between directive contents and template and how the scope is inherited on isolated scopes.
For my application setting the scope to true solved entirely my problem.
Also, kwan245's solution is a real good work-around of this issue.
Both answers cleared my mind, many thanks to New Dev and kwan245 :)
I can't set a scope variable inside my custom directive's controller.
See this plkr:
http://plnkr.co/edit/KVGVxhgRHxkhCLytkLBv?p=preview
Link function prints the variable name but interpolator does not evaluate {{name}}. I set it in the controller of my custom directive with isolate scope. Still for some reason, scope variable does not persist. Is this behavior expected? If so what is the proper way to set scope variables?
Help is appreciated.
Import 'name' from your outside scope into your isolated scope using '=' binding:
// Code goes here
angular.module('test', [])
.directive('nametag', function() {
return {
scope: { name: '='},
controller: function($scope, $attrs, $log) {
$scope.name = 'John Doe';
},
link: function(scope, elem, attrs) {
// elem.text(scope.name);
}
};
});
HTML:
<div ng-app="app" ng-init="name='james'>
<nametag name="name"></nametag>
</div>
When you define an isolated scope for your directive (by specifying scope: {}) you create a private scope that does not prototypically inherit from the parent scope. You can think of an isolated scope as your directive's private sandbox.
Since your directive's scope is isolated, you need to import scope variables from parent scope into your isolated scope through attribute bindings. In this case we are establishing two-way binding between the parent scope variable 'name', and the directive's isolated scope variable with the same name.
Your directive controller assigns the name to the isolated scope of the directive. So you have two possibilities:
Remove the scope: {} from your directive to not create an isolated scope. While this works it is maybe not what you want as your directive modifies the outer scope.
Add a template to your diective containing the <h1>Hello {{name}}</h1>. See this plunker.
Using scope: { ... } in a directive introduces an isolate scope, which does not prototypically inherit from its parent scope. But I have always used it for a different reason: a convenient way to declare HTML attributes with two way data binding:
scope: {
attr1: '=',
attr2: '?='
}
To get a non-isolate scope, you have to use scope: true, which does not offer the opportunity to declare such attributes. I now find myself needing a directive with a non-isolate scope, but with two way binding. What's the best way to achieve this?
Example: My use-case is something like this, in the view of the outer-directive:
<div ng-repeat="e in element">
<inner-directive two-way-attr="e.value"></inner-directive>
</div>
But inner-directive is in the same module as outer-directive. It doesn't need to be encapsulated with an isolate scope. In fact, I need to use $scope inheritance for other purposes, so an isolate scope is not an option. It's just that using an HTML attribute to establish this two-way communication is extremely convenient.
The answer by pixelbits helped me figure this out big time, but taken as a direct answer to my original question, it seems overly complicated. After looking into it, the solution is really quite simple.
Take a directive with an isolate scope like this:
scope: { model: '=myModel' },
link: function(scope, element, attr) {
//...
}
The following is equivalent, except that the scope is not isolate:
scope: true,
link: function(scope, element, attr) {
scope.model = scope.$parent.$eval(attr.myModel);
//...
}
See a working example here: http://jsfiddle.net/mhelvens/SZ55R/1/
Working Demo Here
It is possible to have both a non-isolate scope and an isolate scope in the same directive. You might want to do this, for example, if you have a mix of both non-isolated templates (meaning they should not look for bindings through scope inheritance), and isolated templates (they should look for bindings in its own scope only) and they are both defined in the same directive.
In order to setup both isolate scope and non-isolate scope, you can do the following:
In your directive definition, specify scope=true
In your link function, compile and link your template against the scope parameter. When you do this, the bindings are evaluated against the non-isolate scope (meaning it resolves bindings through prototypical scope inheritance).
link: function(scope, element, attr) {
// this template should look for 'model' using scope inheritance
var template2 = angular.element('<div> Prototypical Scope: {{ model }}</div>');
// add the template to the DOM
element.append(template2);
// compile and link the template against the prototypical scope
$compile(template2)(scope);
}
The advantage of prototypical scope inheritance is that you don't have to explicitly import bindings into your directives' current scope. As long as it is defined in the current scope or any scope higher up the inheritance chain (all the way up to the root scope), the angular run-time will be able to resolve it.
In the same link function, define an isolated scope using scope.$new(true). You can establish a two-way binding of your model by importing a model into your isolated scope - isolatedScope.model = scope.$eval(attr.model):
link: function(scope, element, attr) {
// this template should look for 'model' in the current isolated scope only
var template = angular.element('<div>Isolate Scope: {{model}}</div>');
// create an isolate scope
var isolatedScope = scope.$new(true);
// import the model from the parent scope into your isolated scope. This establishes the two-way binding.
isolatedScope.model = scope.$eval(attr.model);
// add the template to the DOM
element.append(template);
// compile and link the template against the isolate scope
$compile(template)(isolatedScope);
}
The advantage of the isolate scope is that any bindings that exist (ie. are in-scope) are the ones that you explicitly import. Contrast this with non-isolate scope - where the bindings do not need to be explicitly defined on the current scope - it could be inherited from any scope higher up the chain.
I wrote this. You use it like this:
twowaybinder.attach($scope, $attrs.isDeactivated, 'isDeactivated');
.factory('twowaybinder', function ($parse) {
function twoWayBind($scope, remote, local){
var remoteSetter = $parse(remote).assign;
var localSetter = $parse(local).assign;
$scope.$parent.$watch(remote, function (value) {
localSetter($scope, value);
});
$scope.$watch(local, function (value) {
remoteSetter($scope, value);
});
}
return {
attach : twoWayBind
};
});
It will give u true two-way binding from scope values. Note I dont think that $scope.$parent is neccessary, as in an inherited or no scope scenario any expression should resolve on the current scope. You would only need to call $parent in an isolated scope in which case you wouldn't use this, you would use the isolated scope config.
you may use two directives
if gg is an object then "=" points to one place of memory!
angular.module('mymodule', []).directive('a', function($parse, $modal) {
return {
restrict : 'A',
scope : {
gg : "="
},
require : "b",
link : function(scope, element, attrs, bCtrl) {
scope.$watch('gg',function(gg){
bCtrl.setset(scope.gg);
}
}
}
});
angular.module('mymodule').directive('b', function($parse, $modal) {
return {
restrict : 'A',
/*
* scope : { showWarn : "=" },
*/
controller : function($scope) {
$scope.bb = {};
this.setset = function(nn) {
$scope.bb=nn;
};
}
});
I have following example and i want to execute the content of the scope function before returning it but it doesnt
directives.directive('itemElement', function() {
return {
restrict : 'E',
scope : function(scope) {
var itemModelVar = getValue(scope.item.testdata, field.caption);
console.log('test');// this is not executed at all for example
return {
item : '=item',
field : '=field',
schematypes : '=schematypes',
itemmodel : itemModelVar
};
},
templateUrl : 'partials/templates/item-simple.html'
};
});
This leads to itemModelVar being undefined.
AngularJS directive definition does not allow this kind of scope definition. Scope within directives can be assigned in 3 ways.
scope: false
scope: true
scope: {}
In the first scenario, the scope within the directive is the same as the parent scope.
In the second scenario, the parent scope is prototypically inherited in the directive scope.
In the third scenario, the scope is defined is known as isolate scope since here the scope is not inherited but a new identifier which is specific to the directive. Isolate scopes are used for reusable components.
Try and understand the behavior of scope in angularjs.