My approach is this it is translating one time when i applied on element on angular template but when i applied multiple directive as template making multiple time api call so how to avoid it.
good solution please comment
var directive = {
restrict: 'AE',
scope: false,
multiElement: true,
link: link
};
$rootScope.cityName = cookieService.getCookie('lang');
$rootScope.translateThese = [];
$rootScope.translationData = '';
$rootScope.translationCounter = 0;
function link(scope, element, attr) {
scope.$evalAsync(function() {
$rootScope.translateThese.push(element[0]);
scope.$on('translateAPISuccess', function(e, data) {
angular.forEach($rootScope.translateThese, function(elem) {
var translatedString = $rootScope.translationData[elem.innerHTML.trim().toLowerCase()];
elem.innerHTML = translatedString;
// $compile(element.contents())(scope);
})
})
});
// This is a 0 second timeout to push the execution of this
// directive to the next digest cycle where all the dynamic values
// are present. I can also use scope.$evalSync for this purpose.
//
// #example
// $timeout(function() {
//
$timeout(function() { // We could also use scope.$watch here watching $viewContentLoaded // scope.$watch('$viewContentLoaded', function(e, u) {
$rootScope.translationCounter++;
var translateThese = [];
// if (scope.$last === true && scope.translationCounter === $rootScope.translateThese.length) { //we can use this but scope.$last isn't working
if ($rootScope.translationCounter === $rootScope.translateThese.length) {
$rootScope.translateThese.forEach(function(sentence) {
// trim.apply(sentence).split(" ").forEach(function(v) {
translateThese.push(sentence.innerHTML.replace(/[^a-zA-Z ]/g, ""));
// })
})
dataService.staticTranslation('guj', translateThese).then(function (response) {
$rootScope.translationData = response.response;
scope.$emit('translateAPISuccess', response.response);
// When using the translateAPI from horizontal
// $rootScope.translationData = JSON.parse(response.response).StaticLangTranslationApplicationResponse.StaticLangTranslationApplication.translations;
// scope.$emit('translateAPISuccess', JSON.parse(response.response).StaticLangTranslationApplicationResponse.StaticLangTranslationApplication.translations);
});
}
});
}
return directive;
}
You could look at using the Angular Translate service: https://github.com/angular-translate/angular-translate
It offers a filter, directive and service for translating your content.
Related
Here is a sample angular directive to prevent typing non-numeric keys (StackOverflow answer).
I want to write something like this fiddle to use the is-number directive in several inputs. Please consider that since I have various different directives in my inputs, I cannot use the same template as suggested in the update of mentioned answer above.
var $scope;
var app = angular.module('myapp', []);
app.controller('Ctrl', function($scope) {
$scope.myNnumber1 = 1;
$scope.myNnumber2 = 1;
});
app.directive('isNumber', function () {
return {
require: 'ngModel',
link: function (scope, element) {
scope.$watch(element.ngModel, function(newValue,oldValue) {
newValue = String(newValue);
newValue = newValue.replace('۰', '0').replace('۱', '1').replace('۲', '2').replace('۳', '3').replace('۴', '4').replace('۵', '5').replace('۶', '6').replace('۷', '7').replace('۸', '8').replace('۹', '9');
var arr = String(newValue).split("");
if (arr.length === 0) return;
if (arr.length === 1 && (arr[0] == '-' || arr[0] === '.' )) return;
if (arr.length === 2 && newValue === '-.') return;
if (isNaN(newValue)) {
element.ngModel = oldValue;
}
});
}
};
Update:
Please consider that I need to do some processes to convert non English numbers and so on. I created a new fiddle here based on the the Angular_10's answer. Now, every thing is fine except the cursor position while typing Persian numbers. When I type A Persian Number, it is replaced with English equivalent number, but the cursor suddenly jumps to the end.
OK ! Looking at your requirement I've took liberty and wrote more customised directive.
Here is the fiddle for the same
Problem
The example from which you referred and made changes to the given directive is causing the issue.
Your $scope variable names are wrong in HTML/JS ($scope.myNnumber1 = 1;
$scope.myNnumber2 = 1; in JS and in HTML it was ng-model="myNumber1")
You are accessing element ng-model and trying to modify it through directive which is bad practice and also the root cause for directive to not to work.As you are not changing the ng-model value but in turn modifying HTML element value which angular will not recognise.
More over using $watch in directive is not always preferable for performance sake.
Solution
app.directive('isNumber', function() {
return {
require: 'ngModel',
restrict: 'A',
link: function(scope, element, attr, ctrl) {
function inputValue(val) {
if (val) {
var numeric = val.replace(/[^- 0-9]/g, '');
if (numeric !== val) {
ctrl.$setViewValue(numeric );
ctrl.$render();
}
return numeric;
}
return undefined;
}
ctrl.$parsers.push(inputValue);
}
};
});
When controller communication is required from directive we can pass Controller as 4 param in the link function.From that Ctrl param we can modify/view things from controller scope.
Using some basic regex expression to find out what is the entered input and set it in the controller scope object view value.
ctrl.$setViewValue(numeric); //to set the value in the respective ngModdel
ctrl.$render(); //to display the changed value
More about $setViewValue
I finally used the below directive. This directive converts persian number and do not let no numbers to be typed in the text box. Special thanks to Angular_10. I awarded 50 bounties to him for his help.
app.directive('fixPersianAndNoNumberInput', function ($filter) {
return {
require: 'ngModel',
restrict: 'EA',
link: function (scope, element, attr, controller) {
function inputValue(val) {
if (val) {
let numeric = parseInt(String(val).replace('۰', '0').replace('۱', '1').replace('۲', '2').replace('۳', '3').replace('۴', '4').replace('۵', '5').replace('۶', '6').replace('۷', '7').replace('۸', '8').replace('۹', '9').replace(' ', '000').replace(/[^- 0-9]/g, ''));
if (numeric !== val) {
controller.$setViewValue(numeric);
controller.$render();
let value;
let updateOn, debounce;
if (controller.$options) {
if (controller.$options.getOption) {
updateOn = controller.$options.getOption('updateOn');
debounce = controller.$options.getOption('debounce');
} else {
updateOn = controller.$options.updateOn;
debounce = controller.$options.debounce;
}
}
if (updateOn === 'blur' || debounce) {
value = controller.$viewValue;
for (let i = controller.$parsers.length - 1; i >= 0; i--) {
value = controller.$parsers[i](value);
}
} else {
value = controller.$$rawModelValue;
}
for (let j = controller.$formatters.length - 1; j >= 0; j--) {
value = controller.$formatters[j](value);
}
controller.$viewValue = value;
controller.$render();
}
return numeric;
}
return undefined;
}
controller.$parsers.push(inputValue);
controller.$formatters.push((value) => {
if ([undefined, null, ''].indexOf(value) === -1) {
return $filter('currency')(value, '', 0);
}
return value;
});
}
};
});
I have the following directive that I'm using for validation on a multi-select to allow for (dynamic) length validation of the number of selected items.
(function() {
'use strict';
angular
.module('myModule')
.directive('listLength', listLength);
listLength.$inject = ['$parse'];
function listLength($parse) {
var directive = {
require: 'ngModel',
restrict: 'A',
link: link
};
function link(scope, elem, attr, ngModel) {
var length = 0;
var exp = $parse(attr.listLength);
if (exp.constant) {
// Single value, no need to watch
length = exp(scope);
} else {
// We have an expression, need to watch for changes
scope.$watch(attr.listLength, function(newVal, oldVal) {
length = newVal;
});
}
//For DOM -> model validation
ngModel.$parsers.unshift(function(value) {
if (!angular.isUndefined(value) && value !== "") {
var valid = value.length === length;
ngModel.$setValidity('listLength', valid);
return valid ? value : undefined;
}
return value;
});
//For model -> DOM validation
ngModel.$formatters.unshift(function(value) {
if (!angular.isUndefined(value) && value !== "") {
var valid = value.length === length;
ngModel.$setValidity('listLength', valid);
}
return value;
});
}
return directive;
}
})();
In order to have it update properly, I need to trigger the validation to be run when the expression (attr.listLength) updates, but it's not obvious to me how to achieve this. I tried setting ngModel.$dirty to true but it still does not update.
Try change the watch parameter like that:
scope.$watch("listLength", function(newVal, oldVal) {
length = newVal;
});
Looks like the only way to do this in Angular 1.2 is to use ngModel.$setViewValue, which triggers all the validation functions. So if I do
scope.$watch(attr.listLength, function(newVal, oldVal) {
length = newVal;
ngModel.$setViewValue(ngModel.$viewValue); // Set view value to itself...
});
It then triggers the validation functions. In angularjs 1.3+ this can be done with ngModel.$validate()
Quick question, so I'm using tg-dynamic-directive to loop through a json file and output following tree. (See image attached)
So the problem is, when the "tree" gets very long there are some serious performance problems because the browser needs to render a lot of items. (I'm talking about 1000 or longer). So what I'm trying to do is initially only load first 2 levels and the rest will be collapsed. When a user clicks expand arrow of each element I need to render its children. (If that makes sense). So basically run tg-dynamic-directive again.
When page starts rendering and function that returns template with the html is fired I have this to check if its first 2 levels:
$scope.getView = function (item) {
// Check if item is defined
if(typeof item != 'undefined') {
// Load course, label and module first!
if(item.type == 'course' || item.type == 'label' || item.type == 'module' || item.type == 'course_assessment' || item.type == 'module_assessment') {
// Return Template
return 'nestable_item.html';
} else {
// Otherwise return null
return null;
}
} else {
return null;
}
};
Then what I need to do is call that directive again when expand arrow is clicked.
This is the directive:
angular.module('tg.dynamicDirective', [])
.directive('tgDynamicDirective', ['$compile',
function($compile) {
'use strict';
function templateUrlProvider(getView, ngModelItem) {
if (getView) {
if (typeof getView === 'function') {
var templateUrl = getView(ngModelItem) || '';
if (templateUrl) {
return templateUrl;
}
} else if (typeof getView === 'string' && getView.length) {
return getView;
}
}
return '';
}
return {
restrict: 'E',
require: '^ngModel',
scope: true,
template: '<div ng-include="templateUrl"></div>',
link: function(scope, element, attrs, ngModel) {
scope.$watch(function() {
var ngModelItem = scope.$eval(attrs.ngModel);
var getView = scope.$eval(attrs.tgDynamicDirectiveView);
scope.ngModelItem = ngModelItem;
return templateUrlProvider(getView, ngModelItem);
}, function(newValue, oldValue) {
scope.templateUrl = newValue;
});
}
};
}
]);
My question is how can I fire tg-dynamic-directive again when expand arrow is clicked from the controller.
Use $rootScope.broadcast("XXXXXX"); and catch it
$rootScope.$on("XXXXXX", function() {
// function call
})
in directive and call the function you want.
or
use document.createElement("tg-dynamic-directive");
The directive notification should delete "itself" after 5 seconds. However some elements get missed and some get deleted more than once. Identifier property is unique for each notification. Thanks for help.
Factory
angular.module('AdS').factory('notificationFactory', function () {
var notificationFactory = {};
notificationFactory.notifications = [];
notificationFactory.identifier =0;
notificationFactory.add = function(note){
if(typeof note!=='undefined'){
notificationFactory.identifier++;
note.identifier = notificationFactory.identifier;
notificationFactory.notifications.push(note);
}
}
notificationFactory.delete = function (note) {
if(typeof note!=='undefined'){
for(var i =0;i<notificationFactory.notifications.length;i++){
if(notificationFactory.notifications[i].identifier==note.identifier){
notificationFactory.notifications.splice(i,1);
}
}
}
return "";
}
notificationFactory.getNotifications = function () {
return notificationFactory.notifications;
}
return notificationFactory;
});
Directive
angular.module('AdS').directive('siteNotification', [
'$timeout',
function ($timeout) {
return {
restric: "E",
templateUrl: "/Templates/htmlBits/notification.html",
scope: {
note:"=",
center:"="
},
link: function (scope, element, attrs) {
$timeout(function () {
scope.center.delete(scope.note);
}, 5000);
scope.delete=function(note){
scope.center.delete(note);
}
}
};
}
]);
html
<site-notification ng-repeat="not in notificationCenter.notifications track by $index" center=notificationCenter note=not ></site-notification>
Rather than using an array for notificationFactory.notifications, you could use an object, with the unique identifier pointing to your note like so:
notificationFactory.notifications = {};
notificationFactory.add = function(note) {
if (typeof note!=='undefined') {
notificationFactory.identifier++;
notificationFactory.notifications[notificationFactory.identifier] = note;
}
}
notificationFactory.delete = function (note) {
if(typeof note!=='undefined'){
notificationFactory.notifications[notificationFactory.identifier] = null;
}
return "";
}
Also, in your directive, you seem to be injecting notificationFactory via the html. This is very unusual, the typical way to inject your factory is as follows:
angular.module('AdS').directive('siteNotification', [
'$timeout',
'notificationFactory',
function ($timeout, notificationFactory) {
...
}
The only reason I can see to do it differently way is if you want to be able to inject a different type of factory into your directive.
I have two directives:
// Generated by CoffeeScript 1.6.3
app.directive("focusMe", function() {
return {
scope: {
focus: "=focusMe"
},
link: function(scope, element) {
return scope.$watch("focus", function(value) {
if (value === true) {
element[0].focus();
return scope.focus = false;
}
});
}
};
});
and:
// Generated by CoffeeScript 1.6.3
app.directive("cleanMe", function() {
return {
scope: {
clean: "=cleanMe"
},
link: function(scope, element) {
return scope.$watch("clean", function(value) {
if (value === true) {
element[0].value = "";
return scope.clean = false;
}
});
}
};
});
and this input (angularUI):
<input type="text" ng-model="addUserSelected" typeahead="emp.value for emp in allUsers | filter:$viewValue | limitTo:5" typeahead-editable="false" typeahead-on-select="addLine($item.id)" focus-me="usersFocusInput" clean-me="usersCleanInput">
I get this error:
Error: [$compile:multidir] http://errors.angularjs.org/1.2.3/$compile/multidir?p0=cleanMe&p1=focusMe&p…2%20focus-me%3D%22usersFocusInput%22%20clean-me%3D%22usersCleanInput%22%3E
what do I do wrong?
If I remove the clean-me property from the html it works.
Thanks
There is no real need for isolated scopes here. Use a "normal" directive scope and the directive will just inherit from the parent, like this:
// Generated by CoffeeScript 1.6.3
app.directive("focusMe", function() {
return {
link: function(scope, element, attrs) {
return scope.$watch(attrs.focusMe, function(focusMe) {
if (focusMe.value === true) {
element[0].focus();
return scope[attrs.focusMe].value = false;
}
});
}
};
});
// Generated by CoffeeScript 1.6.3
app.directive("cleanMe", function() {
return {
link: function(scope, element, attrs) {
return scope.$watch(attrs.cleanMe, function(cleanMe) {
if (cleanMe.value === true) {
element[0].value = "";
return scope[attrs.cleanMe].value = false;
}
});
}
};
});
Ignore this part if you already know how inheritance works, just adding for completeness:
Note that I am using the [attrs.focusMe].value, not just [attrs.focusMe]. The reason is how inheritance works in javascript. These directives are child scopes, so if you try to do scope[attrs.focusMe] = false you will set a local variable in the scope of the directive, i.e. you will not affect the parent scope (the controller where it is used). However, if you make the focusMe model (whatever it is) an object in the parent scope and then change a value on that object, then it will not set a local variable, it will instead update the parent. So:
scope[attrs.focusMe] = false; // Sets a local variable, does not affect the parent
scope[attrs.focusMe].value = false; // Updates focusMe on the parent
Here is a good answer about inheritance if you want an in depth guide: What are the nuances of scope prototypal / prototypical inheritance in AngularJS?
You have two directives which require isolated scope on the same element which is not allowed.
The reason this is not allowed is because if you have some template {{...}} code inside the directive, then it will be unclear which scope it should take its values from.
Consider instead of isolating scope, using attribute.$observe to watch the cleanMe and focusMe properties and acting on those.
app.directive("focusMe", function() {
return {
link: function(scope, element, attributes) {
attributes.$observe("focusMe", function(value) {
if (value === true) {
element[0].focus();
scope.focus = false;
}
});
}
};
});
and:
app.directive("cleanMe", function() {
return {
link: function(scope, element, attributes) {
attributes.$observe("cleanMe", function(value) {
if (value === true) {
element[0].value = "";
return scope.clean = false;
}
});
}
};
});
I found the solution finally :)
// Generated by CoffeeScript 1.6.3
app.directive("focusMe", function() {
return {
link: function(scope, element, attributes) {
return scope.$watch(attributes.focusMe, function(value) {
if (scope[value] === true) {
element[0].focus();
return scope[attributes.focusMe] = false;
}
});
}
};
});
// Generated by CoffeeScript 1.6.3
app.directive("cleanMe", function() {
return {
link: function(scope, element, attributes) {
return scope.$watch(attributes.cleanMe, function(value) {
if (value === true) {
element[0].value = "";
return scope[attributes.cleanMe] = false;
}
});
}
};
});
In the html usersFocusInput and usersCleanInput are parameters in the scope that is wht I use scope[attributes.focusMe] to get this parameter and change him to false.
<input type="text" ng-model="addUserSelected" typeahead="emp.value for emp in allUsers | filter:$viewValue | limitTo:5" typeahead-editable="false" typeahead-on-select="addLine($item.id)" focus-me="usersFocusInput" clean-me="usersCleanInput">