angularjs directive to manipulate text values - javascript

I want to create angular directives to change or format binded text values.
My values are like this: var priceList = [12.90, 15.90, 80, 55.90];
I want to use a directive and write priceList values as currency format.
<li ng-repeat"price in priceList">
<span currency>{{price}}</span>
</li>
and directive
angular
.module("app.financal")
.directive("currency", [function () {
return {
restrict: "A",
link: function (scope, element, attribute) {
// if currency vale is not null.
var curr = element.html() + "$";
element.html(curr);
}
}
}]);
how can I get <span currency>{{price}}</span> element price value and change in directive.

More simple than a directive, you can use the currency filter to format your data with currency. It already exists in Angular. Filters are used to format displayed data in Angular.
<li ng-repeat"price in priceList">
<span>{{price | currency}}</span>
</li>
See the docs for more details (you can add a symbol if you want).

It may be that you're looking to write your own custom filters, so here's some literature on how to do that:
https://docs.angularjs.org/guide/filter
Consider the following code:
.filter('customCurrency', function() {
return function ( input ) {
// if currency value is not null.
var out = input + '$';
return out;
};
})
This will do what you have outlined above if you change your html to read:
<li ng-repeat="price in priceList">
<span>{{price | customCurrency}}</span>
</li>

#e666's answer will get you to the desired end result. If you're looking to do the work inside the directive you're going to have to access the bound value of the variable directly.
Before we crack into that, I just want to point out that there are two barriers inside the code as written that we should address before moving on. The first is that var priceList = [ 12.90, 15.90, 80, 55.90 ]; isn't currently on $scope. We can fix this by defining it as $scope.priceList = [ 12.90, 15.90, 80, 55.90 ];.
Next, you'll need to ensure that your ng-repeat is assigned a value. ng-repeat"price in priceList" should therefore be rewritten as an assignment of ng-repeat="price in priceList". You'll then have access to scope.price inside the directive. Sorry for the fussy little details, but they needed to be addressed in order to get price into your directive's scope.
As for the directive, as it sits currently, element.html() will return a value of {{price}}, so that's not what we want. Since scope.price is bound data, we can now modify it directly inside the directive to achieve the desired result.
So your HTML will be slightly modified as outlined above:
<li ng-repeat="price in priceList">
<span currency>{{price}}</span>
</li>
and your directive will be:
angular
.module("app.financal")
.directive("currency", [function () {
return {
restrict: "A",
link: function (scope, element, attribute) {
// if currency vale is not null.
scope.price = scope.price + "$";
}
}
}]);
Please keep in mind that this is going to return a list with the "$" appended to the end of the string, so the output will be:
12.9$
15.9$
80$
55.9$
Lastly, here's a little (tangentially) related reading for you:
Using Filters With Directives in AngularJS

Related

Angular JS filter - Do not update DOM value if new value is empty

I'm working on a data heavy Angular project in which I have a ng-repeat where many values get updated every second. If a value is empty, it now updates the DOM to show an empty value; which is correct behaviour.
Needed solution
What I want is a filter or expression which doesn't update the value in the DOM when the new value is empty or NULL. Latching data, I believe they call it.
Possible solutions
I found a couple of possible solutions with $watch, but I believe they are not suitable in ng-repeat, or at least not efficient:
angularjs $watch old value and new value are the same
Example of what I would like to achieve: (this does not work)
app.filter('latchIt', function () {
return function (valueOld, valueNew) {
if (valueNew == '') {
// Do not update the DOM for this value, perhaps a return of the old value
return valueOld;
} else {
return valueNew;
}
};
});
HTML
<div class="item" ng-repeat="item in items track by item.id">
<div class="value" ng-repeat="value in item.data">{{ value | latchIt }}</div>
</div>
Thanks in advance for any help & advice you can give me.
I'd create a directive for that:
codepen: http://codepen.io/anon/pen/EVXgjz
angular.module('app').directive('latched', [function() {
return {
template: '<span>{{saved}}</span>',
link: function($scope) {
$scope.$watch('value', function(val) {
if (!$scope.saved) {
$scope.saved = val || 'Not defined yet';
}
if (val) {
$scope.saved = val;
}
})
},
scope: {
value: '='
}
}
}])
Well I would say that $watch option is good enough to use it, but if you are agains it you can try to combine old collection values and new collection values according to your business rules in controller/directive and after than pass it to view.

