Can multiple directives for one element share an isolated scope? - angularjs

Two directives on the same element can not both have isolated scope, but can they both use the same scope isolated from their parent? And can they both use properties bound to the isolated scope?
For example, if I have two directives on an element
<e-directive a-directive prop="parentProp"/>
And one directive defines an isolated scope with a bound property
App.directive('eDirective', function() {
return {
restrict: 'E',
scope: {
localProp: '=prop'
},
...
};
});
Does the other directive get that scope and can it use the bound property?
App.directive('aDirective', function() {
return {
restrict: 'A',
link: function postLink(scope, element, attrs) {
scope.$watch('localProp', function(newProp, oldProp) {
...
}
},
...
};
});
My initial attempt (pretty much coded as above) failed.

I suggest you make use of communicating between the directives' controllers via the require property of the secondary directive. The first directive (e-directive) holds the isolated scope, while the second helper directive (a-directive) has a reference to the first directive and sets properties via functions defined on the first directive. A small sample would be (see plunker):
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js#1.2.x" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js" data-semver="1.2.16"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<div e-directive config="parentConfig" a-directive></div>
</body>
</html>
and the javascript:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.parentConfig = {};
});
app.controller('ECtrl', function ( $scope ) {
this.setProp = function(newProp){$scope.config.prop = newProp;};
$scope.$watch('config', function(newProp, oldProp) {
console.log(oldProp, newProp);
});
});
app.directive('eDirective', function() {
return {
restrict: 'A',
scope: {
config: '='
},
controller: 'ECtrl',
link: function(scope, element, attrs) {
scope.config.prop ="abc";
}
};
});
app.directive('aDirective', function() {
return {
restrict: 'A',
require: 'eDirective',
link: function(scope, element, attrs,ctrl) {
ctrl.setProp("def");
}
};
});

Instead of an isolate scope, the directives can create a new child scope, which will be shared by both directives. If you need to modify parentProp in a directive, inject and use $parse:
<div ng-controller="MyCtrl">
<e-directive a-directive prop="parentProp"></e-directive>
</div>
Javascript:
var app = angular.module('myApp', []);
app.controller('MyCtrl', function($scope) {
$scope.parentProp = { prop1: 'value1' };
});
app.directive('eDirective', function($parse) {
return {
restrict: 'E',
scope: true,
template: '<div>dir template: {{eDirLocalProp}}<br>'
+ '<a href ng-click="eDirChange()">change</a></div>',
link: function(scope, element, attrs) {
scope.eDirProp1 = 'dirPropValue';
var model = $parse(attrs.prop);
scope.eDirLocalProp = model(scope);
scope.eDirChange = function() {
scope.eDirLocalProp.prop1 = "new value";
};
}
};
});
app.directive('aDirective', function() {
return {
scope: true,
link: function postLink(scope, element, attrs) {
scope.$watchCollection(attrs.prop, function(newValue) {
console.log('aDirective', newValue);
});
},
};
});
fiddle
If both directives need to create properties on the new child scope, use some kind of naming convention to prevent name clashes. E.g., scope.eDirProp1 = ... and scope.aDirProp1 = ....

