AngularJS: Bind to all object properties? - javascript

I have a Model that takes an object that could contain any set of properties.
I am wondering if I can bind to each object property, without knowing the property values ahead of time.
Take this for example
var object1 =
{
Name: '1',
Identifier: '12345',
Password: 'password'
}
var object2 =
{
Name: '2',
Identifier: 'object_two'
}
var objects = [object1,object2];
My objects can have different and unique properties, and I'd like to bind this to a form.
I know I can use a for (var property in object) and iterate through each object -- But can I do the same thing with AngularJS binding?
I wanted to do something along the lines of:
<ul><li ng-repeat="attrib in obj">
{{attrib}}
</li</ul>
Edit: Thanks to the answer below I have gotten a little further. I can't seem to achieve two-way binding with this. This fiddle illustrates what I am trying to do:
http://jsfiddle.net/EpqMc/17/
I essentially want to bind using something like this:
<p ng-repeat="(key,value) in obj">
{{key}} : <input ng-model="obj[key]" />
</p>
This works, except that I can only enter one character at a time -- interesting.
Final Edit: I found an acceptable alternative in case anyone else has this problem. I wasn't able to bind directly to an object without some issues, but i WAS able to bind to an array like the docs state in the answer below.
The following fiddle accomplishes what I need to do, while being only slightly more verbose.
http://jsfiddle.net/8ENnx/9/
function ctrl($scope) {
$scope.objects =
[
[
{Name: 'Name:', Value: 'Joe'},
{Name: 'Identification', Value: '1'},
{Name: 'Password', Value: 'secret'}
],
[
{Name: 'Name:', Value: 'Jane'},
{Name: 'Identification', Value: '2'},
{Name: 'Weather', Value: 'Sunny'}
]
];
// $scope.setSelected = ?????;
}
<div ng-app>
<div ng-controller="ctrl">
Objects
<ul>
<br/>
Listing of properties:
<br/>
<li ng-repeat="obj in objects" class="swatch">
{{obj}}
<p ng-repeat="prop in obj">
{{prop.Name}}: <input ng-model="prop.Value" /></p>
</li>
</ul>
</div>
</div>
This allows me to define an arbitrary set of arguments, but still bind without issues using angular.

No problem:
<li ng-repeat='(key, value) in obj'>
{{key}} : {{value}}
</li>
It's in the docs as well: http://docs.angularjs.org/api/ng.directive:ngRepeat

Related

AngularJs Filter array objects with property file extension [duplicate]

If I have a complex object with objects as property values, how can I filter by one of the nested properties?
Can this be done with the OOB ng-repeat filter?
Data
{
Name: 'John Smith',
Manager: {
id: 123,
Name: 'Bill Lumburg'
}
}
ngRepeat
<li ng-repeat="e in emps | filter:Manager.Name">{{ e.Name }}</li>
You need to pass in the argument to filter by:
<input ng-model="filter.key">
<ul>
<li ng-repeat="e in list | filter: {Manager: {Name: filter.key}}">
{{e.Name}} (Manager: {{e.Manager.Name}})
</li>
</ul>
Example on Plunker
If you are filtering multiple properties then the syntax would be similar to below.
<ul>
<li ng-repeat="item in list | {filter: top_object_property_name: value, top_object_property_with_nested_objects_name: {nested_object_property_name: value}}">
...
</li>
</ul>
eg:
var employees = [name: 'John', roles: [{roleName: 'Manager'},{roleName: 'Supervisor'}]];
<li ng-repeat="staff in employees | {filter: name: 'John', roles: {roleName: 'Manager'}}">
...
</li>
To filter with multiple deep property we need to create custom filter.
What i mean we need to create our own function to filter the data in object and return the required object(filtered object).
For example i need to filter data from below object -
[
{
"document":{
"documentid":"1",
"documenttitle":"test 1",
"documentdescription":"abcdef"
}
},
{
"document":{
"documentid":"2",
"documenttitle":"dfjhkjhf",
"documentdescription":"dfhjshfjdhsj"
}
}
]
In HTML we use ng-repeat to show document list -
<div>
//search input textbox
<input ng-model="searchDocument" placeholder="Search">
</div>
<div ng-repeat="document in documentList | filter: filteredDocument">
//our html code
</div>
In Controller we write filter function to return filtered object by using two properties of object which are "documenttitle" and "documentdescription", code example is as below -
function filterDocuments(document)
{
if($scope.searchDocument)
{
if(document.documentTitle.toLowerCase().indexOf($scope.searchDocument.toLowerCase()) !== -1 || document.document.shortDescription.toLowerCase().indexOf($scope.searchDocument.toLowerCase()) !== -1)
{
//returns filtered object
return document
}
}else {
return document;
}
}
Where $scope.searchDocument is the scope variable which binded to the search textbox (HTML input tag) in which user can input the text to search.
In latest version of angularjs nested obj filter implemented by default.can use filter normally.
It for angular 1 only

Accessing objects in Angularjs

