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

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.

Related

AngularJS template

**Hello Stack Over Flow community, i'm working on a big project using springboot framework and AngularJS, I am new to angularJS, i've just started learning it for not more than 4 days, i'm trying to use a AngularJS template in order to make my work easier, i choosed a template that contains most of functions that i need in my project, but the problem is that the structure and the syntaxes are so diffrent of what i've seen in angularJS and i don't know which version of angular it is, knowing that i don't have documentation of this template, here's an example of how the code of most of files looks like and the structure too, can you guys help me to know which angular version is?? Structure of the template[Structure of the template 2]`define(['layout/module', 'lodash', 'notification'], function (module, _) {
'use strict';
module.registerDirective('demoStates', function ($rootScope) {
return {
restrict: 'E',
replace: true,
templateUrl: 'app/layout/directives/demo/demo-states.tpl.html',
scope: true,
link: function (scope, element, attributes) {
element.parent().css({
position: 'relative'
});
element.on('click', '#demo-setting', function () {
element.toggleClass('activate')
})
},
controller: function ($scope) {
var $root = $('body');
$scope.$watch('fixedHeader', function (fixedHeader) {
localStorage.setItem('sm-fixed-header', fixedHeader);
$root.toggleClass('fixed-header', fixedHeader);
if (fixedHeader == false) {
$scope.fixedRibbon = false;
$scope.fixedNavigation = false;
}
});
$scope.$watch('fixedNavigation', function (fixedNavigation) {
localStorage.setItem('sm-fixed-navigation', fixedNavigation);
$root.toggleClass('fixed-navigation', fixedNavigation);
if (fixedNavigation) {
$scope.insideContainer = false;
$scope.fixedHeader = true;
} else {
$scope.fixedRibbon = false;
}
});
$scope.$watch('fixedRibbon', function (fixedRibbon) {
localStorage.setItem('sm-fixed-ribbon', fixedRibbon);
$root.toggleClass('fixed-ribbon', fixedRibbon);
if (fixedRibbon) {
$scope.fixedHeader = true;
$scope.fixedNavigation = true;
$scope.insideContainer = false;
}
});
$scope.$watch('fixedPageFooter', function (fixedPageFooter) {
localStorage.setItem('sm-fixed-page-footer', fixedPageFooter);
$root.toggleClass('fixed-page-footer', fixedPageFooter);
});
$scope.$watch('insideContainer', function (insideContainer) {
localStorage.setItem('sm-inside-container', insideContainer);
$root.toggleClass('container', insideContainer);
if (insideContainer) {
$scope.fixedRibbon = false;
$scope.fixedNavigation = false;
}
});
$scope.$watch('rtl', function (rtl) {
localStorage.setItem('sm-rtl', rtl);
$root.toggleClass('smart-rtl', rtl);
});
$scope.$watch('menuOnTop', function (menuOnTop) {
$rootScope.$broadcast('$smartLayoutMenuOnTop', menuOnTop);
localStorage.setItem('sm-menu-on-top', menuOnTop);
$root.toggleClass('menu-on-top', menuOnTop);
if(menuOnTop)$root.removeClass('minified');
});
$scope.$watch('colorblindFriendly', function (colorblindFriendly) {
localStorage.setItem('sm-colorblind-friendly', colorblindFriendly);
$root.toggleClass('colorblind-friendly', colorblindFriendly);
});
$scope.fixedHeader = localStorage.getItem('sm-fixed-header') == 'true';
$scope.fixedNavigation = localStorage.getItem('sm-fixed-navigation') == 'true';
$scope.fixedRibbon = localStorage.getItem('sm-fixed-ribbon') == 'true';
$scope.fixedPageFooter = localStorage.getItem('sm-fixed-page-footer') == 'true';
$scope.insideContainer = localStorage.getItem('sm-inside-container') == 'true';
$scope.rtl = localStorage.getItem('sm-rtl') == 'true';
$scope.menuOnTop = localStorage.getItem('sm-menu-on-top') == 'true' || $root.hasClass('menu-on-top');
$scope.colorblindFriendly = localStorage.getItem('sm-colorblind-friendly') == 'true';
$scope.skins = appConfig.skins;
$scope.smartSkin = localStorage.getItem('sm-skin') || appConfig.smartSkin;
$scope.setSkin = function (skin) {
$scope.smartSkin = skin.name;
$root.removeClass(_.pluck($scope.skins, 'name').join(' '));
$root.addClass(skin.name);
localStorage.setItem('sm-skin', skin.name);
$("#logo img").attr('src', skin.logo);
};
if($scope.smartSkin != "smart-style-0"){
$scope.setSkin(_.find($scope.skins, {name: $scope.smartSkin}))
}
$scope.factoryReset = function () {
$.SmartMessageBox({
title: "<i class='fa fa-refresh' style='color:green'></i> Clear Local Storage",
content: "Would you like to RESET all your saved widgets and clear LocalStorage?1",
buttons: '[No][Yes]'
}, function (ButtonPressed) {
if (ButtonPressed == "Yes" && localStorage) {
localStorage.clear();
location.reload()
}
});
}
}
}
});
});
`**

Custom directive don't want scope in this how can i achieve?

function contentValidator() {
var _matchContent = {
require: 'ngModel',
scope: {
contentValidator: '='
},
link: contentValidatorFn
};
return _matchContent;
function contentValidatorFn(scope, element, attrs, ctrl) {
scope.$watch(function() {
var combined;
if (scope.contentValidator || ctrl.$viewValue) {
combined = scope.contentValidator + '_' + ctrl.$viewValue;
}
return combined;
}, function(value) {
if (value) {
var origin = scope.contentValidator;
if (origin !== ctrl.$viewValue) {
ctrl.$setValidity("contentValidator", false);
return undefined;
} else {
ctrl.$setValidity("contentValidator", true);
return ctrl.$viewValue;
}
}
});
}
}
I'd suggest you do use $validators pipeline to set validity of field of form.
ngModel.$validators.contentValidator = function(modelValue, viewValue) {
var value = modelValue || viewValue;
return condition ? value : undefined; //condition would be what you wanted to check
};
Basically when you return defined value from $validators contentValidator function, but when you don't return angular will add content-validator class on that fields & the same property gets added to that form field like myForm.formFields.$error.contentValidator = true

How to get selected value from ng-autocomplete directive to controller

I am using a directive for auto complete / auto suggest in angular Js taken from http://demo.jankuri.com/ngAutocomplete/. It is working fine getting the data from server and filtering it. But I am facing problem into select and use that select item from the auto complete.
Here is the code of directive what I am using for this...
app.factory('ngAutocompleteService', ['$http', function($http)
{
var self = this;
self.getData = function (url, keyword) {
return $http.get(url, { query: keyword });
};
return self;
}])
app.directive('ngAutocomplete', ['$timeout','$filter','ngAutocompleteService',
function($timeout, $filter, ngAutocompleteService)
{
'use strict';
var keys = {
left : 37,
up : 38,
right : 39,
down : 40,
enter : 13,
esc : 27
};
var setScopeValues = function (scope, attrs) {
scope.url = base_url+attrs.url || null;
scope.searchProperty = attrs.searchProperty || 'skills';
scope.maxResults = attrs.maxResults || 10;
scope.delay = parseInt(attrs.delay, 10) || 300;
scope.minLenth = parseInt(attrs.minLenth, 10) || 2;
scope.allowOnlyResults = scope.$eval(attrs.allowOnlyResults) || false;
scope.placeholder = attrs.placeholder || 'Search...';
};
var delay = (function() {
var timer = 0;
return function (callback, ms) {
$timeout.cancel(timer);
timer = $timeout(callback, ms);
};
})();
return {
restrict: 'E',
require: '?ngModel',
scope: true,
link: function(scope, element, attrs, ngModel) {
setScopeValues(scope, attrs);
scope.results = [];
scope.currentIndex = null;
scope.getResults = function () {
if (parseInt(scope.keyword.length, 10) === 0) scope.results = [];
if (scope.keyword.length < scope.minLenth) return;
delay(function() {
ngAutocompleteService.getData(scope.url, scope.keyword).then(function(resp) {
scope.results = [];
var filtered = $filter('filter')(resp.data, {skills: scope.keyword});
for (var i = 0; i < scope.maxResults; i++) {
scope.results.push(filtered[i]);
}
scope.currentIndex = 0;
if (scope.results.length) {
scope.showResults = true;
}
});
}, scope.delay);
};
scope.selectResult = function (r) {
scope.keyword = r.skills;
ngModel.$setViewValue(r.skills);
scope.ngModel = r.skills;
ngModel.$render();
scope.showResults = false;
};
scope.clearResults = function () {
scope.results = [];
scope.currentIndex = null;
};
scope.hoverResult = function (i) {
scope.currentIndex = i;
}
scope.blurHandler = function () {
$timeout(function() {
if (scope.allowOnlyResults) {
var find = $filter('filter')(scope.results, {skills: scope.keyword}, true);
if (!find.length) {
scope.keyword = '';
ngModel.$setViewValue('');
}
}
scope.showResults = false;
}, 100);
};
scope.keyupHandler = function (e) {
var key = e.which || e.keyCode;
if (key === keys.enter) {
scope.selectResult(scope.results[scope.currentIndex]);
}
if (key === keys.left || key === keys.up) {
if (scope.currentIndex > 0) {
scope.currentIndex -= 1;
}
}
if (key === keys.right || key === keys.down) {
if (scope.currentIndex < scope.maxResults - 1) {
scope.currentIndex += 1;
}
}
if (key === keys.esc) {
scope.keyword = '';
ngModel.$setViewValue('');
scope.clearResults();
}
};
},
template:
'<input type="text" class="form-control" ng-model="keyword" placeholder="{{placeholder}}" ng-change="getResults()" ng-keyup="keyupHandler($event)" ng-blur="blurHandler()" ng-focus="currentIndex = 0" autocorrect="off" autocomplete="off">' +
'<input type="hidden" ng-model="skillIdToBeRated">'+
'<div ng-show="showResults">' +
' <div ng-repeat="r in results | filter : {skills: keyword}" ng-click="selectResult(r)" ng-mouseover="hoverResult($index)" ng-class="{\'hover\': $index === currentIndex}">' +
' <span class="form-control">{{ r.skills }}</span>' +
' </div>' +
'</div>'
};
}]);
I am unable to get value which is selected by ng-click="selectResult(r)" function. The value is showing into text field but not getting it into controller.
I was also using the same directive for showing the auto complete text box. I have tried the following for getting the selected value from auto complete.
in HTML
<div ng-controller="Cntrl as cntrl">
<ng-autocomplete ng-model="cntrl.selectedValue" url="url" search-property="keyword" max-results="10" delay="300" min-length="2" allow-only-results="true"></ng-autocomplete>
</div>
in JavaScript
app.controller('Cntrl', function($scope, $http) {
var self = this;
self.selectedValue = '';
$scope.getSelectedValue = function(){
console.log(self.selectedValue);
}
});
I hope this may help you.
I ran into the same issue. I ended up just watching the property from the details attribute in the ng-autocomplete input and it works pretty well.
$scope.$watch(function() {
return vm.location_result;
}, function(location) {
if (location) {
vm.location_list.push(location);
vm.location = '';
}
});
Fiddle Example: http://jsfiddle.net/n3ztwucL/
GitHub Gist: https://gist.github.com/robrothedev/46e1b2a2470b1f8687ad

AngularJS filter with multiple arguments

In my page I have:
<h1>{{name | fontResize: 25:42}}</h1>
and I have a filter
angular.module('myApp').filter('fontResize', function () {
return function (text, length, end) {
if (!text) {
return text;
}
if (isNaN(length))
length = 10;
if (end === undefined)
end = "...";
if (text.length <= length || text.length - end.length <= length) {
$('h1').css('fontSize', '30px');
return text;
} else {
$('h1').css('fontSize', '12px');
return text;
}
};
});
How do I set the fontsize for my second argument (42) ?
Filters are not for manipulating DOM. You should create a directive.
There are 2 example directives:
First:
.directive('fontResize', function() {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
var size = attrs.size || '30px';
var length = attrs.length || 10;
attrs.$observe('text', function() {
var text = attrs.text;
if (text.length <= length) {
elem.css('fontSize', size);
} else {
elem.css('fontSize', '12px');
}
elem.text(attrs.text);
});
}
}
})
HTML:
<h1 font-resize text="{{name}}" size="42px"></h1>
And the second one:
.directive('fontResize2', function() {
return {
restrict: 'A',
scope: {},
link: function(scope, elem, attrs) {
var size = attrs.size;
var length = attrs.length || 10;
scope.$watch(function() {
return elem.text();
}, function(newVal, oldVal) {
setText(newVal)
})
function setText(text) {
if (text.length <= length) {
elem.css('fontSize', size);
} else {
elem.css('fontSize', '12px');
}
elem.text(attrs.text);
}
}
}
});
HTML:
<h1 font-resize2 size="60px">{{name}}</h1>
You can extend them as you wish.
Here is the plunkr: http://plnkr.co/edit/uO9uYqcqLPuqAhJdtJ9m?p=preview

AngularJS $watch newValue is undefined

I am trying to make a alert service with a directive. It is in the directive part I have some trouble. My have a directive that looks like this:
angular.module('alertModule').directive('ffAlert', function() {
return {
templateUrl: 'components/alert/ff-alert-directive.html',
controller: ['$scope','alertService',function($scope,alertService) {
$scope.alerts = alertService;
}],
link: function (scope, elem, attrs, ctrl) {
scope.$watch(scope.alerts, function (newValue, oldValue) {
console.log("alerts is now:",scope.alerts,oldValue, newValue);
for(var i = oldValue.list.length; i < newValue.list.length; i++) {
scope.alerts.list[i].isVisible = true;
if (scope.alerts.list[i].timeout > 0) {
$timeout(function (){
scope.alerts.list[i].isVisible = false;
}, scope.alerts.list[i].timeout);
}
}
}, true);
}
}
});
The reason for the for-loop is to attach a timeout for the alerts that has this specified.
I will also include the directive-template:
<div class="row">
<div class="col-sm-1"></div>
<div class="col-sm-10">
<div alert ng-repeat="alert in alerts.list" type="{{alert.type}}" ng-show="alert.isVisible" close="alerts.close(alert.id)">{{alert.msg}}</div>
</div>
<div class="col-sm-1"></div>
</div>
When I run this, I get this error in the console:
TypeError: Cannot read property 'list' of undefined
at Object.fn (http://localhost:9000/components/alert/ff-alert-directive.js:10:29)
10:29 is the dot in "oldValue.list" in the for-loop. Any idea what I am doing wrong?
Edit: I am adding the alertService-code (it is a service I use to keep track of all the alerts in my app):
angular.module('alertModule').factory('alertService', function() {
var alerts = {};
var id = 1;
alerts.list = [];
alerts.add = function(alert) {
alert.id = id;
alerts.list.push(alert);
alert.id += 1;
console.log("alertService.add: ",alert);
return alert.id;
};
alerts.add({type: "info", msg:"Dette er til info...", timeout: 1000});
alerts.addServerError = function(error) {
var id = alerts.add({type: "warning", msg: "Errormessage from server: " + error.description});
// console.log("alertService: Server Error: ", error);
return id;
};
alerts.close = function(id) {
for(var index = 0; index<alerts.list.length; index += 1) {
console.log("alert:",index,alerts.list[index].id);
if (alerts.list[index].id == id) {
console.log("Heey");
alerts.list.splice(index, 1);
}
}
};
alerts.closeAll = function() {
alerts.list = [];
};
return alerts;
});
try like this , angular fires your watcher at the first time when your directive initialized
angular.module('alertModule').directive('ffAlert', function() {
return {
templateUrl: 'components/alert/ff-alert-directive.html',
controller: ['$scope','alertService',function($scope,alertService) {
$scope.alerts = alertService;
}],
link: function (scope, elem, attrs, ctrl) {
scope.$watch(scope.alerts, function (newValue, oldValue) {
if(newValue === oldValue) return;
console.log("alerts is now:",scope.alerts,oldValue, newValue);
for(var i = oldValue.list.length; i < newValue.list.length; i++) {
scope.alerts.list[i].isVisible = true;
if (scope.alerts.list[i].timeout > 0) {
$timeout(function (){
scope.alerts.list[i].isVisible = false;
}, scope.alerts.list[i].timeout);
}
}
}, true);
}
}
});
if you have jQuery available, try this
if(jQuery.isEmptyObject(newValue)){
return;
}
inside scope.$watch

Resources