Yes by using element.isolateScope() for example (or see fiddle):
HTML
<div ng-app="app" ng-controller="BaseController as baseCtrl">
<input type="text" ng-model="inputA.value" directive-config="{data: 'bar'}" >
<input type="text" ng-model="inputB.value" directive-config="{parsers: externalParser, data: 'buzz'}" custom-input >
<br><br>
<span style="font-style: italic; font-size: 12px; color: red;">*Open Console to view output</span>
</div>
JS
(function(angular){
"use strict";
angular.module("app", [])
.controller("BaseController", ['$scope', function($scope){
$scope.inputA = {value: "This is inputA"};
$scope.inputB = {value: "This is inputB"};
$scope.externalParser = function(value) {
console.log("...parsing value: ", value);
}
}])
.directive("input", [function() {
return {
restrict: "E",
require: '?ngModel',
scope: {
directiveConfig: "="
},
link: function(scope, element, attrs, ngModelCtrl) {
console.log("input directive - scope: ", scope);
console.log("input directive - scope.directiveConfig.data: ", scope.directiveConfig.data);
}
}
}])
.directive("customInput", [function() {
return {
restrict: "A",
require: '?ngModel',
link: function(scope, element, attrs, ngModelCtrl) {
console.log("");
console.log("--------------------------------------------");
console.log("customInput directive - scope: ", scope);
// Use `element.isolateScope()`
var parentScope = element.isolateScope();
console.log("customInput directive - parentScope.directiveConfig.parsers: ", parentScope.directiveConfig.parsers);
console.log("customInput directive - parentScope.directiveConfig.data: ", parentScope.directiveConfig.data);
console.log("");
console.log("--------------------------------------------");
console.warn("DO NOT USE `$$childHead` as it may not target the element you are expecting; use `element.isolateScope()` instead.");
// DO NOT USE `$$childHead` as it may not be the element you expect
console.log("customInput directive - scope.$$childHead.directiveConfig.parsers: ", scope.$$childHead.directiveConfig.parsers);
console.log("customInput directive - scope.$$childHead.directiveConfig.data: ", scope.$$childHead.directiveConfig.data);
}
}
}])
;
})(angular)
console output
//input directive - scope: n {$id: 3, $$childTail: null, $$childHead: null, $$prevSibling: null, $$nextSibling: null…}
//input directive - scope.directiveConfig.data: bar
//input directive - scope: n {$id: 4, $$childTail: null, $$childHead: null, $$prevSibling: n, $$nextSibling: null…}
//input directive - scope.directiveConfig.data: buzz
//--------------------------------------------
//customInput directive - scope: b {$$childTail: n, $$childHead: n, $$nextSibling: null, $$watchers: Array[4], $$listeners: Object…}
//customInput directive - parentScope.directiveConfig.parsers: function (value) {
// console.log("...parsing value: ", value);
// }
//customInput directive - parentScope.directiveConfig.data: buzz
//--------------------------------------------
//DO NOT USE `$$childHead` as it may not target the element you are expecting; use `element.isolateScope()` instead.
//customInput directive - scope.$$childHead.directiveConfig.parsers: undefined
//customInput directive - scope.$$childHead.directiveConfig.data: bar

Related

Calling function of controller from directive when using isolated scope?

I want to call a function of Controller from Directive, It is for validation. But i'm a bit confused about how to call it from Directive when i'm using isolated scope. Here is the code of directive:-
App.directive('test',function(){
return{
require: "ngModel",
scope:{
a:"=",
b:"=",
c:'=',
d:'='
},
link: function(scope, element, attributes,modelVal )
{
modelVal.$validators.test= function(val)
{
return false;
//isolated scope values will make if condition true or false.
if(scope.a=="true"){
//I need to call a function of controller here. But how can
//i call that function from directive? this is my problem
}
}
scope.$watch("a", function()
{
modelVal.$validate();
});
}//end of link function;
}//end of return;
});//end of directive;
Function is in my controller, i think i don't need to write the controller code. In my html , I'm calling this directive as:
<test a="1" b="2" c="3" d="4" ng-model="abc"></test>
What changes i need to do in my directive to call the controller function which is $scope.testFunction()?
var module = angular.module('myModule', []);
module.controller('TestController', function($scope){
$scope.text = 'test';
// Will be invoked from 'test' directive
$scope.alertMe = function(text){
alert(text);
};
});
module.directive('test', function(){
return {
restrict: 'E',
template: '<input ng-model="text" ng-change="onChange(text)" />',
scope: {
text: '='
},
link: function(scope, elem, attr){
scope.onChange = function(text){
// Invoking parent controller function
scope.$parent.alertMe(text);
}
}
}
})
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
<body>
<div ng-app="myModule" ng-controller="TestController">
<test text="text"></test>
</div>
</body>
</html>
I have given a sample, try like this.
Controller:
module.controller('TestController', function($scope){
$scope.onTextChange = function(text){
alert(text);
};
})
HTML:
<test my-func="onTextChange(text)"></test>
Directive:
module.directive('test', function(){
return {
restrict: 'E',
template: '<input ng-model="text" ng-change="onChange(text)" />',
scope: {
myFunc: '&'
},
link: function(scope, elem, attr){
scope.onChange = function(text){
if(typeof scope.myFunc === 'function'){
// Calling the parent controller function
scope.myFunc({text: text});
}
}
}
}
})

