passing object value to directive instead of any other data variable - javascript

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.

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

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.

alter string within angularjs expression

It's possible to make for exmple :
<p>{{data.country}}</p> print out Country:New York, how to alter that to Country:NY ?
You could do it using Regx .match and strings join method.
Markup
{{data.country.match(pattern).join('')}}
Controller
app.controller('MainCtrl', function($scope, $http) {
$scope.data = {};
$scope.data.country = 'New York';
$scope.pattern = /\b(\w)/g;
});
Demo Plunkr
Create a custom filter:
.filter("countryCaps", function(){
return function(country){
return "Country: " + country.match(/[A-Z]/g).join("")
}
})
<p>{{data.country | countryCaps}}</p>
Demo
Always remember that what you have inside the {{...}} is an AngularJS expression. That means you can call functions as well.
Just put a function on your scope like this:
$scope.countryTranslator = function (country) {
if (country === 'New York') {
return 'NY';
}
};
and call it like this instead:
{{ countryTranslator(data.country) }}.
Obviously your "translator" should have better code than my example, but you get the idea.
You should use custom filter for this purpose.
But you still have a problem with database of cities full name and they shortcuts, for this purpose, you can use static json file, or use side server service in your choice.

Unable to call Object.keys in angularjs

I'm using a UI.Bootstrap accordion and I've defined my heading like so:
<accordion-group ng=repeat="(cname, stations) in byClient">
<accordion-heading>
{{ cname }} <span class="pull-right"> {{ Object.keys(stations).length }} Stations</span>
</accordion-heading>
When that displays the Object.keys(stations).length resolves to nothing. If I put that same length call in my controller I get back the expected count. Is there something preventing the method call from working in AngularJS?
The rest of the accordion that uses stations acts as expected, so I know that it's being populated properly. The byClient data structure basically looks like so:
{
"Client Name" : {
"Station Name": [
{...},
{...}
]
}
}
Yes, That is because Object is a part of window/global and angular cannot evaluate that expression against the scope. When you specify Object.keys in your binding angular tries to evaluate it against the $scope and it does not find it. You could store the reference of object.keys in some utility in rootScope and use it anywhere in the app.
Something like this:-
angular.module('yourApp',[deps...]).run(function($rootScope){
//Just add a reference to some utility methods in rootscope.
$rootScope.Utils = {
keys : Object.keys
}
//If you want utility method to be accessed in the isolated Scope
//then you would add the method directly to the prototype of rootScope
//constructor as shown below in a rough implementation.
//$rootScope.constructor.prototype.getKeys = Object.keys;
});
and use this as:-
<span class="pull-right"> {{ Utils.keys(stations).length }} Stations</span>
Well this will be available to any child scopes except for isolated scopes. If you are planning to do it on the isolated scope (eg:- Isolated scoped directives) you would need to add the reference of Object.keys on the scope, or as you expose a method on the scope which will return the length.
Or better yet , create a format filter to return the keylength and use it everywhere.
app.filter('keylength', function(){
return function(input){
if(!angular.isObject(input)){
throw Error("Usage of non-objects with keylength filter!!")
}
return Object.keys(input).length;
}
});
and do:-
{{ stations | keylength }}
Demo
Use the function to determine the number of object properties:
$scope.keyLength = function (obj) {
return Object.keys(obj).length;
}
and use:
{{ keyLength(myObj) }}
I think filters are the most AngularJS way of handling structures in template code:
angular.module('app.filters').filter('objectKeysLength', [function() {
return function(items) {
return Object.keys(items).length;
};
}]);
angular.module('app.filters').filter('objectKeys', [function() {
return function(item) {
if (!item) return null;
var keys = Object.keys(item);
keys.sort();
return keys;
};
}]);
In case someone searches for angular 2 and higher solution. It now hat keyvalue pipe, which can be used to interate over objects
I could not get any of the other answers to work in AngularJS 1.6. What worked for me
using $window to acccess Object.keys like this $window.Object.keys({ 'a': 1, 'b': 2 })
Here's a solution that worked for me :
export class xyzcomponent{
Key = Object.keys;
}
Now in the component html file, you can use something like that :
<li *ngFor="let i of Key(stations)">.........</li>

How to watch an array for changes in AngularJS

I basically want the equivalent to binding to 'add' and 'remove' events in Backbone's Collections. I see basically no way of doing this in AngularJS, and the current workaround we've settled for is $watch()ing the array's length and manually diffing/recalculating the whole thing. Is this really what the cool kids do?
Edit: Specifically, watching the array's length means I don't easily know which element has been changed, I need to manually "diff".
I think using $watch is a good solution, but $watchCollection can be better for you. $watchCollection doesn't perform deep comparison and just watchs for array modification like insert, delete or sort (not item update).
For exemple, if you want to keep an attribut order synchronize with the array order :
$scope.sortableItems = [
{order: 1, text: 'foo'},
{order: 2, text: 'bar'},
{order: 3, text: 'baz'}
];
$scope.$watchCollection('sortableItems', function(newCol, oldCol, scope) {
for (var index in newCol) {
var item = newCol[index];
item.order = parseInt(index) + 1;
}
});
But for your problem, I do not know if there is a better solution than manually browse the array to identify the change.
The way to watch an array in Angular is $watch(array, function(){} ,true)
I would create child scopes and watch them individually.
here is an example:
$scope.myCollection = [];
var addChild = function()
{
var Child = $scope.$new();
Child.name = 'Your Name here';
Child.$watch('name', function(newValue) {
// .... do something when the attribute 'name' is changed ...
});
Child.$on('$destroy', function() {
//... do something when this child gets destroyed
});
$scope.myCollection.push(Child); // add the child to collection array
};
// Pass the item to this method as parameter,
// do it within an ngRepeat of the collection in your views
$scope.deleteButtonClicked = function(item)
{
var index = $scope.myCollection.indexOf(item); //gets the item index
delete $scope.myCollection[index]; // removes the item on the array
item.$destroy(); // destroys the original items
}
Please tell more about your usecase. One of the solutions of tracking element persistance is using ngRepeat directive with custom directive that listening element's $destroy event:
<div ng-repeat="item in items" on-delete="doSomething(item)">
angular.module("app").directive("onDelete", function() {
return {
link: function (scope, element, attrs) {
element.on("$destroy", function () {
scope.$eval(attrs.onDelete);
});
}
}
});
Perhaps the solution is to create the collection class ( like backbone does ) and you can hook into events pretty easily as well.
The solution I have done here isnt really comprehensive, but should give you a general guidance on how this could be done perhaps.
http://beta.plnkr.co/edit/dGJFDhf9p5KJqeUfcTys?p=preview

Categories