Isoleted scope also for transclude? - angularjs

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>

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});
}
}
}
}
})

Use data in angular directive link?

How i can use data , which passed from controller to directive from tag attribute ?? Its show undefined in console .
App.directive('applist', ['$rootScope', function($rootScope) {
'use strict';
return {
restrict: 'E',
scope: {
gamesList: '=',
}.
link: function(scope,attrs){
console.log(scope.gamesList); //undefined
}
}
}])
And html:
<applist games-List="games">
<div ng-repeat="(key, value) in gamesList | groupBy: 'game.id'"> ... </div>
</applist>
You must change your tag to:
<applist games-list="games">...</applist>
Also, in angular, the camelCase in your attributes is used with a '-' in the html tag. And you forgot the 's' to the 'game'
Edit: As stated, the dot before link is making the directive break. Try:
return {
restrict: 'E',
scope: {
gamesList: '='
}, //change dot to coma
link: function(scope,attrs){
console.log(scope.gamesList); //undefined
}
}
Use # for getting String Values:
Modified Code:
<!DOCTYPE html>
<html ng-app="myApp">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<body>
<applist games-list="games"></applist>
<script>
var App=angular.module('myApp',[]);
App.directive('applist', ['$rootScope', function($rootScope) {
'use strict';
return {
restrict: 'E',
transclude:true,
scope: {
gamesList: '#',
},
link: function(scope,attrs){
console.log(scope.gamesList); //games
}
}
}])
</script>
</body>
</html>
To get data from directive attribute you can use below code
console.log(scope.gamesList);
Your directive will be
App.directive('applist', ['$rootScope', function($rootScope) {
'use strict';
return {
restrict: 'E',
scope: {
gamesList: '#',
},
link: function(scope,attrs){
console.log(scope.gamesList); //games
}
}
}])

Angular - ng-click function on transcluded directive content is not triggered

I have two directives, parent directive should simply wrap around its child. The transclusion is used for this purpose.
However, then any other directive, such as ng-click, bound to the child directive element as its attribute does not work (is it not compiled?).
Here is the JS:
(function(angular) {
'use strict';
angular.module('docsIsoFnBindExample', [])
.controller('Controller', ['$scope', '$timeout', function($scope, $timeout) {
$scope.name = 'Tobias';
$scope.message = '';
$scope.hideDialog = function(message) {
$scope.message = message;
$scope.dialogIsHidden = true;
$timeout(function() {
$scope.message = '';
$scope.dialogIsHidden = false;
}, 2000);
};
}]) //controller is not important now
.directive('myDialog', function() { //parent directive
return {
restrict: 'E',
transclude: true,
scope: {
'close': '&onClose'
},
template: '<div class="alert"><a href class="close" ng-click="close({message: \'closing for now\'})">×</a><div ng-transclude></div></div>'
};
})
.directive('daka', function() { //child directive
return {
restrict: 'E',
scope: {
'input': '#'
},
link: function(scope, element, attributes) {
scope.func= function() {
console.log("blablabla"); //no console output after click event
};
}
};
});
})(window.angular);
HTML:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example - example-directive-transclusion-scope-production</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body ng-app="docsIsoFnBindExample">
<div ng-controller="Controller">
{{message}}
<my-dialog ng-hide="dialogIsHidden" on-close="hideDialog(message)">
<daka ng-click="func()" input="11">BLABlABLA</daka>
</my-dialog>
</div>
</body>
</html>
It is trying to look ng-click in your parent directive.
So you can add click event for your child directive.
.directive('daka', function() { //child directive
return {
restrict: 'E',
scope: {
'input': '#'
},
link: function(scope, element, attributes) {
element.on('click', function() {
alert('outcome clicked: ');
});
}
}; });
working jsfiddle link -https://jsfiddle.net/p2vht8sb/

Can multiple directives for one element share an isolated scope?

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

Basic issue with AngularJS directive not working

I have the following html:
<!doctype html>
<html lang="en" ng-app="myApp">
<head>
<meta charset="utf-8">
<title>My AngularJS App</title>
<link rel="stylesheet" href="css/app.css"/>
</head>
<body>
<div ng-controller="myCtrl">
<cmp>
<enhanced-textarea ng-model="name"></enhanced-textarea>
<h3>{{name}}</h3>
<notice></notice>
</cmp>
</div>
<script src="lib/angular/angular.js"></script>
<script src="js/directives.js"></script>
<script src="js/controllers.js"></script>
<script src="js/app.js"></script>
</body>
</html>
I suspect there is an issue with my app.js:
'use strict';
angular.module('myApp', [
'myApp.directives',
'myApp.controllers'
]);
Here is controllers.js:
'use strict';
angular.module('myApp.controllers', [])
.controller('myCtrl', ['$scope', function($scope) {
$scope.name = 'test';
}]);
And finally directives.js:
'use strict';
angular.module('myApp.directives', [])
.directive('cmp', function () {
return {
restrict: 'E',
controller: 'cmpCtrl',
replace: true,
transclude: true,
scope: {
name: '='
},
template: '<div ng-transclude></div>'
};
})
.controller('cmpCtrl', ['$scope', '$element', '$attrs' , function ($scope, $element, $attrs) {
$scope.$parent.$watch('name', function (newVal) {
if (newVal) {
$scope.$parent.updatedSize = newVal.length;
console.log(newVal.length);
}
}, true);
}])
.directive('enhancedTextarea', function () {
return {
restrict: 'E',
replace: true,
transclude: true,
template: '<textarea ng-transclude></textarea>'
};
})
.directive('notice', function () {
return {
restrict: 'E',
require: '^cmp',
replace: true,
scope: {
updatedSize: '='
},
template: '<div>{{size}}</div>',
link: function ($scope, $element, $attrs, cmpCtrl) {
console.log(cmpCtrl);
$scope.$parent.$watch('updatedSize', function (newVal) {
if (newVal) {
$scope.size = newVal;
}
}, true);
}
};
});
My code is bloated I know, but I am in the process of pruning it down. Bear with me.
I don't understand why the size model attribute inside the notice element is not updated...
Full app is located on github here
The problem is scope inheritance, your enhancedTextarea directive' scope inherits the name property from your controller because it's undefined. But as soon as you change the textarea value, it's property is created and what you change after that changes this directive's scope property.
Take a look at this DEMO.
When you inspect the console without changing the textarea, you won't see the name property of the scope. When you type something, you see the property is created which will override the parent's scope name property.
When you change the code like this, it works:
<enhanced-textarea ng-model="name"></enhanced-textarea>
<cmp>
<h3>{{name}}</h3>
<notice></notice>
</cmp>
DEMO
In order to create loosely coupled code, I recommend you not to rely on $scope.$parent inside you directives. You should try directive bindings to bind with parent properties.

Resources