How to bind to event.time variable in parent scope from a directive with isolate scope without using $parent in directive?

angular.module('hfp.calendar')
.controller('printPreviewCntrl', ['$scope', 'htmlFromServer',
function($scope, htmlFromServer){
$scope.htmlReceived = htmlFromServer;
$scope.event = {};
}])
.directive('compile', function($compile, $timeout) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
$timeout(function() {
element.html(scope.$eval(attrs.compile));
$compile(element.contents())(scope);
});
}
};
})
.directive('initModel', function($compile) {
return {
restrict: 'A',
scope: {
eventField: '=initModel'
},
link: function(scope, element, attrs) {
scope.eventField = element[0].innerText;
element.attr('ng-bind', '$parent.' + attrs.initModel); // why do i have to use $parent here to make it work ?
element.removeAttr('init-model');
$compile(element)(scope);
}
};
});
<!-- clientside html -->
<input type="text" ng-model="event.time">
<div compile="htmlReceived">
</div>
<!-- html coming from server -->
<div init-model="event.time">
10:30 AM
</div>
I want to bind to the parent scope var event.time from initModel directive but it only works when i use $parent to refer to the var in parent scope. Can i achieve this binding without using $parent ?
There is no need to implement that directive with isolate scope. Simply use the $parse service:
angular.module("app",[])
.directive('initModel', function($parse) {
return {
restrict: 'A',
//scope: {
// eventField: '=initModel'
//},
link: function(scope, elem, attrs) {
var setter = $parse(attrs.initModel).assign;
setter(scope, elem.text().trim());
scope.$watch(attrs.initModel, function(value) {
elem.text(value);
});
}
};
})
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="app" ng-init="event={}">
<input type="text" ng-model="event.time">
<div init-model="event.time">
10:30 AM
</div>
</body>
For more information, see AngularJS $parse Service API Reference.

Cannot receive value of isolated scope in directive