How to provide value of $scope variable as string in HTML?

Yeah, This is weird issue.
I am adding code to existing code base where there is a directive
<my-progress ng-progress="processingReport" msg="someString"></my-progress>
The problem is that msg needs to be dereferenced string.
I have a scope variable as $scope.myStatus but providing msg={{myStatus}} is not producing anything
Is there a way to dereference the value of $scope.myStatus so that msg only receives its value?
UPDATE
The directive looks like
.directive('myProgress', function($compile) {
return {
restrict: 'E',
link: function(scope, element, attrs){
var msg = attrs.msg?attrs.msg: "{{"+attrs.ngMsg+"}}";
var template = '<p ng-if="'+ attrs.ngProgress +'"><img src="img/busy-20.gif" alt=""> '+ msg +'</p>';
var el = $compile(template)(scope);
element.replaceWith(el);
}
};
})
UPDATE 1
As per #charlietfl recommendation, the following worked well
In Controller
$scope.runningStatus = {progressStatus: 'Not Started'};
In HTML
<my-progress ng-progress="processingReport" ng-msg="runningStatus.progressStatus"></my-progress>
Based on directive code you should be able to use :
ng-msg="myStatus"
Which would get added into the template as an expression
Note the line in directive:
var msg = attrs.msg?attrs.msg: "{{"+attrs.ngMsg+"}}";
which looks for one or the other attribute and treats them differently
The directive is kind of a hack and should be rewritten, but as is it should accept an interpolated expression:
<my-progress ng-progress="processingReport" msg="{{myStatus}}"></my-progress>
Don't forget the double quotes " around {{myStatus}}
If it still doesn't work, could you test that your scope is okay by adding this test element just after the directive one:
<div>{{myStatus}}</div>

passing object value to directive instead of any other data variable

I am trying to customize this working http://jsfiddle.net/markcoleman/JNqqU/ ,in current working fiddle directly object is assigned . where i am trying to change it to $scope.obj.items . passing object to directive is not working .
do i need to write some $watch fo r the variable ??? i am getting dynamic value that's why i am trying to pass Object value with this .
Code ::
<a href="#" pop-over items="obj.items", title="Mode of transport">
Show Pop over</a>
javascript Directive part ::
scope: {
items: '=',
title: '#'
}
Any suggestion ,
I am trying Below Fiddle
http://jsfiddle.net/JNqqU/652/
You can change your controller to this:
bootstrap.controller('maincnt', function ($scope) {
$scope.obj = { // declare the scope object here with a blank items
items: []
};
$scope.updateitem = function () {
alert('scope update called');
$scope.obj.items = ['car', 'truck', 'plane', 'bike']; // now update here
}
});
Checkout fiddle.
Yes you should be making a watcher.
$scope.$watchCollection('items', function (newValue, oldValue) {
if (newValue) {
buildTemplate(newValue);
}
});
Note: I used watchCollection because it is an array. If it were an object or simple value $watch would be used instead.
You don't need to wrap it into object, but don't 'rewrite' whole array in 'update' method, but push values into it:
bootstrap.controller('maincnt',function($scope){
$scope.items = [];
$scope.updateitem=function(){
alert('scope update called');
$scope.items.push('car', 'truck', 'plane', 'bike');
}
});
http://jsfiddle.net/btfu30k2/1/
$watch isn't necessary too.
You need two changes:
Change in HTML items :: {{obj.items}}
Change in Controller default obj items should be assigned with empty array ( $scope.obj={items:[]}; ) as popOver's $compile is looking for scope.items
See this Working fiddle
Also your testing code {{items | json }} in template can be removed after your observation.

Converting an array to a nested object

