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.
Related
I got two directives, one directive is a sub-directive to the other one.
In the sub-directive there will be a ng-repeat. So there will be x amount of sub-directives depending on the list it will repeat. My question is: Is it possible for the base directive to know how many amount of sub-directives there will be? The base directive will then know when to concat all the values from the sub-directive and pass it on.
In this example the $scope.length of the list is know. But because of the ng-if 3 sub-directives is created and not the length of $scope.length which is 5.
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<body ng-app="myApp">
<test-directive>
<test-sub-directive ng-repeat="l in list" ng-if="l > 2" variable="{{l}}">{{l}}</test-sub-directive>
</test-directive>
<script>
var app = angular.module("myApp", []);
app.directive('testDirective', function () {
return {
restrict: 'E',
controllerAs: 'testCtrl',
controller: ['$scope', '$element', '$attrs', function testCtrl($scope, $element, $attrs) {
$scope.list = [1,2,3,4,5];
this.testFunc = function () {
//When everything is fetched from the sub-directive send it to another function
}
}]
};
})
app.directive('testSubDirective', function () {
return {
require: '^test-directive',
restrict: 'E',
link: function (scope, element, attrs, testCtrl) {
testCtrl.testFunc();
},
};
})
</script>
</body>
</html>
https://www.w3schools.com/code/tryit.asp?filename=FIUXSWL47ZC9
You can substitute ng-if with a filter. Now you can use the result to determine length on your parent directive. This is a working edit of your sample
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/
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>
I have an question about AngularJs scopes and especially the way those can be inspected with the Batarang Chrome extension.
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">
<div enhanced-textarea ng-model="name"></div>
<div cmp>
<h3>{{name}}</h3>
<div notice></div>
</div>
</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>
Here are the directives:
'use strict';
angular.module('myApp.directives', [])
.directive('cmp', function () {
return {
restrict: 'A',
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;
}
}, true);
}])
.directive('enhancedTextarea', function () {
return {
restrict: 'A',
replace: true,
transclude: true,
template: '<textarea ng-transclude></textarea>'
};
})
.directive('notice', function () {
return {
restrict: 'A',
require: '^cmp',
replace: true,
scope: {
updatedSize: '='
},
template: '<div>{{size}}</div>',
link: function ($scope, $element, $attrs, cmpCtrl) {
$scope.$parent.$watch('updatedSize', function (newVal) {
if (newVal) {
$scope.size = newVal;
}
}, true);
}
};
});
and the controller:
'use strict';
angular.module('myApp.controllers', [])
.controller('myCtrl', ['$scope', function($scope) {
$scope.name = 'test';
}]);
When I inspect the scopes using batarang, I come up with the following conclusion:
Scope 002: ng-app
Scope 003: ng-controller (myCtrl)
Scope 004: ????
Scope 005: cmpCtrl (controller for cmp directive)
Scope 006: inside cmp (h3 and notice)
Scope 007: link function of notice directive
Is the above correct?
Also, my biggest interrogation is what does the 004 scope correspond to?
Full app is located on github here
See also screen capture below:
It's not that each $scope has to correspond to an element of your page. In fact in every AngularJS app there are a bunch of $scopes which aren't directly linked to any element.
In your case it's the ng-transclude which causes a child scope to be created.
Take a look at the implementation of AngularJS which causes the creation of your 004 $scope.
if (!transcludedScope) {
transcludedScope = scope.$new();
transcludedScope.$$transcluded = true;
scopeCreated = true;
}
https://github.com/angular/angular.js/blob/master/src/ng/compile.js#L959
If you like to dig deeper yourself, go and set a breakpoint right here in your AngularJS file:
Then just use the call stack and follow the rabbit...
I also use this scenario to debug and inspect what is in the scope of an element, might be helpful:
You inspect the element with chrome dev tools
Once you select certain element you can get its scope by typing in console:
angular.element($0).scope()
You can get the controller in the same way just instead of scope() you can type controller()
In order to set a breakpoint in you code and look at in in chrome debugger (I sometimes find this easier than setting a breakpoint in dev tools) you can type:
debugger;
in your source and dev tool will stop there so you see the declared vars etc.
I have ng-click="foo()" which alert "foo"
In my own directive, if ng-click is found, I want to add another function to alert "bar"
I tried this
DEMO: http://plnkr.co/edit/1zYl0mSxeLoMU3yjoGBV?p=preview
and it did not work
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<script src="http://code.angularjs.org/1.2.7/angular.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('MyCtrl', function($scope) {
$scope.foo = function() { alert('foo'); }
$scope.bar = function() { alert('bar'); }
});
app.directive("myAttr", function() {
return {
link: function(scope, el, attrs) {
el.attr('ng-click', attrs.ngClick+';bar()');
}
}
})
</script>
</head>
<body ng-controller="MyCtrl">
<a my-attr ng-click="foo()" href="">click here!</a>
</body>
</html>
I was also not able to another ng-* directive to this to make it work, i.e. el.attr('ng-focus', 'bar()');. It seems that I cannot change or add ng-* directive once it is rendered.
How can I achieve this, and what was I doing wrong?
app.directive("myAttr", function() {
return {
priority: 1,
compile: function(el, attrs) {
attrs.ngClick += ';bar()';
}
}
})
First of all you want a compile function, for when link is called, the ng-click directive is already set up.
The second important thing is to change the priority. You want to ensure that your directive is called before ng-click. ng-click has the default priority 0, so 1 is enough.
The last and important thing, which is not obvious, is that you don't want to change the element, but attrs itself. It is created only once per element. So when ng-click accesses it it would still contain the same value, if you changed the attribute on the element directly.
I think you can do what you want with ngTransclude.
app.directive("myAttr", function() {
return {
transclude:true,
template: '<span ng-click="bar()" ng-transclude></span>',
link: function(scope, el, attrs) {
}
}
});
Does that work?
EDIT
Okay what about this one?
app.directive("myAttr", function($compile) {
return {
link: function(scope, el, attrs) {
el.attr('ng-click', 'bar()');
el.removeAttr('my-attr');
$compile(el)(scope);
}
}
});
While this could be done with compile as outlined above, that approach doesn't guarantee the order in which the ng-click items would be added to a DOM node (as you have already discovered), and is inherently slow (as has been pointed out by Words Like Jared.
Personally, I would just do something like this:
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<link rel="stylesheet" href="style.css">
<script src="http://code.angularjs.org/1.2.7/angular.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('MyCtrl', function($scope) {
$scope.foo = function() { alert('foo'); }
$scope.bar = function() { alert('bar'); }
});
app.directive('myAttr', function() {
return {
scope: true,
link: function(scope, el, attrs) {
if(attrs.hasOwnProperty('ngClick')){
scope.foo = function(){
scope.$parent.foo();
scope.$parent.bar();
}
}
}
};
});
</script>
</head>
<body ng-controller="MyCtrl">
<a my-attr ng-click="foo()" href="">click here!</a>
</body>
</html>
Whats going on:
scope: true: By default directives do not create new scopes, simply sharing their parent scope. By setting scope: true, every instance of this directive will create a child scope, that will prototypically inherit from the parent scope.
Then you can simply override the method desired (foo()) and voila
Live demo:
http://plnkr.co/edit/8A8y96wAhqGEowFaRQUH?p=preview
I freely admit I may entirely misunderstand what you are trying to do. However, given the example you provided, I think you might be better served by separating concerns a little more.
It seems from your example that you are trying to trigger foo and bar together whenever your directive is present. If both foo and bar are concerns of the controller, then why not wrap them both up in another function and assign that function to the ng-click of your element. If foo is a concern of the controller, but bar is a concern of the directive, why not trigger the bar functionality directly from the directive code?
If the functionality wrapped up in 'foo' and 'bar' is suppose to be defined by the controller creator...
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<link rel="stylesheet" href="style.css">
<script src="http://code.angularjs.org/1.2.7/angular.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('MyCtrl', function($scope) {
$scope.foo = function() { alert('foo'); }
$scope.bar = function() { alert('bar'); }
$scope.pak = function() {
$scope.foo();
$scope.bar();
}
});
</script>
</head>
<body ng-controller="MyCtrl">
<a ng-click="pak()" href="">click here!</a>
</body>
</html>
Or, if the functionality wrapped up in 'foo' is suppose to be defined by the controller creator, but the functionality wrapped up in 'bar' is suppose to be defined by the directive creator...
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<link rel="stylesheet" href="style.css">
<script src="http://code.angularjs.org/1.2.7/angular.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('MyCtrl', function($scope) {
$scope.foo = function() { alert('foo'); }
});
app.directive('myAttr', function(){
return {
link: function(scope, element, attrs){
element.click(function(){
alert('bar');
});
}
}
});
</script>
</head>
<body ng-controller="MyCtrl">
<a ng-click="foo()" href="">click here!</a>
</body>
</html>