AngularJS Directive - Access $rootScope variable in template - angularjs

I have one function defined in $rootScope which has some value. I have created one directive to show value from there. I am not getting any value there.
<last-data-update-message type="info" update-for='stats'></last-data-update-message>
$rootScope.Helpers = {
getLastUpdateStatus: function(type) {
if (!_.isEmpty(Helpers.lastUpdateDetails)) {
if (type == 'requests') {
return Helpers.lastUpdateDetails.last_request_generated;
} else if (type == "stats") {
return Helpers.lastUpdateDetails.last_stats_executed || ((new Date()).getTime() - 2 * 60 * 60 * 1000);
} else if (type == "response") {
return Helpers.lastUpdateDetails.last_response_recieved;
} else {
return Helpers.lastUpdateDetails.last_stats_executed || ((new Date()).getTime() - 2 * 60 * 60 * 1000);
}
}
}
}
mymodule.directive('lastDataUpdateMessage', ["$rootScope", '$compile', function($rootScope, $compile) {
return {
restrict: 'AE',
replace: true,
template: function(element, attrs) {
if (element && element[0]) {
var targetElem = element[0];
console.log(attrs)
console.log("type", attrs.type)
console.log("for", attrs.updateFor);
console.log(attrs.updateFor);
return '<div class="alert alert-info fade in margin-0" style="margin-top:10px !important">' +
'<i class="fa-fw fa fa-info"></i>' +
'<strong>Information!</strong> The below report relies on data that is computed in batch. The computaitons were last updated about <span am-time-ago="$rootScope.Helpers.getLastUpdateStatus(' + attrs.updateFor + ')"></span>.' +
'</div>';
}
},
link: function(element) {
}
}
Any help will be appreciated.

Few Mistakes in your code.
1) You can't access Any angular variable in dom. :)
2) if a function defined on $rootScope or $scope, if called from dom by interpolation, then in function argument you can pass only those variable which are defined on $scope not Attrs, which is second mistake. :)
I have just little modified your code to make it work.
Please modified the return statement, based on your requirement.
Hope, I was able to answer your question. :)
app.directive('lastDataUpdateMessage', ['$rootScope', '$compile', function ($rootScope, $compile) {
return {
restrict: 'AE',
replace: true,
template: function (element, attrs) {
if (element && element[0]) {
return '<div class="alert alert-info fade in margin-0" style="margin-top:10px !important">' +
'<i class="fa-fw fa fa-info"></i>' +
'<strong>Information!</strong> The below report relies on data that is computed in batch.'+
'The computaitons were last updated about <span>{{Helpers.getLastUpdateStatus()}}</span>.' +
'</div>';
}
},
link: function (scope, element, attrs) {
scope.type = attrs.updateFor;
scope.Helpers = {
getLastUpdateStatus: function () {
if (scope.type == 'requests') {
return 'requests';
} else if (scope.type == "stats") {
return "stats Test";
} else if (scope.type == "response") {
return "response";
} else {
return "OOPS! Type is empty";
}
}
}
}
}
}])

Related

Decide when to 'templateUrl' of AngularJS directive in the link function

