How to loop over object properties with ngFor in Angular app - javascript

I can write data by keyValuePipe or {{data | JSON}} but I think that is not good solution for me. How I can write data differently? How I use for this example Object.entries, Object.keys or Object.value?
Code with JSON in Stackblitz: https://stackblitz.com/edit/angular-ivy-jn8snh?file=src/app/app.component.ts

Your object is considered as a dictionary in javascript.
You can loop over properties of your object as follow:
<div *ngFor="let property of data">
<p>{{ property }} - {{ data[property] }}</p>
</div>

You can't to do that out of the box. NgForOf allows iterating over a collection, and by default object does not implement required methods and properties. In theory you could get the keys of the object by writing something like this:
<p *ngFor="let property of Object.keys(object)">
{{property}} {{object[property]}}
</p>
But the way the contents of ngFor are evaluated, the global variable Object is not present in the scope. So you're best off writing a custom pipe that would do the work for you by transforming your object into iterable of your choice that is suitable for your requirements (not idea what that would be since the only information is that "keyValuePipe or {{data | JSON}} is not good solution for me" without any reasoning).

Related

How to access variable defined in *ngFor?

In my first Angular 4 application, I defined a list component :
<edm-document *ngFor="let document of documents" class="column is-one-quarter"></edm-document>
Document is an interface :
export interface Document {
id?: Number,
name: string,
filePath: string
}
All is working as expected, i.e I get my documents list. But now I would like to access to document variable inside my DocumentComponent (the edm-document tag component)
Inside my DocumentComponent template, if I try this it's not working :
<p>{{ document.name }}</p>
I get this error : DocumentComponent.html:1 ERROR TypeError: Cannot read property 'name' of undefined.
I need to enforce document definition like this, and specify document as an input :
<edm-document *ngFor="let document of documents" [document]="document" class="column is-one-quarter"></edm-document>
Now it works but seems a bit redundant to me as I defined a let in loop. Does that mean the variable defined with let is only available in tag where ngFor directive is set ?
Am I missing something?
Thanks,
Nicolas
it works but seems a bit redundant to me as I defined a let in loop
It is not as redundant as it might seem, which becomes obvious when rewriting things a bit:
When not explicitly defining what the component should use (with [document]="document" in your example) then how would your component know that the parent variable is named document? Consider:
<edm-document *ngFor="let d of documents" [document]="d"></edm-document>
One could argue that Angular could introduce some parent variable to access the outer loop variable, but then the component would know how it's going to be used, and could only be used in a loop. Reusable components should not be aware of that.
How would it know that it can use that loop variable directly, and does not need some child property instead? Like:
<edm-document *ngFor="let d of documents" [document]="d.text"></edm-document>
So: your code is just fine.
Initially during DOM rendering the documents object will undefined
Use a typesafe ? operator
<p>{{ document?.name }}</p>
Use a *ngIf with a array length condition as below,
<span *ngIf="documents.length > 0">
<edm-document *ngFor="let document of documents" [document]="document"
class="column is-one-quarter"></edm-document>
</span>
well you can also do something like this
<edm-document *ngFor="let document of documents" class="column is-one-quarter">
<span class="something">{{document.name}}</span>
</edm-document>
and in the edm-document.component.html do something like
<ng-content select=".something"></ng-content>
The value (document) of the loop is valid inside of that block where the *ngFor placed. In your case between: <edm-document>..</edm-document>
In your example:
<edm-document *ngFor="let document of documents"class="column is-one-quarter">
<p>{{ document.name }}</p> <!-- document.name is valid -->
</edm-document>
<p>{{ document.name }}</p> <!-- document.name is invalid -->

Sort an object in angularjs ng-repeat directive

I have an object
Object {1: "CMAR0002", 2: "CMAR0003", 4: "CMAR0001"}
I am using this object in ng-repeat directive but i want to sort it by values. But the problem is that angular orderBy filter requires array of objects. But i have only pretty simple object here ;)
Thanks in advance
By Angular ng-repeat documentation:
It is possible to get ngRepeat to iterate over the properties...
However, there are a limitations compared to array iteration, like "The built-in filters orderBy and filter do not work with objects, and will throw if used with one"
If you are hitting any of these limitations, the recommended workaround is to convert your object into an array that is sorted into the order that you prefer before providing it to ngRepeat. You could do this with a filter such as toArrayFilter or implement a $watch on the object yourself.
Anyway, you can try to implement you own filter for this, but it will be pretty ugly and it will require much effort (probably for nothing).
Hope, it will help you!
Do this:
<li ng-repeat="(key, value) in Object | orderBy: key">
{{ key }} - {{ value }}
</li>
Fiddle: http://jsfiddle.net/oo4cb9xf/1/

Angular 2. Loop of array inside object using ngFor

I have next structure of a component data:
I want to print a list of assignedCards within component's view using *ngFor. I do this possible:
<div *ngFor="#item of mission.assignedCards" class="b-progress-bar__item m-progress-bar__item_completed">
<div class="b-progress-bar__inner">{{item}}</div>
</div>
But it falls with an exception:
If i test mission value, it says me, that mission is an object.
So, i'd like to access assignedCards array inside mission object and make a loop of it.
I guess that the mission object is loaded asynchronously. So it's undefined at a first time and is set later...
So you could use the Elvis operator (mission?.assignedCards):
<div *ngFor="#item of mission?.assignedCards"
class="b-progress-bar__item m-progress-bar__item_completed">
<div class="b-progress-bar__inner">{{item}}</div>
</div>

