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

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>

Related

angularjs directive to manipulate text values

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

Isolation of directives with AngularJS

I'm new to Angular. I created a small directive where the user can input a timestamp and add/substract 1 day. The directive needs to be isolated so that it can appear multiple times in my application.
This is the part where I initialize my dates:
$scope.dateFrom = {
label : 'Date From',
date : mii.utils.date.dateToIntDate(start)
}
$scope.dateUntil = {
label : 'Date Until',
date : mii.utils.date.dateToIntDate(end)
}
In the HTML of my view I create 2 instances of my directive:
<date-input date="dateFrom"></date-input>
<date-input date="dateUntil"></date-input>
The result looks like the below image. Two seperate input fields, each has its own label and default value. So far the isolation seems to be working.
The problem is however, when I click the plus sign of Date From, it will add a day to the value of Date Until. When I look in the debugger on function _addDays I see that $scope.dateInfo is indeed pointing to the dateUntil object even though I am interacting with date From. What am I missing?
date.html
<div id="date-input">
<span>
<img src="assets/img/minus.png" class="icon left" ng-click="yesterday()"/>
{{dateInfo.label}}
<img src="assets/img/add.png" class="icon right" ng-click="tomorrow()"/>
</span>
<input ng-model="dateInfo.date" class="center"/>
</div>
dateController.js
app.directive("dateInput", function() {
return {
restrict: "E",
templateUrl : "app/shared/dateInput/date.html",
scope : {
dateInfo : "=date"
},
link : function($scope, $element, $attrs) {
$scope.yesterday = function(){
_addDays(-1);
};
$scope.tomorrow = function(){
_addDays(1);
}
_addDays = function(days){
var d = mii.utils.date.intDateToDate($scope.dateInfo.date);
var newD = new Date(d);
newD.setDate(d.getDate()+days);
$scope.dateInfo.date = mii.utils.date.dateToIntDate(newD);
}
}
}
});
To fix this, all you need to do is prepend var to the function definition:
var _addDays = function(days){
It was a simple oversight -- Because it didn't specifically state its scope, _addDays is interpreted as a global variable. This means that the second time the function is declared, it clobbers the definition of the first. Even though the function body looks the same, the closure is different in each context.
You're two way binding the dateInfo property to the parent scope (in your case, the parent scope is probably the controller scope). You need to isolate this property to its own child scope after it inherits its initial value from $scope.date from the controller. You can do this with one way data binding
scope : {
dateInfo : "#date"
},
Notice the = has been changed to #
This allows dateInfo to inherit its initial value from $scope.date in your controller. But once that value is inherited, any changes made to the child scope within the directive itself will not bubble up to $scope.date in the controller scope. You'll then have two child scopes, each with a separate dateInfo property that does not corrupt the other.

AngularJS passing an attribute value as a parameter in a function called by ng-change

I can't seem to wrap my mind around how I would pull this off. I have a directive which looks like the following:
.directive('seqWidget', ['Sequence', function(Sequence){
return {
restrict: 'E',
scope: {
placeholder: '#',
option: '#'
},
template: '<fieldset><legend data-ng-transclude></legend><input type="text" placeholder = {{placeholder}} autofocus data-ng-model="index" data-ng-change="retrieve({{option}});"/>{{output}}</fieldset>',
replace: true,
transclude: true,
link: function ($scope, $elem, $attr){
$scope.retrieve = function(key){
//var option = $attr.option;
console.log(key);
}
}
}
}]);
My HTML is as such:
<seq-widget placeholder="Index 0-400" option="accurate">Which index number would you like to gain accuracy on?</seq-widget>
I have tried several other ways of accomplishing a dynamic way of changing my function call based on an attribute value. I would use the '&' prefix but I'd like for this function to be triggered anytime the input is changed. Is there a practical way to achieve what I am trying to do? Or do I need to use jQuery to say something like $('input').on('change', function(){}); in my link function?
You do not have to pass option it is already in the scope, while you set up a text binding option: '#'.
So just do:-
$scope.retrieve = function(key){
console.log($scope.option);
}
It will also work if you remove interpolation, you do not have to interpolate scope variables in an expression.
data-ng-change="retrieve(option);"

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 use custom interpolation symbols for scope

I currently have an underscore.js template that I would also like to use with angular and still be able to use with underscore. I was wondering if it's possible to change the interpolation start and end symbols for a particular scope using a directive, like this:
angular.directive('underscoreTemplate', function ($parse, $compile, $interpolateProvider, $interpolate) {
return {
restrict: "E",
replace: false,
link: function (scope, element, attrs) {
$interpolateProvider.startSymbol("<%=").endSymbol("%>");
var parsedExp = $interpolate(element.html());
// Then replace element contents with interpolated contents
}
}
})
But this spits out the error
Error: Unknown provider: $interpolateProviderProvider <- $interpolateProvider <- underscoreTemplateDirective
Is $interpolateProvider only available for module configuration? Would a better solution be to simply using string replace to change <%= to {{ and %> to }}?
Also, I noticed that element.html() escapes the < in <%= and > in %>. Is there a way to prevent this automatic escaping?
Ok, you have a couple issues here, but I found a solution for you.
Demo
http://jsfiddle.net/colllin/zxwf2/
Issue 1
Your < and > characters are being converted to < and >, so when you call element.html(), you won't even find an instance of < or > in that string.
Issue 2
Since the $interpolate service has already been "provided" by the $interpolateProvider, it doesn't look like you can edit the startSymbol and endSymbol. However, you can convert your custom startSymbol and endSymbol to the angular start/end symbols dynamically in your linking function.
Solution
myApp.directive('underscoreTemplate', function ($parse, $compile, $interpolate) {
return {
restrict: "A",
link: function(scope, element, attrs) {
var startSym = $interpolate.startSymbol();
var endSym = $interpolate.endSymbol();
var rawExp = element.html();
var transformedExp = rawExp.replace(/<%=/g, startSym).replace(/<%-/g, startSym).replace(/%>/g, endSym);
var parsedExp = $interpolate(transformedExp);
scope.$watch(parsedExp, function(newValue) {
element.html(newValue);
});
}
}
});
Alternatives
I'm not sure how, but I'm sure there's a way to instantiate your own custom $interpolate service using the $interpolateProvider (after configuring it for underscore tags).

Categories