I created this directive
angular.module('panel')
.directive('sigPanel', sigPanel)
function sigPanel() {
return {
restrict: 'E',
scope:{
imgData:"="
},
templateUrl: 'app/widgets/signature/signature.html',
link: function (scope, element, attrs) {
console.log(scope.imgData);
}
}
}
This is the templateURL:
<canvas style="border:1px solid black;"></canvas>
And added this into the HTML:
<sig-panel imgData="test"></sig-panel>
The console log only outputs "undefined", shouldn't it log "test"? I know the directive html tag is working properly because the canvas appears on the page, but why won't the directive pick up the value of "imgData"?
If I try setting the scope.imgData inside the directive I get the error
[$compile:nonassign] Expression 'undefined' used with directive 'signaturePanel' is non-assignable!
Not sure why this is happening.
You need to change imgData (camelCase normalized) to img-data (dash-delimited). Also if you want to pass the string "test" to the scope you need to put quotes around it.
angular.module('app', []).directive('sigPanel', sigPanel);
function sigPanel() {
return {
restrict: 'E',
scope: {
imgData: "="
},
template: '<canvas style="border:1px solid black;"></canvas>',
link: function(scope, element, attrs) {
console.log(scope.imgData);
}
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<div ng-app='app'>
<sig-panel img-data="'test'"></sig-panel>
</div>
assignable example:
angular.module('app', [])
.controller('myController', function($scope) {
$scope.test = 'test';
})
.directive('sigPanel', sigPanel);
function sigPanel() {
return {
restrict: 'E',
scope: {
imgData: "="
},
template: '<canvas style="border:1px solid black;"></canvas>',
link: function(scope, element, attrs) {
console.log(scope.imgData);
scope.imgData = 'bob';
console.log(scope.imgData);
}
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<div ng-app='app' ng-controller='myController'>
<sig-panel img-data="test"></sig-panel>
{{ test }}
</div>

Issue in Custom Directives

In the below code i have added a controller to store the name which can be used in child scope without use of prelinks.But still the name value is undefined.Where am i going wrong.
<!DOCTYPE html>
<html ng-app="test">
<head>
<title>AngularJS</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.8/angular.min.js">
</script>
</head>
<body>
<parent></parent>
</body>
<script>
var test1 = angular.module("test", []);
test1.directive("child", function() {
return {
restrict: "E",
template: "<div>{{message1}}</div>",
link: function(scope, ctrl) {
scope.message1 = "i m child of " + ctrl.name;
}
}
});
test1.directive("parent", function() {
return {
restrict: "E",
template: "<div style='color:red'>{{message}}{{name}}" + "<child></child>" + " </div>",
link: function(scope, ctrl) {
scope.name = ctrl.name;
alert(ctrl.name);
scope.message = "hi i am parent ";
},
controller: function() {
this.name = "aditya";
}
}
});
</script>
</html>
As you declared the parent controller using controller as syntax, this, you can add controllerAs option to your directive and define a name of the controller:
var test1 = angular.module("test", []);
test1.directive("child", function() {
return {
restrict: "E",
template: "<div>{{message1}}</div>",
link: function(scope) {
scope.message1 = "i m child of " + scope.parentCtrl.name;
}
}
});
test1.directive("parent", function() {
return {
restrict: "E",
template: "<div style='color:red'>{{message}}{{name}}" + "<child></child>" + " </div>",
link: function(scope, element, attrs, ctrl) {
scope.name = ctrl.name;
alert(scope.name);
scope.message = "hi i am parent ";
},
controller: function($scope) {
this.name = "aditya";
},
controllerAs: 'parentCtrl' // Name of the controller
}
});
Then, in your child, you can access the parent scope by using its defined name, scope.parentCtrl.name.

Isoleted scope also for transclude?

I want to add my custom directive within my other directive.
Second directive should use scope from first directive.
Problem is that first directive has isolated scope and apperently it is also isolated for second directive and in my opinion it shouldn't because I'm using transclude.
Here is example. When I comment scope: {test:"#"} all wroks as it should.
How to fix it?
angular.module("myApp", [])
.controller("initCtrl", function ($scope) {
});
angular.module('myApp')
.directive('firstDirective', ['$timeout', function ($timeout) {
return {
restrict: 'E',
scope: {test: "#"}, //everything is OK when I comment that.
transclude: true,
template: '<div> First Directive {{myVar}} {{test}}<div ng-transclude></div></div>',
controller: "firstDirectiveCtrl",
link: function (scope, element, attributes) {
}
};
}])
.controller("firstDirectiveCtrl", ['$scope', '$timeout', function ($scope, $timeout) {
$scope.myVar = "Var from first directive";
$timeout(function () {
$scope.myVar = "Var from first directive has changed";
}, 1000);
}])
.directive('secondDirective', [function () {
return {
restrict: 'E',
scope: false,
require: "^firstDirective",
template: '<div> Second Directive {{myVar}}</div>',
link: function (scope, element, attributes) {
//scope.myVar = "Var from second directive";
}
};
}]);
<!DOCTYPE html>
<html data-ng-app="myApp">
<head lang="en">
<meta charset="UTF-8">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<title></title>
</head>
<body>
<div ng-controller="initCtrl" class="container">
<first-directive test="test">
<second-directive></second-directive>
{{myVar}}
<br><i>no var unless I comment scope: {test:"#"} i first directive.</i>
</first-directive>
</div>
</body>
</html>
You can access $parent scope ex. $parent.myVar.
Works only in angular 1.3
angular.module("myApp", [])
.controller("initCtrl", function ($scope) {
});
angular.module('myApp')
.directive('firstDirective', ['$timeout', function ($timeout) {
return {
restrict: 'E',
scope: {
test:"#",
//myVar:"="
},
transclude: true,
template: '<div> First Directive {{myVar}} {{test}}<div ng-transclude></div></div>',
controller: "firstDirectiveCtrl",
link: function (scope, element, attributes) {
}
};
}])
.controller("firstDirectiveCtrl", ['$scope', '$timeout', function ($scope, $timeout) {
$scope.myVar = "Var from first directive";
$timeout(function () {
$scope.myVar = "Var from first directive has changed";
}, 1000);
this.getMyVar = function () {
return $scope.myVar;
};
//console.log($scope.getMyVar());
}])
.directive('secondDirective', [function () {
return {
restrict: 'E',
scope: false,
require: "^firstDirective",
template: '<div> Second Directive {{$parent.myVar}}</div>',
link: function (scope, element, attributes, ctrl) {
//console.log(scope.$parent);
//console.log(ctrl);
// console.log(ctrl.myVar);
// console.log(ctrl.getMyVar());
//scope.myVar = "Var from second directive";
}
};
}]);
<!DOCTYPE html>
<html data-ng-app="myApp">
<head lang="en">
<meta charset="UTF-8">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular.min.js"></script>
<title></title>
</head>
<body>
<div ng-controller="initCtrl" class="container">
<first-directive test="test">
{{$parent.myVar}}
<second-directive></second-directive>
</first-directive>
</div>
</body>
</html>
It is because the first directive uses isolated scope that myVar is not visible inside of the directive's template. myVar is visible inside of the transcluded contents because it is linked to the transclusion scope, which is a child scope of your parent controller scope. The transclusion scope, and firstDirective's isolated scope are sister scopes - but separate from eachother.
Note: This is only true for 1.2+. In 1.3, it looks like things have changed, and transclusion scope is a child scope of the next scope higher up the chain.
To fix this, you just have to pass myVar to your isolated scope:
.directive('firstDirective', ['$timeout', function ($timeout) {
return {
restrict: 'E',
scope: {test: "#", myVar: "="}, //pass myVar into your isolated scope.
transclude: true,
template: '<div> First Directive {{myVar}} {{test}}<div ng-transclude></div></div>',
controller: "firstDirectiveCtrl",
link: function (scope, element, attributes) {
}
};
}])
HTML
<div ng-controller="initCtrl" class="container">
<first-directive test="test" my-var="myVar">
<second-directive></second-directive>
{{myVar}}
...
</first-directive>
</div>
angular.module("myApp", [])
.controller("initCtrl", function ($scope) {
});
angular.module('myApp')
.directive('firstDirective', ['$timeout', function ($timeout) {
return {
restrict: 'E',
scope: {test: "#", myVar:"="}, //everything is OK when I comment that.
transclude: true,
template: '<div> First Directive {{myVar}} {{test}}<div ng-transclude></div></div>',
controller: "firstDirectiveCtrl",
link: function (scope, element, attributes) {
}
};
}])
.controller("firstDirectiveCtrl", ['$scope', '$timeout', function ($scope, $timeout) {
$scope.myVar = "Var from first directive";
$timeout(function () {
$scope.myVar = "Var from first directive has changed";
}, 1000);
}])
.directive('secondDirective', [function () {
return {
restrict: 'E',
scope: false,
require: "^firstDirective",
template: '<div> Second Directive {{myVar}}</div>',
link: function (scope, element, attributes) {
//scope.myVar = "Var from second directive";
}
};
}]);
<!DOCTYPE html>
<html data-ng-app="myApp">
<head lang="en">
<meta charset="UTF-8">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<title></title>
</head>
<body>
<div ng-controller="initCtrl" class="container">
<first-directive test="test" my-var="myVar">
<second-directive></second-directive>
{{myVar}}
<br><i>no var unless I comment scope: {test:"#"} i first directive.</i>
</first-directive>
</div>
</body>
</html>

Resources