What I'm trying to do:
I am trying to dynamically update a scope with AngularJS in a directive, based on the ngModel.
A little back story:
I noticed Angular is treating my ngModel strings as a string instead of an object. So if I have this:
ng-model="formdata.reports.first_name"
If I try to pull the ngModel in a directive, and assign something to it, I end up with $scope["formdata.reports.first_name"]. It treats it as a string instead of a nested object.
What I am doing now:
I figured the only way to get this to work would be to split the ngModel string into an array, so I am now working with:
models = ["formdata", "reports", "first_name"];
This works pretty good, and I am able to use dynamic values on a static length now, like this:
$scope[models[0]][models[1]][models[2]] = "Bob";
The question:
How do I make the length of the dynamic scope dynamic? I want this to be scalable for 100 nested objects if needed, or even just 1.
UPDATE:
I was able to make this semi-dynamic using if statements, but how would I use a for loop so I didn't have a "max"?
if (models[0]) {
if (models[1]) {
if (models[2]) {
if (models[3]) {
$scope[models[0]][models[1]][models[2]][models[3]] = "Bob";
} else {
$scope[models[0]][models[1]][models[2]] = "Bob";
}
} else {
$scope[models[0]][models[1]] = "Bob";
}
} else {
$scope[models[0]] = "Bob";
}
}
This is an answer to
I noticed Angular is treating my ngModel strings as a string instead of an object
Add the require property to your directive then add a fourth ctrl argument to your link function
app.directive('myDirective', function() {
return {
require: 'ngModel',
link: function(scope, element, attributes, ctrl) {
// Now you have access to ngModelController for whatever you passed in with the ng-model="" attribute
ctrl.$setViewValue('x');
}
};
});
Demonstration: http://plnkr.co/edit/Fcl4cUXpdE5w6fHMGUgC
Dynamic pathing:
var obj = $scope;
for (var i = 0; i<models.length-1; i++) {
obj = obj[models[i]];
}
obj[models[models.length-1]] = 'Bob';
Obviously no checks are made, so if the path is wrong it will fail with an error. I find your original problem with angular suspicious, perhaps you could explore a bit in that direction before you resort to this workaround.

AngularJS: input type=number" parse ngModel string value to number and back

I am pretty new to angularJs. My problem is:
My model data are kept in a database and if a project is reopened the data is displayed. With save the data are stored again in the database. I have a problem with displaying the number with
since the value is stored as a string and must be parsed to an integer.
Here is the html:
<numeric-input numericbinding="item.showInt" ng-model="item.operandvalue" ></numeric-input>
Depending on the format of the item value (bool, int, string), the operandvalue is displayed in different ways. This is directed with the value of numericbinding.
I tried to use the following directive to parse the string to an int:
.directive('numericInput', function () {
return {
restrict: 'E',
require: 'ngModel',
scope: {
model: '=ngModel',
numericbinding: '='
},
template: '<input id="integerType" type="number" ng-model="intValue" ng-hide="!numericbinding" >',
link: function (scope) {
//avoid that string and bool is parsed, showInt is false
if (scope.numericbinding === false || scope.numericbinding === undefined) {
return;
}
scope.intValue = parseInt(scope.model, 10);
}
};
})
This way the number is displayed correctly. But if I want to store my data again for the bound item.operandvalue I get a Nullpointer in the backend. I understand that I need to convert the number back to a string. I tried multiple ways,e.g.
ng-change: But it never got in the given function.
Then I wanted to use ngModelController with formatter and parser as described in AngularJS - Formatting ng-model before template is rendered in custom directive
.directive('numericInput', function () {
return {
restrict: 'E',
require: 'ngModel',
scope: {
model: '=ngModel',
numericbinding: '='
},
template: '<input id="integerType" type="number" ng-model="item.operandvalue" ng-hide="!numericbinding" >',
link: function (scope, ngModelController) {
//avoid that string and bool is parsed, showInt is false
if (scope.numericbinding === false || scope.numericbinding === undefined) {
//console.log('not a number - return');
return;
}
ngModelController.$formatters.unshift(function(valueFromModel) {
return parseInt(valueFromModel, 10);
});
ngModelController.$parsers.push(function(valueFromInput) {
return valueFromInput.toString();
});
}
};
})
However, the valueFromModel is undefined. The number is not displayed at all. Also scope.item.operandvalue is undefined. In Chrome the error is: TypeError: Cannot read property 'operandvalue' of undefined.
I think that I should keep the item.operandvalue, which is data bound, as a string and display the int value with a different name. In my first way it seems that item.operandvalue gets a number and the data binding does not work any longer.
How can I fix my problem? Many thanks for any help!
You could use two fields: backing_field and binding_field. When the db is read you write to the backing_field. This could then be used to update the binding_field, e.g. via $watch. You can use two watches to keep them in sync. In case one changes, you convert its value and write it to the other. You might now argue that his will create a loop, but AngularJS detects that nothing changes and will not fire again. Using this, you can update the backing_field, when the binding_field changes due to user interaction and update the binding_field through the backing_field when you get new data from the database.

Categories