Make child scope same as parent isolated scope - angularjs

I would like to make a directive that has an isolated scope.
Within this directive are some other directives and those should have the same scope as it parent.
<parent> <!-- Isolated scope -->
<child> </child> <!-- Belongs to the container isolated scope -->
</parent>
I'm not able to make this happen.
Edit
this Plunker shows how children keeps the same scope as it parent if parent is a child scope. I forgot, when you don't use a template/templateUrl, data inside the element will not be thrown away.
It seems that children's scope can't be the same as parent scope if parent scope is isolated. Require is needed to add data to the parent scope.

It's because of ngTransclude. In the docs (https://docs.angularjs.org/guide/directive) it states:
The transclude option changes the way scopes are nested. It makes it
so that the contents of a transcluded directive have whatever scope is
outside the directive, rather than whatever scope is on the inside. In
doing so, it gives the contents access to the outside scope.

Have you tried using the require property on your child directive definition object? You will need to define a controller on your parent and with the require property set to ^container in the child directive you will then have access to the parent controller in your link function as the fourth argument:
angular.module('app', [])
.directive('container', function(){
return {
restrict : 'E',
transclude : true,
scope : {
},
template : 'container <ng-transclude> </ng-transclude>',
controller: function($scope){
// use this to add properties to the controller itself
// which you can then access in the child directive
this.container = "container";
},
link : function($scope){
}
};
})
.directive('child', function(){
return {
require: '^container',
restrict : 'E',
template : 'child container',
link : function($scope, element, attrs, containerController){
$scope.child = "child";
// logs: containerController Object {container: "container"}
console.log('containerController', containerController);
}
};
});

Related

Get parent controller scope into directive using controller instead of link

I am using a 'LocalCtrl' controller for all functionality needed for directive, but how do i get scope of parent controller into the directive and scope from directive back to controller.
My directive is embedded in parent controller. I know how to use link function and isolate scope for two way binding between directive and controller. I'm not sure how to inherit parent scope by using the following structure.
<div ng-controller = "mainCtrl">
<my-directive></my-directive>
</div>
app.controller('mainCtrl',function ($scope) {
$scope.mainContent = "this is main content"
});
app.controller('LocalCtrl',function () {
var vm = this;
vm.content = "This is Header"
});
app.directive('mydirective',function () {
return{
controller:'LocalCtrl as local',
templateUrl: '<div>{{local.content}}</div>',
}
});
Directives in Angularjs has 3 scopes , as mentioned below
refer In which cases angular directive scope equals controller scope?
1 . By default , scope is false , which incase on change of the scope variable in your directive also changes the parents scope variable as it doesn't create a new scope.
app.directive('mydirective',function () {
return{
controller:'LocalCtrl as local',
templateUrl: '<div>{{local.content}}</div>',
}
});
scope:true , With this it will create a new child scope in child directive , which inherits prototypically from the parent scope or parents controller scope
app.directive('mydirective',function () {
return{
scope:true,
controller:'LocalCtrl as local',
templateUrl: '<div>{{local.content}}</div>',
}
});
3: scope:{} : isolate scope , which doesn't inherit from parent's scope (could create re-usable components/directives)
view
<div ng-controller = "mainCtrl ">
<my-directive content="mainContent" some-fn="someFn"></my-directive>
</div>
app.directive('mydirective',function () {
return{
scope:{
twoWayConent:'=content',// two way data binding
oneWayConent:'#conent', // one way binding
someFn:'&someFn' //function binding ( 2 way)
},
controller:'LocalCtrl as local',
templateUrl: '<div>{{local.content}}</div>',
}
});
4. using require: : if you have one directive in another directive ,In Directive Defination object (DDO) require can be used to access the parents directive controllers variables and functionalities in your child directive as below
view
<parent-directive>
<child-directive></child-directive>
</parent-directive>
app.directive('childDirective',function () {
return{
require:'^parentDirective' // can be array of parents directive too
link:function(scope,element,attrs,parentDirectiveController){
console.log(parentDirectiveController) // can access parent directive controllers instances
}
}
});

Along with Transclude element, can i pass its scope too to a directive?

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.

Directive default scope value is not set?

I want to set default value in my directive like this but its going to be null
(function(angular) {
'use strict';
angular.module('docsTransclusionExample', [])
.controller('Controller', ['$scope', function($scope) {
}])
.directive('myDialog', function() {
return {
restrict: 'E',
transclude: true,
scope: {
name: '=?'
},
templateUrl: 'my-dialog.html',
link: function (scope, element) {
},
controller: function($scope) {
$scope.name = 'Tobias';
}
};
});
})(window.angular);
http://plnkr.co/edit/gVLvFwdXp0oxmJUOAMMN
Check demo: http://plnkr.co/edit/Ui9EqKNeUc3K01L1el0d?p=preview
Because when you call the directive, you do not pass in the binding property.
Bind the name to the directive:
<my-dialog name="name">Check out the contents, {{name}}!</my-dialog>
Since you are using transclude: true. Angular will create a separate scope for the (transclude scope) transcluded content, i.e., Check out the contents, {{name}}!. This scope is prototypically inherits from the parent scope, and it is a sibling of the isolated scope. So here {{name}} is not accessing the name on the isolate scope. Instead, it is accessing transclude scope (and traverse upstream to its parent scope, where you do not set the name).
Use <my-dialog name="name">... will create a name property in the parent scope and two-way bind to the directive. In this way, changing the value inside the directive will also change the value outside.
Colliding Scopes
You are having an issue because you have a transcluded scope and an isolate scope colliding. Remove scope: { name: '=' } to interpolate the name on the controller scope. Unless you need to keep the scope from inheriting from or colliding with the parent scope, an isolate scope isn't typically needed.
If you need to keep the directive's scope isolated then don't remove the above and add the name attribute to the element and set it to the scope property you want to use: name="name" for example.
Given the following:
scope: {
name: '='
}
This means you are two-way binding to the scope property defined in the name attribute on the element and calling it name on the isolate scope. If you said somename: '=thatName' then you would two-bind to the that-name attribute but call it "somename" on the isolate scope.
In two-way binding, changes on the directive scope will affect the parent scope. If you want to one-way bind (changes won't affect the parent) you can use name: '#' and if you want to bind to the value of the name attribute you can use name: '&'.

can a custom directive have both an isolated scope AND a "controller: ctrlName" field?

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

angularjs ng-class in nested directive

Tried so many different ways and still can't figure this. I have a menu with ng-class="{menuVisibleAnimation: menuOpen}" in a template in a nested directive. When I click on the button in the parent directive I want to change the value of menuOpen to true but the menu in the child directive is not updating?
http://plnkr.co/edit/nOunKkch0Gt8hjMWtruA?p=preview
The main issue in your implementation is that you want to use the the $scope to share the value of menuOpen between the parent and child directive, but your parent directive has an isolated scope :
scope: {
menuOpen: '#menuOpen'
}
You need to declare menuOpen in a scope shared by both directives, due to transclusion it has to be the parent scope of the parent directive. So, in the parent directive you should not create a new scope :
scope: false,
link: function($scope) {
$scope.menuOpen = false;
$scope.toggleMenu = function() {
$scope.menuOpen = !$scope.menuOpen;
};
}
Then, openMenu is accessible in the child directive. See it in in a fork of your Plunker.

Resources