I have this controller with directive inside my html code. The directive have scope value I would like access from the parent controller and show in the html. Hope my example code give you a simple overview of what I'm trying to achieve.
I know this work using $rootScope.showvalue = 'pass text'; in the directive works but don't know if this is the best solution.
app.directive('customdir', [function() {
return {
restrict: "E",
scope: {
showvalue: "="
},
link: function(scope, el, attrs) {
scope.showvalue = 'pass text';
}
};
}]);
app.controller('mycontroller', function($scope) {
alert($scope.showvalue);
});
<html>
<div ng-controller="mycontroller">
<custom-dir></custom-dir>
/* Show scope value from custom dir */
{{showvalue}}
</div>
</html>
Since you're binding the directive's isolate scope showvalue property via bidirectional binding, you can simple pass the parent scope property in as an attribute
<customdir showvalue="showvalue"></customdir>
<p>{{showvalue}}</p>
Demo ~ http://plnkr.co/edit/W5k0hgDElklseOvjwRsS?p=preview
If you want the HTML to be <custom-dir>, you'll need to rename your directive to 'customDir'.
You can pass "showvalue" to the directive via attributes.
You can set value through $parent property of directive scope. eg. scope.$parent.showvalue = 'pass text';
You can use emit event to pass data from child (directive) to parent (controller).
You can directly set value to the controller scope using shared scope. (Do not specify scope property in directive)
Related
Moment i felt i have understood enough about Transclude i came across this statement :
Transclude allows us to pass in an entire template, including its scope, to a directive.
Doing so gives us the opportunity to pass in arbitrary content and arbitrary scope to a directive.
Does this mean, if there is a scope attached to Transclude element and it can be passed on to the directive ? If that's true then am not able to access that scope property inside directive template.
Let me take couple of steps back and explain with code about what am trying to do :
JSFiddle Link
My directive is directive-box and transclude: true is defined in Directive Definition Object(DDO).
Now there is a Child Div, which is the element to be Transcluded
<div ng-controller='TransCtrl'>Inside Transclude Scope : {{name}}</div>
and it has controller TransCtrl attached to it.
Now am trying to access $scope.name property which is part of TransCtrl from directive level after defining this in DDO :
scope: {
title: '#directiveTitle',
name: '='
}
Is this possible ?
This is more like a Parent scope trying to access Child scope property, is this permitted in JavaScript Protoypical inheritance ? Or is there something else i need to know ??
If this is not possible what does first statement mean ?
Transclude allows us to pass in an entire template, including its scope, to a directive.
UPDATE 1 :
My primary concern is Controller should remain with Transclude element, still we should be able to pass its (Transclude element) scope to Directive and then Directive should be able to consume that scope i.e., name from TransCtrl controller .
<div ng-controller='TransCtrl'>Inside Transclude Scope : {{name}}</div>
Above line of code should remain as is.
I may be completely wrong with my question but please let me if this can be accomplished.
The problem seems to be with the way the controller is defined within the ng-transcluded html.
I have made it clearer by using
the bindToController construct
using a controller at the directive level
Refer this fiddle for a working example.
controllerAs: "TransCtrl",
bindToController: true
And your statement, 'Parent scope trying to access Child scope property' is incorrect right? Since we are trying to use the parent scope property, i.e. name from within the child (ng-transcluded content), which is possible with protypical inheritance, and not the other way around.
Does this answer your question: https://jsfiddle.net/marssfa4/4/?
In it I have created a new controller on the outside (effectively replacing the functionality of your rootScope for inside the directive) and I made the directive's controller be set inside your controller template.
The long and short of it is though that you can see that it is possible to transclude html along with its scope even into a directive with its own scope.
The html:
<div ng-app='myApp' ng-controller="OutsideScope">
<h1>{{externalWorld}}</h1>
<div directive-box directive-title='{{directiveWorld}}' name='name'>
<div>Inside Transclude Scope : {{name}}</div>
</div>
</div>
JS (includes Update 1):
angular.module('myApp', [])
.directive('directiveBox', function() {
return {
restrict: 'EA',
scope: {
title: '#directiveTitle',
name: '='
},
transclude: true,
template: '<div ng-controller="TransCtrl">\
<h2 class="header">{{ title }}</h2>\
<div class="dirContent">Directive Element</div>\
<div>Outside Transclude Scope : {{name}}</div>\
<div class="content" ng-transclude></div>\
</div>'
}
})
.controller('TransCtrl', function($scope) {
$scope.name = 'Transclude World'
})
.controller('OutsideScope', function($scope) {
$scope.name = 'External World'
})
.run(function($rootScope) {
$rootScope.externalWorld = 'External World',
$rootScope.directiveWorld = 'Here comes directive'
});
UPDATE 1: JSFIDDLE
I restored the original scope declarations as the scope: false was a mistake.
If I understand your comment correctly you want to leave the controller on the element to be transcluded but still have the {{name}} within that element ignore its immediate controller and use as controller its parent (i.e. the directive's) scope.
The reason I placed the controller within the template directive is because that is the only way to limit the directive's scope on the directive and not its transcluded elements. If you are explicitly placing a controller on an element, then regardless of whether it is contained within a directive with another scope, its closest scope will override whatever scope has been declared on the directive. In other words, regardless of what the directive's scope is, the {{name}} in
<div ng-controller='TransCtrl'>Inside Transclude Scope : {{name}}</div>
will always be whatever $scope.name is in TransCtrl.
I have a case where, I have a directive which get a value form Controller $scope.colorName. Directive bind it one-way and keep this value in isolated scope "colorVar". Template of directive render "colorVar" as {{ colorVar }} .
I need to change the value of "colorVar" in link function of directive. but its not reflecting on UI.
HTML:
<div ng-app="myApp" ng-controller="appCtrl">
{{colorName}}
<my-directive color='{{colorName}}'>
</my-directive>
</div>
JavaScript:
angular.module('myApp', []).controller('appCtrl',function($scope){
$scope.colorName='red';
})
.directive('myDirective', function () {
return {
restrict: 'E',
scope: {
colorVar: '#color'
},
template: '<span> {{ colorVar }} </span><span>{{extra}}</span>',
link:function(scope,element,attrs){
scope.colorVar='orange';
scope.extra='kk';
}
};
});
In Link function I have updated scope.colorVar with 'orange' but is don't reflect on UI, but scope.exta do reflect on UI.
http://jsfiddle.net/ut7628d7/1/
Any idea what am I doing wrong. and why this is happening and how to achieve it ?
Thanks.
It depends on whether you want the directive's updated value to also change the original value in the controller or not:
Modify the original variable
If you want the directive to modify the original colorName in the controller, pass colorName by reference and use a two-way binding in the template:
<my-directive color='colorName'> <!-- instead of color='{{colorName}}' -->
and in the directive:
scope: {
colorVar: '=color' // instead of '#color'
},
http://jsfiddle.net/9szjpx9d/
The output from this will be "orange orange kk" because it's the same object throughout, so when the directive changes it to 'orange' it will affect both places.
Copy the variable
If you want the directive to only modify its own value, and output "red orange kk", then keep an attribute binding as you have now, but delay the directive link function by one tick using $timeout, so that the value it sets on scope will overwrite the value received via the directive attribute:
$timeout(function() {
scope.colorVar = 'orange';
scope.extra = 'kk';
});
Meanwhile the separate original color value will remain untouched in the controller, because it was passed to the directive as a string rather than as an object reference.
http://jsfiddle.net/mpb2cuaj/
If you want to be able to change the color form inside your directive then use a two way binding. e.g.
scope: {
colorVar: '=color'
}
HTML
<my-directive color='colorName'></my-directive>
http://jsfiddle.net/0he428hw/
If a directive has an isolated scope, will the controller field be taken into account ?
If yes, how does exactly the directive access this controller ?
Yes, the isolated scope has nothing to do with the controller. Your problem is more of how to work with the controller (it use doesn't change in regard of the scope type).
A controller is useful when you want a directive to be required. If you have a directive called menu and another directive called menu-item and you want for example register all your menu-item in the menu directive, you create a controller.
When your menu-item does a require: 'menu' what it requires is the menu controller, not the directive itself.
Then you can have a directive like:
angular.module('app').directive('menu', function() {
return {
scope: {},
controller: function($scope) {
$scope.foo = "foo";
this.register = function(scope) {
// register child here
};
}
});
$scope.foo can be accessed by menu template, but this.register can't.
When you require menu in your menu-item you can't access $scope.foo but you can access this.register.
TL;DR; Scope type and having a controller are not related.
Example: http://plnkr.co/edit/fzbSnhxN9rp4Ct5kvB9i?p=preview
I have directive myDirective, that has an two-way binding isolate scope. When the user clicks a button, I want to change the isolate scope to be a value. I thought isolate scopes were bound to the $scope, but I am wrong. How do I 'grab' and interact with that isolate scope? Are they not attached to the directive controller's scope?
angular.module("app", [])
.controller("myCtrl", function($scope){
$scope.ctrlTwoway = "Eggs";
})
.directive("myDirective", function(){
return {
scope: {
twoway: =
},
template: "<button ng-click="changeTwoway()">Change two way isolate scope</button>",
controller: function($scope, $element, $attrs){
$scope.changeTwoway = function(){
// get twoway from isolate scope, and update the value with "bacon"
// $scope.twoway = "bacon" doesn't work
// nor does $attrs.twoway = "bacon" work, either :(
};
}
}
});
And the HTML
...
<div my-directive twoway="{{ctrlTwoway}}"></div>
Current value: {{ctrlTwoway}}
I created a plunker with working version.
You don't need to put {{variable}} on on the twoway="". Just change to twoway="ctrlTwoway" to work.
Another thing is that the way that you declare the binding. You are using = instead of '='.
Another thing is: try to use the link function instead of controller function in directives. It's a good practice and the right place if you want to manipulate DOM elements.
Source
I hope it helps.
I'd like to use a directive, transclude content, and call directive's controller method within the transcluded part:
<mydirective>
<div ng-click='foo()'>
click me
</div>
</mydirective>
app.directive "mydirective", ->
return {
restrict: 'EACM',
transclude: true
template: "<div ng-transclude></div>"
scope: { } #required: I use two way binding on some variable, but it's not the question here
controller: [ '$scope', ($scope)->
$scope.foo = -> console.log('foo')
]
}
plunkr here.
How can I do that please?
I have a different answer, which is not a hack and I hope it will be accepted..
see my plunkr for a live demo
Here is my usage of the directive
<div custom-directive custom-name="{{name}}">
if transclude works fine you should see my name right here.. [{{customName}}]
</div>
Note I am using customName within the directive and I assign it a value as part of the directive's scope.
Here is my directive definition
angular.module('guy').directive('customDirective', function($compile, $timeout){
return {
template : '<div class="custom-template">This is custom template with [{{customName}}]. below should be appended content with binding to isolated scope using the transclude function.. wait 2 seconds to see that binding works</div>',
restrict: 'AC',
transclude: true,
scope : {
customName : '#'
},
link : function postLink( scope, element, attrs, dummy, transcludeFn ){
transcludeFn( scope, function(clone, innerScope ){
var compiled = $compile(clone)(scope);
element.append(compiled);
});
$timeout( function(){
scope.customName = 'this stuff works!!!';
}, 2000);
}
}
});
Note that I am changing the value on the scope after 2 seconds so it shows the binding works.
After reading a lot online, I understood the following:
the ng-transclude directive is the default implementation to transclusion which can be redefined per use-case by the user
redefining a transclusion means angular will use your definition on each $digest
by default - the transclusion creates a new scope which is not a child of the isolated scope, but rather a sibling (and so the hack works). If you redefine the transclusion process you can choose which scope is used while compiling the transcluded content.. -- even though a new scope is STILL created it seems
There is not enough documentation to the transclude function. I didn't even find it in the documentation. I found it in another SO answer
This is a bit tricky. The transcluded scope is not the child of the directive scope, instead they are siblings. So in order to access foo from the ng-click of the transcluded element, you have to assign foo to the correct scope, i.e. the sibling of the directive scope. Be sure to access the transcluded scope from the link function because it hasn't been created in controller function.
Demo link
var app = angular.module('plunker', []);
app.directive("mydirective", function(){
return {
transclude: true,
restrict: 'EACM',
template: "<div> {{ name }} <br/><br/> <div ng-transclude> </div></div>",
scope: { },
link: function($scope){
$scope.name = 'Should change if click below works';
$scope.$$nextSibling.foo = function(){
console.log('foo');
$scope.name = 'it works!';
}
}
}
})
Another way is assigning foo to the parent scope because both prototypally inherits from the parent scope, i.e.
$scope.$parent.foo = ...
Technically, if you remove scope: { }, then it should work since the directive will not create an isolated scope. (Btw, you need to add restrict: "E", since you use the directive as element)
I think it makes more sense to call actions defined in parent scope from directive rather than call the actions in the directive from parent scope. Directive should be something self-contained and reusable. The actions in the directive should not be accessible from outside.
If you really want to do it, you can try to emit an event by calling $scope.$broadcast(), and add a listener in the directive. Hope it helps.