Is it possible to decide whether to use templateUrl parameter in the link function of AngularJS directive?
Suppose I have the following directive:
app.directive('sitesAndImprovements', function() {
return {
restrict: 'E',
replace:true,
templateUrl: '<path-to-file>/site-and-improvments.html',
link: function (scope, elem, attrs) {
scope.testClick = function() {
var myScope = scope;
//debugger;
}
scope.constructionCompleteClick = function () {
if (scope.construction_complete == 'Yes') {
scope.hold_back = '';
scope.percent_complete = 100;
} else
if (scope.construction_complete == 'No') {
scope.hold_back = '1';
if (scope.percent_complete == 100) {
scope.percent_complete = '';
}
}
}
scope.calcTotal = function () {
var total;
total = (scope.main || 0) + (scope.second || 0) + (scope.third || 0) + (scope.fourth || 0);
scope.total = total || null;
}
}
}
})
I want to control whether to use or not to use the templateUrl and also the replace parameters in the link() function.
This is because I already implemented this directive in about 10+ places without using templateUrl and now I want to start using this feature, but I don't want to make changes to existing and working code.
Is that possible and how?
Tarek
I don't think you can do that in the link, but I believe you can turn templateUrl into a function that can return different values for the directive.
Try doing something like this for your templateUrl:
templateUrl: function() {
if (someCondition) {
return '<path-to-file>/site-and-improvments.html';
} else {
return null;
}
},
app.directive('sitesAndImprovements', function() {
return {
restrict: 'E',
replace:function(){
if (aCondition){
return true;
} else {
return false;
}
},
templateUrl: function(){
if (aCondition){
return '<path-to-file>/site-and-improvments.html';
} else {
return undefined;
}
},
link: function (scope, elem, attrs) {
scope.testClick = function() {
var myScope = scope;
//debugger;
}
scope.constructionCompleteClick = function () {
if (scope.construction_complete == 'Yes') {
scope.hold_back = '';
scope.percent_complete = 100;
} else
if (scope.construction_complete == 'No') {
scope.hold_back = '1';
if (scope.percent_complete == 100) {
scope.percent_complete = '';
}
}
}
scope.calcTotal = function () {
var total;
total = (scope.main || 0) + (scope.second || 0) + (scope.third || 0) + (scope.fourth || 0);
scope.total = total || null;
}
}
}
})
Explanation : As stated in the source code, the template will be compiled only if templateUrl is given :
...
if (directive.templateUrl) {
hasTemplate = true;
assertNoDuplicate('template', templateDirective, directive, $compileNode);
templateDirective = directive;
if (directive.replace) {
replaceDirective = directive;
}
// eslint-disable-next-line no-func-assign
nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode,
...
Please, note that aCondition could be an attribute passed to the directive to enable/disable the templateUrl and the replace. Also, keep in mind that the replace is deprecated.

Angular Directive working in all controllers

I have following directive:
app.directive('remoteDirective', ['$location', 'playlistService', 'videoService', function ($location, playlistService, videoService) {
return {
restrict: 'A',
link: function (scope, el, attrs) {
$('body').on('keydown', function (evt)
{
if (evt.keyCode === 38) //Up Arrow
{
scope.currentChannel.number++;
$('#modal1').openModal();
videoService.playVideo(playlistService.getChannel(scope.currentChannel.number).file);
scope.currentChannel.name = scope.playlist[scope.currentChannel.number].title;
scope.$apply();
}
else if (evt.keyCode === 40) //Down Arrow
{
scope.currentChannel.number--;
$('#modal1').openModal();
videoService.playVideo(playlistService.getChannel(scope.currentChannel.number).file);
scope.currentChannel.name = scope.playlist[scope.currentChannel.number].title;
scope.$apply();
}
else if (evt.keyCode === 13) //Enter
{
$('.button-collapse').sideNav('show');
}
else if (evt.keyCode === 27) //Esc
{
$location.path('settings');
scope.$apply();
}
});
}
};
}]);
And i Use it in my main controller like this
<div class="valign-wrapper" style="width: 100%; height: 100%" remote-directive >...</div>
Problem is, that this directive fires in all controller, not only in my main... How to solve? I want to have another actions for theese keys in other controllers...

Angular Number Picker Directive Expression is undefined

I've read a few questions having to do with this topic and cannot figure out what I'm missing in my own direcitve.
angular.module('app')
.directive('numberPicker', [NumberPicker]);
function NumberPicker () {
var getTarget, getType;
getTarget = function (e) { return angular.element(e.target); }
getType = function (e) { return getTarget(e).attr('direction-type'); }
return {
restrict: 'E',
replace: true,
require: 'ngModel',
scope: {
value: '='
},
template: '<div class="ui action input">' +
'<input value="{{value}}" type="text" />' +
'<button class="ui icon button" type="button" direction-type="up" ng-class="{disabled : canUp === false}">' +
'<i class="angle up icon" direction-type="up"></i>' +
'</button>' +
'<button class="ui icon button" type="button" direction-type="down" ng-class="{disabled : canDown === false}">' +
'<i class="angle down icon" direction-type="down"></i>' +
'</button>' +
'</div>',
controller: function ($scope) {},
link: function (scope, element, attrs, ctrl) {
scope.value = 0;
var options = {
min: 0,
max: 10,
step: 1
};
scope.$watch('value', function (newValue) {
scope.canDown = newValue > options.min;
scope.canUp = newValue < options.max;
if (ctrl.$viewValue != newValue) {
ctrl.$setViewValue(newValue);
}
});
var changeNumber = function (event) {
var type = getType(event);
if ('up' === type) {
if (scope.value >= options.max) {
return;
}
scope.value += options.step;
}
if ('down' === type) {
if (scope.value <= options.min) {
return;
}
scope.value -= options.step;
}
}
var btn = element.find('button');
var input = element.find('input');
btn.on('click', function (e) {
scope.$apply(function () {
changeNumber(e);
});
e.preventDefault();
});
input.on('change', function (e) {
scope.value = input[0].value;
scope.$apply();
})
scope.$on('$destroy', function () {
btn.off('touchstart touchend click')
});
}
}
}
The purpose of this was to create a number picker form element for Semantic UI. It was working perfectly a few days ago. And this error is so vague I can't even process where to start. Did I mention I am an Angular noob?
The error is :
Error: [$compile:nonassign] Expression 'undefined' used with directive 'numberPicker' is non-assignable!
How do you use the directive?
According to the definition you need to have both attributes "value" and "ng-model" set.
For example:
<number-picker value="xyz" ng-model="abc"></number-picker>
The error "Expression 'undefined' used with directive..." is normally thrown if one of the scope values is not set.

Trigger directive on ng-click

I using elastic directive for resizing textarea from this answer.
But i using ng-show for textarea, and on click height of textarea is 0.
So i need to use $watch somehow to trigger directive on click, but don't know how.
Html:
<textarea ng-show="showOnClick" elastic ng-model="someProperty"></textarea>
<a ng-click="showOnClick = true"> Show text area </a>
Directive:
.directive('elastic', [
'$timeout',
function($timeout) {
return {
restrict: 'A',
link: function($scope, element) {
$scope.initialHeight = $scope.initialHeight || element[0].style.height;
var resize = function() {
element[0].style.height = $scope.initialHeight;
element[0].style.height = "" + element[0].scrollHeight + "px";
};
element.on("input change", resize);
$timeout(resize, 0);
}
};
}
]);
Here is JSFIDDLE
as requested, the solution is to $watch the ngShow attr and run some sort of init function when the value is true.
user produced jsfiddle
example code:
.directive('elastic', [
'$timeout',
function($timeout) {
return {
restrict: 'A',
scope: {
ngShow: "="
},
link: function($scope, element, attr) {
$scope.initialHeight = $scope.initialHeight || element[0].style.height;
var resize = function() {
element[0].style.height = $scope.initialHeight;
element[0].style.height = "" + element[0].scrollHeight + "px";
};
if (attr.hasOwnProperty("ngShow")) {
function ngShow() {
if ($scope.ngShow === true) {
$timeout(resize, 0);
}
}
$scope.$watch("ngShow", ngShow);
setTimeout(ngShow, 0);
}
element.on("input change", resize);
$timeout(resize, 0);
}
};
}
]);

How to make AngularJS compile the code generated by directive?

Please help me on, How can we make AngularJS compile the code generated by directive ?
You can even find the same code here, http://jsbin.com/obuqip/4/edit
HTML
<div ng-controller="myController">
{{names[0]}} {{names[1]}}
<br/> <hello-world my-username="names[0]"></hello-world>
<br/> <hello-world my-username="names[1]"></hello-world>
<br/><button ng-click="clicked()">Click Me</button>
</div>
Javascript
var components= angular.module('components', []);
components.controller("myController",
function ($scope) {
var counter = 1;
$scope.names = ["Number0","lorem","Epsum"];
$scope.clicked = function() {
$scope.names[0] = "Number" + counter++;
};
}
);
// **Here is the directive code**
components.directive('helloWorld', function() {
var directiveObj = {
link:function(scope, element, attrs) {
var strTemplate, strUserT = attrs.myUsername || "";
console.log(strUserT);
if(strUserT) {
strTemplate = "<DIV> Hello" + "{{" + strUserT +"}} </DIV>" ;
} else {
strTemplate = "<DIV>Sorry, No user to greet!</DIV>" ;
}
element.replaceWith(strTemplate);
},
restrict: 'E'
};
return directiveObj;
});
Here's a version that doesn't use a compile function nor a link function:
myApp.directive('helloWorld', function () {
return {
restrict: 'E',
replace: true,
scope: {
myUsername: '#'
},
template: '<span><div ng-show="myUsername">Hello {{myUsername}}</div>'
+ '<div ng-hide="myUsername">Sorry, No user to greet!</div></span>',
};
});
Note that the template is wrapped in a <span> because a template needs to have one root element. (Without the <span>, it would have two <div> root elements.)
The HTML needs to be modified slightly, to interpolate:
<hello-world my-username="{{names[0]}}"></hello-world>
Fiddle.
Code: http://jsbin.com/obuqip/9/edit
components.directive('helloWorld', function() {
var directiveObj = {
compile:function(element, attrs) {
var strTemplate, strUserT = attrs.myUsername || "";
console.log(strUserT);
if(strUserT) {
strTemplate = "<DIV> Hello " + "{{" + strUserT +"}} </DIV>" ;
} else {
strTemplate = "<DIV>Sorry, No user to greet!</DIV>" ;
}
element.replaceWith(strTemplate);
},
restrict: 'E'
};
return directiveObj;
});
Explanation: The same code should be used in compile function rather than linking function. AngularJS does compile the generated content of compile function.
You need to create an angular element from the template and use the $compile service
jsBin
components.directive('helloWorld', ['$compile', function(compile) {
var directiveObj = {
link: function(scope, element, attrs) {
var strTemplate, strUserT = attrs.myUsername || "";
console.log(strUserT);
if (strUserT) {
strTemplate = "<DIV> Hello" + "{{" + strUserT +"}} </DIV>" ;
} else {
strTemplate = "<DIV>Sorry, No user to greet!</DIV>" ;
}
var e = angular.element(strTemplate);
compile(e.contents())(scope);
element.replaceWith(e);
},
template: function() {
console.log(args);
return "Hello";
},
restrict: 'E'
};
return directiveObj;
}]);

Resources