See codepen.
What do I have to do to make scope.test visible in my HTML when I give my directive an isolated scope, replacing "scope: false" with "scope: {}"?
My directive:
angular
.module("MyApp", [])
.directive("myDir", () => {
return {
scope: false,
restrict: "A",
link: (scope, element) => {
scope.test = 'my test';
}
};
});
My HTML:
<div ng-app="MyApp">
<div my-dir>{{test}}</div>
</div>
No Controller, only link function in my directive.
Earlier it had work because you had scope: false (shared scope).
In your case adding scope: {} to directive wouldn't reflected test value changes on screen. Because when scope: {} isolated scope created in directive, it binds that scope to the directive template if it present. In your case you don't have any template in your directive.
If you wanted to see input value you could change it by
Either using $parent convention like scope.$parent.test = 'my test'
OR shift the {{test}} inside directive template so that isolated scope will get compiled with directive template.
In basic words scope:false means - directive takes the same scope of controller from where it was called.
On other hand scope:{} is a isolate scope and it uses internal scope of directive and its effected on directive template only
You can try to play with ng-transclude something like:
<div my-dir>{{$parent.test}}</div>
and:
.directive("myDir", () => {
return {
scope: {},
transclude: true,
restrict: "A",
template: '<div ng-transclude></div>',
link: (scope, element) => {
scope.test = 'my test';
}
};
});
Codepan Demo
Related
I have a tab-content component which gets a dynamic url from the controller
<tab-content url="{{tabContentUrl}}"></tab-content>
Directive of the component:
myApp.directive("tabContent", () => {
return {
restrict: "E",
scope: true,
link: (scope, element, attrs) => {
scope.getContentUrl = () => {
return attrs.url || "views/new-week-stats.html";
}
},
template: "<div ng-include='getContentUrl()'></div>"
}
});
At this point, it can only inject the initial values of the $scope in the controller. When something is changed in the scope it is not refreshed in the DOM. How can I enable two way binding in the directive?
ngInclude directive already does what you want for you, just use the directive scope 2 way bindings.
myApp.directive("tabContent", () => {
return {
restrict: "E",
scope: {
url: '='
},
template: '<div ng-include="url || 'views/new-week-stats.html'"></div>'
}
});
That way, any change in the url on the tabContent element will trigger a change in the ngInclude.
Quick Answer
You have two options.
Same scope as parent with two way binding (scope:false):
HTML:
<tab-content ng-model="tabContentUrl"></tab-content>
JS:
myApp.controller("MainCtrl", function($scope){
$scope.default = "views/new-week-stats.html";
$scope.tabContentUrl = "views/example.html";
});
myApp.directive("tabContent", () => {
return {
restrict: "E",
scope: false,
template: "<div ng-include='tabContentUrl || default'></div>"
}
});
Isolated scope with two way binding (scope:{url: '='}):
HTML:
<div ng-controller="MainCtrl">
<tab-content url="tabContentUrl" default="views/new-week-stats.html">
</tab-content>
</div>
JS:
myApp.controller("MainCtrl", function($scope){
$scope.tabContentUrl = "views/example.html"
});
myApp.directive("tabContent", () => {
return {
restrict: "E",
scope: {
url: '=',
default: '#'
},
template: "<div ng-include='url || default'></div>"
}
});
Explanation:
All directives have a scope associated with them. They use this scope for accessing data/methods inside the template and link function. By default, unless explicitly set, directives don’t create their own scope. Therefore, directives use their parent scope (usually a controller) as their own.
In your code on line 4 you specified scope: true,
When a directive scope is set to “true”, AngularJS will create a new scope object and assign to the directive. This newly created scope object is prototypically inherited from its parent scope (the controller scope where it’s been used).
In total there are three options:
Same scope as parent with two way binding.
scope: false,
Isolated scope but a one way binding.
scope: true,
Isolated scope with options.
scope: {
"#" // Creates a copy as text
"=" // Direct model with two-way binding
"&" // For passing functions
}
add a watcher in your directive:
HTML
<tab-content url="tabContentUrl"></tab-content>
JS
myApp.directive( "tabContent", () => {
return {
restrict: "E",
scope : true,
link : ( scope, element, attrs ) => {
scope.$watch( () => scope.$eval( attrs.url ), newValue => {
scope.url = newValue;
} );
},
template: "<div ng-include='url'></div>"
};
} );
You can also use $parse instead of $eval, but you don't got an isolated scope, so it should be ok, as long as you don't overwrite the parameters in your directive
You can use an observer to dynamically change the scope value inside your directive.
link: function(scope, element, attrs){
attrs.$observe('tabContentUrl', function(val) {
scope.tabContentUrl = val;
})
}
I am very new to the concepts of angularJS. The problem I am facing is I have declared a variable $scope.myVariable = true in my controller. I need to toggle the the value of $scope.myVariable from the directive. Is it possible to do that.. if yes how??
Please help..
From a directive, you can access the scope through the link function:
app.directive('myDirective', function() {
return {
restrict: 'E',
transclude: true,
scope: {},
controller: 'MyController',
templateUrl: 'my-template.html',
link: function (scope, element) {
scope.name = 'Jeff';
}
};
});
See https://docs.angularjs.org/guide/directive
If you want to use Angularjs Directive, you can use 3 methods of it's scope
1) true (inherit)
2) false (not inherit)
3) {} (Isolated)
if you use 1) method, you have to work with it's parent scope, because that variable is in parent scope
if you use 2) method you don't have to do anything else.You are working with current scope
if you want more with examples here is a cool link
http://www.w3docs.com/snippets/angularjs/bind-variable-inside-angularjs-directive-isolated-scope.html
You can pass the reference of "myVariable" from your main controller to the directive like this :
//index.html
<my-directive myvar="myVariable"></my-directive>
//myDirective.js
app.directive('myDirective', [function () {
return {
restrict: "E",
require: 'myvar',
scope: {
myvar:"=" //"=" mean, you can set the variable (your toggle function needs that)
},
templateUrl: "./directives/myDirective/myDirectiveView.html",
controller: "myDirectiveController as myDirectiveCtrl"
}
}]);
With this solution, myvar in the directive contains the reference to myVariable of the main controller and you can use it with $scope.myvar in the directive.
EDIT :
for example, if you have a list for object contains src and compression var like this :
{ src:"path/to/img", compressed:false}
<div ng-repeat="imgObj in imgObjList">
<my-directive myvar="imgObj"></my-directive>
</div>
In the my-directive view :
<img ng-src="myvar.src" ng-show="myvar.compressed == true"/>
In your my-directive Controller :
you can set myvar.compressed to true when encoding is finished
I've been looking at isolateScope directives, to get a better understanding of how they interact with other nested isolateScope directives, so put together a plnkr to test a few things out.
http://plnkr.co/edit/7Tl7GbWIovDSmVeKKN26?p=preview
This worked as expected. As you can see each directive has it's own separate template.
I then decided to move the html out of each directive and into the main html file, but it's stopped working? I can see that the e1Ctrl is on the scope of the directive, but it doesn't appear to be available when the enclosed markup is processed.
http://plnkr.co/edit/33Zz1oO4q7BVFw0cMvYa?p=preview
Can someone please tell me why this is happening?
----------- UPDATE -----------
I've simplified the non-working plunker to clearly show the problem. The directive uses the controllerAs syntax and the e1Ctrl is clearly set on its $scope (see the console output).
http://plnkr.co/edit/g2U2XskJDwWKuK3gqips?p=preview
angular
.module('app', [])
.controller('AppCtrl', AppCtrl)
.directive('elementOne', elementOne)
.controller('E1Ctrl', E1Ctrl)
function AppCtrl() {
var vm = this;
vm.data = [
{
label: 'one'
},
{
label: 'two'
},
{
label: 'three'
},
{
label: 'four'
}
];
vm.callback = function() {
console.log('called app callback');
};
}
function elementOne() {
return {
restrict: 'E',
scope: {
data: '=',
handler: '&'
},
controller: 'E1Ctrl',
controllerAs: 'e1Ctrl',
bindToController: true
}
}
function E1Ctrl($scope) {
console.log('E1Ctrl', $scope);
var vm = this;
vm.click = function() {
vm.handler();
};
vm.callback = function() {
console.log('called e1 callback');
};
}
Mark up:
<body ng-app="app" ng-controller="AppCtrl as appCtrl">
<ul>
<div ng-repeat='item in appCtrl.data'>
<element-one data='item' handler='appCtrl.callback()'>
<button ng-click='e1Ctrl.click()'>e1: {{e1Ctrl.data.label}}</button>
</element-one>
</div>
</ul>
</body>
------ Transclusion solution -----
http://plnkr.co/edit/l3YvnKOYoNANteNXqRrA?p=preview
function elementOne() {
return {
restrict: 'E',
transclude: true,
scope: {
data: '=',
handler: '&'
},
controller: 'E1Ctrl',
link: function($scope, $element, $attr, ctrl, transclude) {
transclude($scope, function(clone){
$element.append(clone);
});
}
}
}
There's a difference in scope for HTML in the template of the directive and HTML that in a subtree of the directive. The former is evaluated in the context of the scope of the directive; the latter - in the scope of the View.
If the directive has an isolate scope - scope: {}, then the subtree doesn't see it. If it uses scope: true, then it creates a new child scope for the subtree which prototypically inherits from the View's scope.
Consider the following:
// isolate scope
app.directive("foo", function(){
return {
scope: {},
link: function(scope){
scope.name = "foo";
}
}
});
// child scope
app.directive("bar", function(){
return {
scope: true,
link: function(scope){
scope.name = "bar";
}
}
});
app.controller("Main", function($scope){
$scope.name = "main";
});
Here's how the View would render:
<body ng-controller="MainCtrl">
<pre>in main: {{name}} will render "main"</pre>
<foo>
<pre>in subtree of foo: {{name}} will render "main"</pre>
</foo>
<bar>
<pre>in subtree of bar: {{name}} will render "bar"</pre>
</bar>
</body>
In your case, the subtree is evaluated in the scope of the View - not the directive, and that is why it doesn't work as you expected.
plunker
EDIT:
In some cases it may makes sense to evaluate the subtree in the context of the isolate scope of the directive. I've seen this used with directives that allow templating. But be careful with this because the author of the Main View should not know (too much) about the inner workings of the directive (i.e. what is exposed in the inner scope). This would also be difficult to read because you would see variables that do not make sense in the outer scope.
To evaluate the subtree in the isolate scope of the directive, the directive needs to $compile the subtree and link it against its scope.
Here's a directive that allows the user to provide a template for each item in the list. The item variable is not defined in the main scope, and only makes sense in the context of the directive's isolate scope:
<list src="items">
<item-template>
{{item.a}} | {{item.b}}
</item-template>
</list>
The directive 'list' is below:
app.directive("list", function($compile){
return {
scope: {
src: "="
},
link: {
pre: function(scope, element){
var itemTemplate = element.find("item-template");
element.empty();
var template = angular.element('<div ng-repeat="item in src"></div>')
.append(itemTemplate.html());
element.append(template);
$compile(element.contents())(scope);
}
}
}
});
plunker 2
I've written a directive without the transclude option.
But now it would be nice when I could activate the transclude function/option when calling the directive with another attribute or something else if possible.
If that's not possible the only Way I see is, to copy the directive and add the Transclude in the second one, but then I've doubled my code whtat I'm not willing to do.
any Ideas how to optionally activate the transclude in Angular 1.2.x
Edit:
alternate problem is also that I need to set the ng-transclude in my directive Template because its a big one and only a few rows can be replaced by the transclusion content.
You could conditionally modify a template to include ng-transclude in the compile: function.
.directive('foo', function () {
return {
restrict: 'E',
transclude: true,
replace: true,
templateUrl: 'foo.html',
compile: function (element, attrs) {
if (attrs.bar !== undefined) {
element.find('.may-transclude-here')
.attr('ng-transclude', '');
}
return function postLink(scope, element, attrs, controllers) {
scope.listEntries = ['apple', 'banana', 'tomato'];
};
}
}
})
and a html template:
<div class="foo">
<h4>Directive title</h4>
<div class="may-transclude-here" ng-repeat="item in listEntries">
Original content: {{item}}
</div>
<span>blah blah blah</span>
</div>
but contents that are transcluded via ng-transclude will not bind with a scope of each item created by ng-repeat. In case you also need the binding, here is the modified version of ng-transclude that do the correct scope binding.
.directive('myTransclude', function () {
return {
restrict: 'EAC',
link: function(scope, element, attrs, controllers, transcludeFn) {
transcludeFn(scope, function(nodes) {
element.empty();
element.append(nodes);
});
}
};
});
Plunker example: http://plnkr.co/edit/8lncowJ7jdbN0DEowdxP?p=preview
hope this helps.
I'd like to have a directive with an isolated scope, and to set properties to this scope from within the directive. That is to create some environment variables, which would be displayed by other directives inside it, like so:
HTML:
<div environment> <!-- this directive set properties to the scope it creates-->
{{ env.value }} <!-- which would be available -->
<div display1 data="env"></div> <!-- to be displayed by other directives (graphs, -->
<div display2 data="env"></div> <!-- charts...) -->
</div>
JS:
angular.module("test", [])
.directive("environment", function() {
return {
restrict: 'A',
scope: {},
link: function(scope) {
scope.env = {
value: "property set from inside the directive"
};
}
};
})
.directive("display1", function() {
return {
restrict: 'A',
require: '^environment'
scope: {
data: '='
},
link: function(scope, elt, attr, envController) {
scope.$watch('data', function(oldV, newV) {
console.log("display data");
});
}
};
})
.directive("display2", function() {
return {/* ... */};
});
But it doesn't work. Here is a Plunker.
If I remove the isolation, it works ok though. What do I do wrong ? Is it a problem of transclusion ? It seems to work if I use a template in the 'environment' directive, but this is not what I want.
Thanks for your help.
Edit: I see this same problem answered here. The proposed solution would be to use a controller instead of a directive. The reason I wanted to use a directive is the possibility to use 'require' in the inner directives, thing that can't be done with ngController I think.
By introducing external templates, I managed to find a working solution to your problem.
I'm quite certain the way you have it set up has worked at some point but I can't be certain about when. The last time I built a directive not reliant on an external markup file, I don't even know.
In any case, the following should work, if you are willing to introduce separate templates for your directives:
app.directive('environment', function () {
return {
restrict: 'A',
templateUrl: 'env.html',
replace: true,
scope: {},
link: function (scope, el, attrs) {
scope.env = {
value: "property set from inside the directive"
};
}
};
});
app.directive('display1', function () {
return {
restrict: 'A',
scope: {
data: '='
},
templateUrl: 'display1.html',
replace: false,
link: function(scope) {
// console.log(scope.data);
}
};
});
And then for your markup (these wouldn't sit in <script> tags realistically, you would more than likely have an external template but this is simply taken from the fiddle I set up).
<script type="text/ng-template" id="display1.html">
<span>Display1 is: {{data}}</span>
</script>
<script type="text/ng-template" id="env.html">
<div>
<h1>env.value is: {{env.value}}</h1>
<span display1 data="env.value"></span>
</div>
</script>
<div>
<div environment></div>
</div>
Fiddle link: http://jsfiddle.net/ADukg/5421/
Edit: After reading that you do not want to use templates (should've done that first..), here's another solution to get it working. Unfortunately, the only one you can go with (aside from a few others, link coming below) and in my opinion it is not a good looking one...
app.directive('environment', function () {
return {
restrict: 'A',
template: function (element, attrs) {
return element.html();
},
scope: {},
link: function (scope, el, attrs) {
scope.env = {
value: "property set from inside the directive"
};
}
};
});
And the markup:
<div environment> {{env.value}} </div>
Fiddle: http://jsfiddle.net/7K6KK/1/
Say what you will about it, but it does do the trick.
Here's a thread off of the Angular Github Repo, outlining your issue and why it is not 'supported'.
I did a small edit to your Plunker
When you create a variable on scope of directive other directives can access it two ways (presented in plunker) either directly or by two-way data binding
HTML:
<body ng-app="test">
<div environment>
{{ env.value }}
<div display1 data="env"></div>
<div display2 data="env"></div>
</div>
</body>
<input type="text" ng-model="env.value"> #added to show two-way data binding work
<div display1 info="env"></div> #changed name of attribute where variable is passed, it's then displayed inside directive template
<div display2>{{env.value}}</div> #env.value comes from environment directive not from display2
</div>
JS
angular.module("test", [])
.directive("environment", function() {
return {
restrict: 'A',
scope: true, #changed from {} to true, each environment directive will have isolated scope
link: function(scope) {
scope.env = {
value: "property set from inside the directive"
};
}
};
})
.directive("display1", function() {
return {
restrict: 'A',
template: '<span ng-bind="info.value"></span>', #added template for directive which uses passed variable, NOTE: dot in ng-bind, if you try a two-way databinding and you don't have a dot you are doing something wrong (Misko Hevry words)
scope: {
info: '=' #set two-way data binding for variable from environment directive passed in 'info' attribute
}, #removed unnecessary watch for variable
};
})
.directive("display2", function() {
return {/* ... */};
});