How to output array of objects in angularjs?

I have an object that looks like this:
$scope.locations [
{Kanaanbadet: {"name":"Kanaanbadet","image":"http://www.stockholm.se/Web/Core/Pages/Special/StreamServiceGuideImage.aspx?path=%2fWeb%2fCore%2fPages%2fSpecial%2fServiceGuideFile.aspx%3ffileid%3d14b313cb2b2f45e380eb88156c95b539","_cached_page_id":"4b71e342c82be9de1c74de3c2f57ea1c4dde8150","long":"17.85448","lat":"59.34966","url":"http://www.stockholm.se/-/Serviceenhetsdetaljer/?enhet=cf0a856830e4422cb55dcd60e8e6b40b"}},
{Johannelundsbadet:{"name":"Johannelundsbadet","image":"http://www.stockholm.se/Web/Core/Pages/Special/StreamServiceGuideImage.aspx?path=%2fWeb%2fCore%2fPages%2fSpecial%2fServiceGuideFile.aspx%3ffileid%3d3e4c2056b5534cfc9b0799e2377b8ce4","_cached_page_id":"18cf34222d74612979afd945a925abc0bf16e44d","long":"17.98914","lat":"59.34098","url":"http://www.stockholm.se/-/Serviceenhetsdetaljer/?enhet=ebf8d49780224e908064551c35dbcca4"}},
...more items
]
I would lie to put out the name in a template within a foreach, and I would like to be able to reference the key.
<a ng-href="#/{{location}}" class="location" ng-repeat="location in locations">{{location}}</a>
I can change the array around to look some other way, but I would like to keep it as an array so I can sort it and select items from the objects keys somehow. How should structure my array?
I think you want to wrap this in two repeats:
<div ng-controller="DemoCtrl">
<div ng-repeat="location in locations">
<a ng-href="#/{{item.url}}" class="location" ng-repeat="item in location">{{item.name}}</a>
</div>
</div>
Maybe check this Plunker
I have to be honest, I really dislike the way you have structured your object. The object should be anonymous, and name should be a property within it. That way, you could use location.name.
All that being said, you can use Object.keys() to get an array of the keys within the object.
<a ng-href="getKey(location)"
class="location"
ng-repeat="location in locations"> {{ getKey(location) }}
</a>
getKey would have to be a function on your scope:
$scope.getKey = function(location){
return Object.keys(location)[0];
}
Example plunk
Note: depending on desired browser support you might be better to iterate over the properties using a for (key in location) loop as some older browsers won't support Object.keys().

Pass parent scope value into ng-repeat loop in Angular

This should be an extremely simple question, but all of the workarounds I've found are complex. I'm looping through an array of objects in using ng-repeat in a template as follows:
<div class="row-fluid" ng-repeat="message in messages.current|filter:'draft'">
{{ message.subject }} ... {{ campaign.name }} ...
</div>
Since the ng-repeat creates a new scope, the 'campaign' object from the controller doesn't seem to be accessable. Is there any way (aside from adding the campaign object to every item in my array) of getting that value?
Thanks in advance.
You can access the parent scope by using $parent
<div class="row-fluid" ng-repeat="message in messages.current|filter:'draft'">
{{ message.subject }} ... {{ $parent.campaign.name }} ...
</div>
This is a way that works that doesn't use $parent. It searches upwards through the nested scopes to find the object you're using, however many scopes it has to go through.
In the scope that contains the list, you can define an object with the list as a property, like this:
$scope.obj = {};
$scope.obj.items = ['item1','item2','item3'];
Then have the ng-repeat look like this:
<div ng-repeat="item in obj.items | filter:'item3' track by $index">
{{obj.items[ obj.items.indexOf(item) ]}}
</div>
(you need to use obj.items[ obj.items.indexOf(item) ] rather than obj.items[ $index ] because $index is the index of the filtered array, not the original)
The reason this works is because while obj doesn't exist in the current scope, as you attempt to access its property, Angular will look above the current scope rather than give you an error (if you just tried {{obj}} it would be undefined, and Angular would be happy with giving you nothing instead of looking through higher scopes). This is a helpful link about nested scopes: http://www.angularjshub.com/examples/basics/nestedcontrollers/
In my case I needed the track by $index, because I had an input with ng-model bound to an item in the array, and whenever the model updated, the input would blur because I think the HTML was being re-rendered. A consequence of using track by $index is that items in the array with identical values will be repeated. If you modify one of those other than the 1st one, weird things will happen. Maybe you can filter for uniqueness to avoid that.
I'm relatively new to AngularJS, so please comment if there is anything big I'm missing. But this works, so I'm using it at least.
Another method might be to pass parent scope as a scope variable to the directive i.e.
<my-directive
md-parent-scope="this"
ng-repeat="item in items"></my-directive>
It's a bit messy, but you have more control over what the parent actually is and can pass anything in.

Categories