Is it possible to get age object's value with the name object on following mentioned AngularJS expression:
{{{name: 'Jhon', age: '15' }.name}}
You can do something like below:
<div>
Name = {{ {name: 'Jhon', age: '15' }.name | json }}
</div>
<div>
Age = {{ {name: 'Jhon', age: '15' }.age | json }}
</div>
Have a look at the demo.
Yes, but with a slight modification Eg.
({key : 1}).key
But you cannot use this object elsewhere. So there is no point in creating such objects unless you are referring via a variable.

Fitting multiple dependent datasets into AngualrJs ngRepeat

I am looking for a little bit of help with logically fitting two objects with common reference into AngularJs ngRepeat.
Example objects (these get called from a service):
$scope.objArr1 = [
{ id: 1, Name: 'Name 1', Value: 'Value 1', },
{ id: 2, Name: 'Name 3', Value: 'Value 2', },
{ id: 3, Name: 'Name 3', Value: 'Value 3', },
];
$scope.objArr2 = [
{ id: 1, Name: 'Name 1', Value: 'Value 1', ObjArr1: { id: 1, Name: 'Name 1', Value: 'Value 1', }, },
{ id: 2, Name: 'Name 1', Value: 'Value 1', ObjArr1: { id: 1, Name: 'Name 1', Value: 'Value 1', }, },
{ id: 3, Name: 'Name 1', Value: 'Value 1', ObjArr1: { id: 3, Name: 'Name 3', Value: 'Value 3', }, },
];
Something along those lines. Basically if you can think of it this way; first array objects form buckets while second array objects form items that fit into corresponding bucket.
First approach
HTML:
<ul>
<li data-ng-repeat="item in objArr1 | filter : someFilter">{{item.Name}}
<ul>
<!-- how to filter objArr2 items based on objArr1 property ? -->
<li data-ng-repeat="item2 in objArr2 | filter : someOtherFilter">{{item2.Name}}</li>
</ul>
</li>
</ul>
In simple terms I was trying to filter $scope.objArr2 items that correspond to the current repeater item in the inner repeater. I tried various things with someOtherFilter but I was unable to reference the item from outer repeater.
Problem
I couldn't figure out how get this filtering bit to work.
Second approach
When all else failed I decided to combine the data structures into one like so:
// deep copy to avoid dependency
angular.copy($scope.objArr1, $scope.objArr3);
// loop over objArr3 and add empty array objArr2
// which we will populate a bit later
angular.forEach($scope.objArr3, function (val, key) {
$scope.objArr3[key]["objArr2"] = [];
});
Then I setup a $watch-er`to monitor both objArr1 and objArr2 because I don't know when these will return.
$scope.$watchGroup(['objArr1', 'objArr2'], function (newVals, oldVals) {
// check to make sure there is stuff to loop over
// i am wrongly assuming there will be items in both objArr1 and objArr2
// i'll worry about what to do when there is no data a bit later
if(newVals[0].length > 0 && newVals[1].length > 0) {
angular.forEach($scope.objArr1, function (val1, key1) {
angular.forEach($scope.objArr2, function (val2, key2) {
if (val1.Id === val2.objArr1.Id) {
$scope.objArr3[key1].objArr2.push(val2);
}
});
});
}
});
HTML:
<ul>
<li data-ng-repeat="item in objArr1 | filter : someFilter">{{item.Name}}
<ul>
<li data-ng-repeat="item2 in item.objArr2">{{item2.Name}}</li>
</ul>
</li>
</ul>
Problem
While this has worked just fine on the surface I get a lovely Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting! in the console.
I am a bit puzzled what would cause for $digest to fire so many times.
However, by commenting update line $scope.objArr3[key1].objArr2.push(val2); of my watcher the error goes away. But then I don't understand how this would result in extra digest iterations.
Halp
In the end either of the approach that I came up with has some problem. While second approach actually does its job and populates my repeater correctly but there is that nasty error in the console.
Anyone with a bit more experience in this field please help.
Update
Some of the silly things I tried with someOtheFilter are:
data-ng-repeat="item2 in objArr2 | filter : someOtherFilter"
$scope.someOtherFilter = function(item){
// item is always the current inner repeaters item2 object
// that just the way angular filter works
return item.objArr2 === $scope.objArr1.Id; // this is silly but idea is there
};
data-ng-repeat="item2 in objArr2 | filter : someOtherFilter(item)"
$scope.someOtherFilter = function(item){
// if memory serves me right
// in this case item is always repeaters current item2 object
// with no ability to reference outer repeaters current item object
}
data-ng-repeat="item2 in objArr2 | filter : someOtherFilter(item, item2)"
$scope.someOtherFilter = function(item, item2) {
// if memory serves me right
// in this case item was always inner repeaters current item2 object
// and item2 is always undefined
// again with no ability to reference outer repeaters current item
}
At this point I gave up on first approach. But thinking about it now I might have been able to utilise $index (if inner repeater somehow or other didn't overwrite outer repeaters $index reference) to get index value of the outer repeater and try to get at $scope.objArr1[index].
No matter which scenario would have worked for someOtherFilter inner working only need to compare inner object objArr1.Id to outer objects Id.
UPDATE (learn from my mistakes)
OK, after confirming the answer as working I still had the same issue in my production example Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!.
After cooling down for a few days I decided to revisit this problem and this is what I found.
<div class="block" data-ng-repeat="team in Teams | filter : validateAgeCategory">
<div data-ng-style="getHeaderStyle()">
<span>{{team.Name}}</span>
<!-- bunch of things removed for brevity -->
</div>
<ul data-ng-style="getListStyle()">
<li data-ng-repeat="players in Players | filter : { Team: { Id: team.Id, }, }">
<a data-ng-style="getListItemStyle()" data-ng-href="#/players/{{player.Id}}">{{player.Name}}</a>
</li>
</ul>
</div>
I adapted my Team/Player example for easier understanding. Regardless notice that in production I use a few ng-style calls to retrieve CSS.
Reason why I am doing so is because IE has a tendency to remove {{}} definitions from inline style="color: {{color}};" definition during document load. It's IE bug so to speak.
Moving on, what I found is that the innermost ng-style was causing the error with $digest. By removing data-ng-style="getListItemStyle()" everything is happy. Everything bu me of course.
Looking at this as an overhead it would be better to create CSS classes and instead apply classes based on some indexing to style my HTML.
There you have it.
OK, I'll try my best to help.
I think the problem with your second approach is somehow related to this question. Read the comments there. It might be related to the list being changed by the filter.
As for your first approach, I'm still not sure what you were trying to do, but I've created this example to show you that you can filter inside nested ngRepeats.
BTW, If you need to access outer $index inside an inner ngRepeat, you can use ngInit.

AngularJS dropdown not showing selected value

Am facing problem in displaying selected value in angular dropdown.
it works when i give like this
$scope.selectedItem = $scope.items[1];
not working, if i give directly that value
$scope.selectedItem = { name: 'two', age: 27 };
HTML:
<html ng-app="app">
<body>
<div ng-controller="Test">
<select ng-model="selectedItem" ng-options="item.name for item in items">
</select>
</div>
</body>
</html>
JS:
var app = angular.module('app',[]);
app.controller('Test',function($scope){
$scope.items = [{name: 'one', age: 30 },{ name: 'two', age: 27 },{ name: 'three', age: 50 }];
$scope.selectedItem = $scope.items[1];
});
CODEPEN:
http://codepen.io/anon/pen/zxXpmR
SOLUTION:
Thank you samir-das. I fixed as per your suggestion.
var choosen_value = { name: 'two', age: 27 };
angular.forEach($scope.items, function(item){
if(angular.equals(choosen_value, item)){
$scope.selectedItem = item;
}
});
As explained in the other answers, while the two objects may have the same properties and values, they are two different objects so angular doesn't consider them to be equal.
You can however use the track by expression in ng-options to specify a property which will decide equality:
ng-options="item.name for item in items track by item.name"
http://codepen.io/anon/pen/WbWMrp
Well, because
$scope.items[1] and { name: 'two', age: 27 } is totally different thing.
{ name: 'two', age: 27 } is a totally different object whereas $scope.items[1] is part of the object $scope.items
When you put something in the template using {{}}, angular add it in its watcher list.
SO when angular put it in the watch list, it was an object (i.e. { name: 'two', age: 27 } ) that is different than $scope.items.
selectedItem is attached with the object that you set in the controller. In summary while dirty checking, angular will checks selectedItem against { name: 'two', age: 27 } NOT against $scope.items
Hope you understand what I mean
This is not an Angular feature/issue, it is a consequence of how object equality works in Javascript. This article does a fairly good job in explaining what is going on in a pretty concise way and gives some examples. Check out the source of lodash's isEqual method (it will take you to the definition of baseIsEqualDeep eventually) to see how what you are trying to achieve can be done in pure JS.
In any case, I do not think there is an easy way to achieve this in Angular, you would have to re-write the way ng-options works and you probably do not want to go there...
In angular, Arrays and objects are passed by reference while strings, numbers and booleans are passed by value. So, angular interprets $scope.items[1] and { name: 'two', age: 27 } as two different objects. That's why your binding fails when you do $scope.selectedItem = { name: 'two', age: 27 }; directly and find it in '$scope.items'.

Reference a string filter from object value

I am trying to apply a filter to the output of an angular ng-repeat. Here's an example of the data:
{name: "followers", label: "Followers", filter:"number:0"},
{name: "following", label: "Following",filter:"number:0"},
{name: "mediaCount", label: "Media Count",filter:"number:0"}
The display of things looks like this:
<span ng-repeat="item in rows">
<div class="col col-gh-4"><label>{{item.label}}</label></div>
<div class="col col-gh-2" ng-bind="item.value ? (item.value|{{item.filter}}) : '-'"></div>
I am trying to apply the filter in this case "number:0" as defined in the data object at display-time using something like: item.value|{{item.filter}} or item.value|item.filter
Both of those attempts don't work, any advice on how to reference the filter from the object value? Is it possible?
Thanks for any suggestions!

Categories