How can an Angular directive compile() function access an isolated scope? - angularjs

I have the following directive:
angular.module("example_module", [])
.directive("mydirective", function() {
return {
scope: { data: "#mydirective" }
compile: function(element) {
element.html('{{example}}');
return function($scope) {
$scope.example = $scope.data + "!";
};
}
};
});
and the following HTML code:
<!DOCTYPE html>
<html ng-app="example_module">
<head>
<meta charset="utf-8">
<title>Example title</title>
<script src="lib/angular/angular.min.js"></script>
<script src="js/example.js"></script>
</head>
<body>
<div mydirective="Hello world"></div>
</body>
</html>
I would expect the directive to compile to Hello world!, but it compiles to an empty string instead. scope creates an isolated scope which seems impossible to reach for {{example}}.
I would like to know how the new HTML code created by compile() can access the link function $scope.

This doesn't work because {{example}} is being evaluated against the parent scope, which makes sense, since you are essentially changing the element before compilation to:
<div>{{example}}<div>
You can verify by replacing '$scope.example =' with '$scope.$parent.example =' (for demonstration purposes only - it's not a best practice to use $parent).
What you are really trying to do is something similar to transclusion, but there are very easier ways to do it. For instance:
angular.module("example_module", [])
.directive("mydirective", function() {
return {
restrict: 'A',
scope: { data: "#mydirective" },
template: '{{example}}',
compile: function(element) {
return function($scope) {
console.log($scope.data);
$scope.example = $scope.data + "!";
console.log($scope.example);
};
}
};
});
When you use a template, it replaces the content of the element the directive is applied to (unless you use replace: true, in which case it replaces the entire element), and the contents of the template are evaluated against the directive scope.
You could do what you are trying to do using the transclude parameter passed to compile (see the docs), but this has been deprecated so I wouldn't recommend going down that road.
Here's a Plunk where you can play with some variations.

Related

block-ui-pattern has no effect

i am trying to implement block-ui into our angular application on an element by element basis. (everything is included, referenced and injected correctly)
We have been trying to implement
block-ui-pattern
with no success.
our $http request is :-
$http.get('/user/123/GetUserAddress/').then(function (data) {
and our block-ui-pattern is :-
< div block-ui block-ui-pattern="/^user\/123\/GetUserAddress\$/">
{{address}}
</div>
This seems to match the documentation, but is failing to work. Am i missing something fundamental?
Our application exposes an isloading flag. initially set to true, and when the $http promise returns, sets this to false.. I realize that it is not in the documentation, however, Is there a way to set
< div block-ui="isloading"></div>
Post by Parash Gami pointed me in the right direction.
I actually ended up writing a custom directive that wraps block-ui
var myBlocker = angular.module('app.Angular.Directive.myBlocker ', []);
myBlocker.directive('myBlocker ', ['$compile', function ($compile) {
return {
restrict: 'A',
scope :{
blockId: '#id',
block: '=',
},
controller: ['$scope', 'blockUI', function ($scope, blockUI) {
var myBlock = blockUI.instances.get($scope.blockId);
$scope.$watch('block', function (newValue, oldValue) {
if ($scope.block === true)
{
myBlock.start()
}
else {
myBlock.stop()
}
});
}],
link: function link(scope, element, attrs) {
element.attr('block-ui', scope.blockId);
element.attr('style', 'min-height:200px;');
element.removeAttr("my-blocker");
element.removeAttr("data-my-blocker");
$compile(element)(scope);
}
};
}]);
This allows me to now simply add the directive to an element
< div id="myId" my-blocker block="loading">
Please check sample code. Just include one CSS and one JS of blockUI and add dependency blockUI, use blockUI.start() method to show loader and use blockUI.stop() method to hide loader. Following example hide loader after 2 seconds. Use it as per your requirement.
<!DOCTYPE html>
<html>
<head>
<title></title>
<link rel="stylesheet" type="text/css" href="http://angular-block-ui.nullest.com/angular-block-ui/angular-block-ui.css">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js"></script>
<script type="text/javascript" src="http://angular-block-ui.nullest.com/angular-block-ui/angular-block-ui.js"></script>
</head>
<body ng-app="app.user">
<div ng-controller="tempController">
</div>
</body>
</html>
<script type="text/javascript">
var app = angular.module('app.user',['blockUI']);
app.controller('tempController', function(blockUI,$timeout)
{
blockUI.start();
$timeout(function()
{
blockUI.stop();
},2000)
});
</script>

My directive uses isolate scope, but still fetches data from the controller's scope

I have created two directives, one with the controller passed in, and the other with the require passed it.
What I am trying to do it to just check how directives use isolated scopes and utilize the data of its own scope.
But my out put is strange.
Please follow the code below,
<!DOCTYPE html>
<html lang="en" ng-app="MyApp">
<head>
<title>Recipe 02 example 01</title>
<script type="text/javascript" src="D:\Rahul Shivsharan\JavaScript-Framework\AngularJS\angular.js"></script>
<script type="text/javascript" src="D:\Rahul Shivsharan\JavaScript-Framework\jQuery\jquery-1.11.1.js"></script>
<script type="text/javascript" src="D:\Rahul Shivsharan\JavaScript-Framework\BootstrapCSS\bootstrap-3.2.0-dist\js\bootstrap.js"></script>
<link rel="stylesheet" href="D:\Rahul Shivsharan\JavaScript-Framework\BootstrapCSS\bootstrap-3.2.0-dist\css\bootstrap.css"></link>
<link rel="stylesheet" href="D:\Rahul Shivsharan\JavaScript-Framework\BootstrapCSS\bootstrap-3.2.0-dist\css\bootstrap-theme.css"></link>
<script type="text/javaScript">
angular.module("MyApp",[]);
(function(){
angular.module("MyApp").controller("myCtrl",myCtrl);
angular.module("MyApp").controller("StudentCtrl",StudentCtrl);
angular.module("MyApp").directive("parentClass",parentClass);
angular.module("MyApp").directive("childClass",childClass);
function parentClass(){
var obj = {
restrict : 'EA',
controller : "StudentCtrl"
}
return obj;
};
function childClass(){
var obj = {
restrict : 'EA',
require : "^parentClass",
scope : {
array : '='
},
template : "<ol><li ng-repeat='student in array'>{{student}}</li></ol>",
link : function(scope,element,attrs,someCtrl){
}
}
return obj;
}
function myCtrl($scope){
$scope.studentList = ["Tom Cruise","Jammy Watson","Simon Colins"]
};
function StudentCtrl($scope){
$scope.studentList = ["Shahrukh Khan","Salmaan Khan","Amir Khan"]
};
})();
</script>
</head>
<body ng-controller="myCtrl">
<parent-class>
<child-class array="studentList"></child-class>
</parent-class>
<pre />
<ul>
<li ng-repeat="student in studentList">{{student}}</li>
</ul>
</body>
In the above code there are two directives 'parentClass' and 'childClass'.
'childClass' directive requires 'parentClass' as its parent directive, and hence 'childClass' uses the controller 'StudentCtrl'
What I am expecting in the output is
the directive 'child-class' should print the students in 'StudentCtrl' and the student outside the directives should be from 'myCtrl',
but what I am getting is both the students are from controller 'StudentCtrl',
When I do the code change in directive 'parentClass' as follows,
function parentClass(){
var obj = {
restrict : 'EA',
controller : "StudentCtrl" ,
scope : {
array : '='
}
}
return obj;
};
The output is all the students are from controller 'myCtrl' irrespective of the directives closing tag.
The links for the live demonstration is as follows
Link One
Link Two
What I am expecting is
the directive 'child-class' should output list of students present in controller 'StudentCtrl' and the student outside the directives should output from controller 'myCtrl'
Please solve my confusion in scopes also.
Thanks
In your parentClass directive, you do not specify a scope value. This means it defaults to false.
A false scope value means that the directive does not create a new scope, meaning it will use the scope that is closest (up the scope chain) to it.
In this particular case, the directive uses the scope of the myCtrl controller. As such, when you specify the controller: 'StudentCtrl', and inject the $scope object to set the studentList variable, you overwrite the value in the myCtrl controller - thereby giving you the unexpected results.
One way to resolve this is to put scope: true as an option to the parentClass directive, thereby forcing the directive to create a new scope and not overwrite its parent.

Why didn't the link function in my AngularJS directive run?

Here is the HTML and Javascript for a simple Angular JS example. In this example, why didn't the link function run and set scope.flag to be true (boolean) instead of 'true' (string)?
Javascript:
angular.module('truthyTypes', [])
.directive('myDirective', function(){
return {
template: '<div>flag value: {{ flag }}<br/>flag type: {{ flag | typeOf }}</div>',
scope: {
flag: '#'
},
link: function(scope){
scope.flag = scope.flag === 'true';
}
}
})
.filter('typeOf', function(){
return function(input){
return typeof input;
}
})
;
HTML:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example - example-example78-production</title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-rc.1/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body ng-app="truthyTypes">
<div my-directive flag="true"></div>
</body>
</html>
Live Example: http://plnkr.co/edit/jqk5529FmdGsxK6Xj8LZ?p=preview
You are using one-way binding, hence any updates you make to the flag variable in the directive won't be propagated back to the controller.
Use two-way bindings (= instead of #), to change the flag value:
scope: {
flag: '='
}
Inside the link function, you can then set the flag to any appropriate value, e.g.
scope.flag = true;
The isolated scope is not immediately available to the link function. Please have a look at the answer to this similar question: AngularJS - Access isolated scope in directive's link function

ng-init not working inside link function of directive?

According to this Plunkr, using ng-init in a directive won't execute a function assigned to scope inside link, but it will if I do it in controller.
Can anyone explain this?
app.js
var app = angular.module('app', []);
app.directive('linkDir', function(){
return {
restrict: 'E',
scope: true,
template: 'Link: <p ng-init="initLink()">{{ linkmsg }}</p>',
link: function(scope){
scope.initLink = function(){
scope.linkmsg = 'link function executed'
}
}
};
});
app.directive('controllerDir', function(){
return {
restrict: 'E',
scope: true,
template: 'Controller: <p ng-init="initController()">{{ controllermsg }}</p>',
controller: function($scope){
$scope.initController = function(){
$scope.controllermsg = 'controller function executed';
}
}
};
});
HTML
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body ng-app="app">
<link-dir></link-dir>
<controller-dir></controller-dir>
</body>
</html>
This has to due with how Angular's directives work. The linkage between the scope and the DOM element is a little bit complex to explain, but making a long story short, if you use controllers, the scope will be immediately created with the initController property, while if you use the link attribute, the scope will only be populated after it as been linked to the DOM element (this means initLink will be undefined during the ng-init).
In order to avoid these problems, don't use the ngInit directive, as stated on Angular's documentation:
The only appropriate use of ngInit is for aliasing special properties
of ngRepeat, as seen in the demo below. Besides this case, you should
use controllers rather than ngInit to initialize values on a scope.
Stick with Controllers if you need to init properties on the scope.
With newer versions of Angular you can work around this by adding an ng-if to the element the ng-init is on so it gets evaluated when the function is available:
template: 'Link: <p ng-if="initLink" ng-init="initLink()">{{ linkmsg }}</p>',
http://plnkr.co/edit/qGjPZ4P3OjIn8rU2cufQ?p=preview

Template manipulation in Angular.js

I am trying to modify an Angular template before any other directive is triggered, in particular interpolation. I am doing this through the compile option in the directive definition.
Here is my test code:
<!doctype html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.js"></script>
<script>
angular.module('soQuestion', [])
.directive('example', function () {
return {
compile: function (tElement, tAttrs) {
if (tAttrs.foo && tAttrs.foo.match(/^keyword/)) {
tElement.attr('foo', 'prefix-' + tAttrs.foo);
console.log(tElement.attr('foo'));
}
}
};
})
.controller('controller', function($scope) {
$scope.value = 'something';
});
</script>
</head>
<body ng-app="soQuestion">
<div ng-controller="controller">
<div example foo="keyword_{{value}}"></div>
</div>
</body>
</html>
However, the final result that I get is <div foo="keyword_something"></div> instead of <div foo="prefix-keyword_something"></div>, even if the compile function was triggered properly. What is going on here?
Its a directive priority issue, and admittedly I still don't completely understand. But don't for get that {{}} is itself a directive. Its getting applied in some order with yours, and overwriting your manipulation. If its terminal and high priority, it clears up.
DEMO
angular.module('soQuestion', [])
.directive('example', function () {
return {
priority: 1000,
terminal : true,
compile: function (tElement, tAttrs) {
if (tAttrs.foo && tAttrs.foo.match(/^keyword/)) {
tElement.attr('foo', 'prefix-' + tAttrs.foo);
console.log(tElement.attr('foo'), tElement[0]);
}
return function(){};
}
};
})
.controller('controller', function($scope) {
$scope.value = 'something';
});
because this breaks the {{}} I would consider compiling the attr manually as well.
angular.module('soQuestion', [])
.directive('example', function () {
return {
compile: function (tElement, tAttrs) {
if (tAttrs.foo && tAttrs.foo.match(/^keyword/)) {
// Change the tAttrs hash instead of the element itself
tAttrs.foo = 'prefix-' + tAttrs.foo;
// Change the element too, in case no interpolation is present
tElement.attr('foo', tAttrs.foo);
console.log(tElement.attr('foo'));
}
}
};
})
Explanation: the interpolate directive does check for changes in the attribute value. However it doesn't look again on the DOM node itself but on the tAttrs hash.
Old pessimistic answer:
I don't think it's possible to achieve the desired result.
Looking at Angular's source, collectDirectives puts together the list of directives that affect a certain node. Their respective compile functions are collected and then sorted by priority. The problem is that the compile function of {{}} is then bound to keyword_{{value}} and is not affected by the example directive.

Resources