Angular 4 ngFor looping through array of objects with HTML - arrays

I'm using Angular 4 and I want to loop through an array of objects and display the information in Angular Material grids tiles. My html (app.component.html) looks a bit like this
<div class="list" *ngIf="listContacts == true">
<mat-grid-list cols="3" rowHeight="2:1" *ngFor="let i of contacts">
<mat-grid-tile>
<div class="card">
<img src={{contacts[i].image}} alt="picture">
<h2>{{contacts[i].name}}</h2>
</div>
</mat-grid-tile>
</mat-grid-list>
</div>
And contacts is an array of objects inside app.component.ts. There are NINE objects within contacts but for simplicity I've only put two here.
export class AppComponent {
// array of contact objects
contacts = [
{
name: "Robbie Peck",
image: "src/assets/robbie.jpg",
},
{
name: "Jenny Sweets",
image: "src/assets/jenny.jpg",
},
...
]
}
So rather than have nine different 's appear, each with their own information (looping i through contacts), I only have one and the console shows an error where it doesn't recognize contacts[i]. What have I done wrong? How can I get the to have 9 's, each with name and image i within the contacts object in the typescript (app.component.ts) file?

You don't have to pass the index, you can just use the variable i and access i.image and i.name
<mat-grid-list cols="3" rowHeight="2:1" *ngFor="let i of contacts" >
<mat-grid-tile>
<div class="card">
<img src={{i.image}} alt="picture">
<h2>{{i.name}}</h2>
</div>
</mat-grid-tile>

Related

How to iterate over arrays in angular 12?

I am trying to iterate over an object that's in an array of an array, however, I'm not able to display the data in the front-end.
I tried this method:
<div *ngFor="let item of events of event | keyvalue">
{{item.key}}:{{item.value}}
</div>
and this one:
<div *ngFor="let item of events of event | json">
Test: {{item}}
</div>
The structure of the JSON data is given in this format.
[
[
{
"age": "age3",
"gender": "gender3"
},
{
"age": "age3",
"gender": "gender3"
}
]
]
You'll have to iterate through each internal array as well, so you'll need a nested *ngFor. Make sure to reference the correct item array in your nested *ngFor.
<div *ngFor = "let item of events">
<div *ngFor = "let x of item">
{{x.age}}:{{x.gender}}
</div>
</div>
It looks likes a nested array, try using multiple ngFor:
<div *ngFor="let event of events">
<div *ngFor="let item of event">
Test: {{item | json}}
</div>
</div>
Otherwise you could try using Array.prototype.flat to flatten the nested array:
this.flattened = this.events.flat();
// ...
<div *ngFor="let item of flattened">
Test: {{item | json}}
</div>
<ng-container> is fine when you want structural logic without polluting the DOM with useless elements.
<ng-container *ngFor="let subArray of array"> <!-- This does not appear in the DOM -->
<div *ngFor="let item of subArray"> <!-- This appears in the DOM -->
{{item.key}}:{{item.value}}
</div>
</ng-container>

Binding List Item Names to Images

I want to list the names of the items in the module (name). I then want to click the name and have the corresponding image load. The first image should load automatically. Tried following this question to make work, but it's related to thumbnail pics. I believe i'm missing some code in the ng-repeat section. Thx!
How to bind the src of an image to ng-model and extract it in Angular?
HTML
<div ng-controller ="DemoController as main">
<div>
<img ng-src="{{selectedImg.src}}" />
</div>
<ul>
<li ng-repeat="cat in main.cats">
<img ng-src="{{cat.images[0].name}}"
ng-click="selectedImg.src = cat.images[0].name"/>
</li>
</div>
</ul>
</div>
JS
angular.module('myApp',[]);
angular.module('myApp').controller('DemoController',
['$scope',function($scope) {
this.cats = [
{
name: 'Fluffy',
images: 'images/Fluffy.jpeg'
},
{
name: 'Tabby',
images: 'images/tabby.jpeg'
}
];
$scope.selectedImg = {};
}]);
Initially you want to set first image, then do it manually inside controller.
$scope.selectedImg = this.cats.image[0];
and also change ng-click to assign whole object of cat.images[0]
ng-click="selectedImg= cat.images[0]"

How to bind array with ng-bind-html directive in angularJS?

I want to show for every item different description.
This is the controller:
todoApp.controller('todos',function($scope,todoFactory){
todoFactory.getTodos().success(function (data) {
courses = x2js.xml_str2json(data);
$scope.todos = courses.rss.channel.item;
for(var i = 0 ; i < $scope.todos.length ; i++){
item = $scope.todos[i];
console.log(item.description);
$scope.message = item.description;
}
});
this is the html:
<div ng-controller="todos" class="list" style="padding-top: 8%">
<div class="list card" ng-repeat="todo in todos | filter:search" >
<div class="item item-avatar" ng-click="openLink(todo.link)" >
<img src="Bla-Bla-Logo-1.png">
<h2>{{todo.title}}</h2>
<p>{{todo.pubDate | limitTo:25 }}</p>
</div>
<div class="item item-body">
<p ng-bind-html="message"></p>
<p>
1 Like
5 Comments
</p>
</div>
</div>
<!--end list card-->
</div>
<!--end todos-->
Just to explain the code I get xml and convert into json so todos is array of objects.
Message is entering every object and get the description (but in the description has tags so i use ng-bind-html directive to show it properly).
I understand that $scope.message will hold just the last description. How to make it to belong in the ng-repeat so I can get different description for different item?
Thanks.
replace
<p ng-bind-html="message"></p>
with
<p ng-bind-html="todo.description"></p>
please provide the data which is you want to displayed repeatedly.
How data is represented.You are getting last one because it is overriding.
The "ngBind" attribute tells Angular to replace the text content of the specified HTML element with the value of a given expression, and to update the text content when the value of that expression changes.
Typically, you don't use "ngBind" directly, but instead you use the double curly markup like {{ expression }} which is similar but less verbose.

ng-repeat multiple value pairs on single element using angular-isotope

I'm using the angular-isotope plugin to repeat multiple quotes in a masonry grid. My goal is to add multiple unique tags to each quote with each tag having its own unique filter class so that when you click the tag, only tags from that filter appear. Currently, the functionality works fine with just a single tag:
HTML:
<div class="quotes js-masonry" isotope-container="isotope-container">
<div class="quote-col {{item.class}}" ng-repeat="item in quotes" isotope-item="isotope-item">
<div class="quote-single">
<div class="quote-quote">"{{item.quote}}"</div>
<div class="quote-author">
<a class="filter-click" data-filter="{{item.dataFilter}}">- {{item.author}}</a>
</div>
<div class="quote-bottom clearfix">
<span class="quote-share">
<img src="img/icons/facebook.png">
<img src="img/icons/twitter.png">
</span>
<span class="quote-tags" ng-repeat="?">
<a class="filter-click" data-filter="{{item.tagFilter}}">{{item.tag}}</a>
</span>
</div>
</div>
</div>
Angular Controller:
var demo = angular.module('angular-isotope-demo', []);
demo.controller('QuoteController', function QuoteController($scope) {
$scope.quotes = [
{
"quote":"It is better to lead from behind and to put others in front, especially when you celebrate victory when nice things occur. You take the front line when there is danger. Then people will appreciate your leadership.",
"author":"Nelson Mandela",
"tag": [
"Leadership",
"Second Class"
],
"class":"mandela leadership",
"dataFilter": ".mandela",
"tagFilter": [
".leadership",
".second-class"
]
}
]
});
My question is, how do I repeat that item tag (i.e. "Leadership", "Second Class") and attach it to that ".second-class" in the tagFilter so it looks like this?:
I hope this is clear to understand.
You will likely have a nested ng-repeat. So turn your quote.tag element into an array and do something like this:
<div ng-repeat="tag in item.tags">
<a class="filter-click" data-filter="{{tag.filter}}">{{tag.tag}}</a>
{{ $last ? '' : ( $index < item.tags.length-2 ) ? ', ' : '' }}
</div>
If you wish to have different classes for each item, then I would have another element within item.tags, say item.tags.classes and then do this:
<a class="filter-click" ng-class="tag.classes" data-filter="{{tag.filter}}">{{tag.tag}}</a>
Your data will need to be restructured for this to work:
{
"quote":"It is better to lead from behind and to put others in front, especially when you celebrate victory when nice things occur. You take the front line when there is danger. Then people will appreciate your leadership.",
"author":"Nelson Mandela",
"tags": [
{
"tag":"Leadership",
"filter":".leadership",
"classes": "second-class"
}
],
"class":"mandela leadership",
"dataFilter": ".mandela",
}

LocalStorage of contenteditable content (AngularJS)

I have a little AngularJS tool that inserts cards. My goal is to store them locally. I worked that out for the cards array, but not for the card content, that is "contenteditable"
Can u help me with this and give me some best practice solutions?
Here's a Plunker (in JS) for that (the red big button deletes the localStorage. Be sure to have a wide window open): http://plnkr.co/edit/SlbWZ5Bh62MDKWViUsMr
This is my code (with CoffeeScript. For JS see the Plunker above):
This is the markup for the user input
<div class="card card-{{ card.color }}">
<header>
<p class="points" contenteditable></p>
<p class="number" contenteditable>#</p>
<h2 class="customerName" contenteditable>{{ card.customer.name }}</h2>
<h3 class="projectName" contenteditable>Project Name</h3>
</header>
<article>
<h1 class="task" contenteditable>Title</h1>
<p class="story" contenteditable>Description</p>
</article>
<footer>
<div class="divisions">
<p class="division"></p>
<button ng-click="deleteCard()" class="delete">X</button>
</div>
</footer>
</div>
<div class="card card-{{ card.color }} backside">
<article>
<h2 class="requirement">Requirements</h2>
<p contenteditable></p>
</article>
</div>
Here you see my localStorage setup for the cards array that was in my controller above:
Card = (#color, #customer) ->
$scope.cards = []
json = localStorage.getItem "cards"
getCards = JSON.parse(json) if json?
$scope.cards = $scope.cards.concat getCards if getCards?
$scope.reset = ->
localStorage.clear()
$scope.save = ->
cards = []
for card in $scope.cards
cards.push
color: card.color
customer:
name: card.customer.name
localStorage.setItem "cards", JSON.stringify(cards)
$scope.addCardRed = (customer) ->
$scope.cards.push new Card("red", customer)
$scope.save()
How do I store the user inputs in the different fields in localStorage? I heards something about serialization, but I don't know what it means in my case!
Thank you so much in advance!
You can use the ng-model directive with any contenteditable field, just like you would do with an input or textarea. so, instead of trying to use the {{...}} braces to bind your model to your view, you should just use ng-model, and then just treat your editable DOM elements as if they were fields in a form.
For example, in your view :
<header ng-repeat="card in cards">
<!-- These are just sample fields for the card object, but you can modify them -->
<p class="points" contenteditable ng-model="card.points"></p>
<p class="number" contenteditable ng-model="card.number"></p>
<h2 class="customerName" contenteditable ng-model="card.customer.name"></h2>
<h3 class="projectName" contenteditable ng-model="card.projectName"></h3>
</header>
And then in your controller you would attach the model to the $scope as you've already done using $scope.cards = $scope.cards.concat getCards if getCards?. This will two-way bind your cards model to your controller's scope.
And then in your controller, to mirror the model data in LocalStorage, you can do it yourself using something like this:
In your controller:
....
// for example's sake
$scope.cards = [ // the cards array would look something like this
{points: 0, number: 5, customer: {name: 'bob'}, projectName: 'myProj1'},
{points: 1, number: 6, customer: {name: 'joe'}, projectName: 'myProj2'},
{points: 2, number: 7, customer: {name: 'bill'}, projectName: 'myProj3'},
{points: 3, number: 8, customer: {name: 'jerry'}, projectName: 'myProj4'}
];
....
// listen to all changes to the $scope.cards array
$scope.$watch('cards', function(newVal){
var str = angular.toJson(newVal); // serialize the cards array into a string
// NOTE: the 'someKey' string is the key that you'll use later to retrieve it back
window.localStorage['someKey'] = str; // store the serialized string in localStorage
}, true);
....
In the above example, the angular.toJson(newVal) will take the newVal variable (which is just a reference to the "recently updated" cards array), and will serialize it into JSON string (ie angular.toJson just basically wraps the native JSON.stringify() method). In order to put a javascript object into LocalStorage it must be serialized to a string because you can only put primitives as the value in a LocalStorage key.
So the newVal will get put into localStorage looking something like this:
"[{"points":0,"number":5,"customer":{"name":"bob"},"projectName":"myProj1"},{"points":1,"number":6,"customer":{"name":"joe"},"projectName":"myProj2"},{"points":2,"number":7,"customer":{"name":"bill"},"projectName":"myProj3"},{"points":3,"number":8,"customer":{"name":"jerry"},"projectName":"myProj4"}]"
And then later (whenever you need it) you can retrieve the cards array from localStorage again using the following:
var str = window.localStorage['someKey'];
$scope.cards = angular.fromJson(str);
Or you can use a library to do the serialization/saving like this one: https://github.com/grevory/angular-local-storage. I've never used it but it does exactly what you want it to.
Hopefully that helps clarify things a bit.
UPDATE: This is beyond the scope of this question, but since you asked. It sounds like your'e not grasping the concept of the ng-repeat and ng-model directives. These are probably the two most famous (and most widely used) directives in Angular. By combining these two directives (as in the view example above), it will automatically keep your model (ie $scope.cards) and your view (ie <header>) in sync as users edit the data in your view. ng-repeat will "automagically" create a new header element for every card in your cards array (hence the ng-repeat="card in cards"). So as new cards get added or cards are removed from the cards array, Angular will add or remove <header> elements as needed. Then, using the contenteditable and ng-model directives, Angular will bind the content of those editable DOM elements to the values for each card. Typically ng-model is used with form elements (ie inputs/textareas/selects) but it can also be used for any editable field. Think of your editable elements as if they were an input, and then take a good long look at the <input ng-model="" /> example from the angular docs found here. That should help.
Now I know what confused me!
The Two-Way-Binding of my card properties didn't work with contenteditable. I removed each contenteditable attribute and instead of p, h2 and so on I replaced the tags with input tags. That worked fine for me.
Thank you for your patience and your great explanations, #tennisagent! And for the $watch hint! I am really grateful for your help :)
That's my solution for the Controller:
angular.controller 'CustomerController', ($scope) ->
$scope.customers = [
{ name: "name1" }
{ name: "name2" }
{ name: "name3" }
]
$scope.cards = []
Card = (#color, #customer, #points, #number, #projectName, #story) ->
$scope.$watch 'cards', ((newValue) ->
string = angular.toJson(newValue)
localStorage["cards"] = string
), true
json = localStorage["cards"]
parsedCards = angular.fromJson(json) if json?
$scope.cards = $scope.cards.concat parsedCards if parsedCards?
$scope.reset = ->
localStorage.clear()
sessionStorage.clear()
$scope.cards = []
$scope.addCardRed = (customer) ->
$scope.cards.push new Card("red", customer)
That's my solution for the Markup:
<div class="card card-{{ card.color }}">
<header>
<input class="points" contenteditable ng-model="card.points"></input>
<input class="number" placeholder="#" contenteditable ng-model="card.number"></input>
<input class="customerName" contenteditable ng-model="card.customer.name"></input>
<input class="projectName" placeholder="Projekt" contenteditable ng-model="card.projectName"></input>
</header>
<article>
<input class="task" placeholder="Titel" contenteditable ng-model="card.task"></input>
<textarea class="story" placeholder="Story" contenteditable ng-model="card.story"></textarea>
</article>
<footer>
<div class="divisions">
<p class="division"></p>
<button ng-click="deleteCard()" class="delete">X</button>
</div>
</footer>
</div>
<div class="card card-{{ card.color }} backside">
<article>
<h2 class="requirement">Requirements</h2>
<textarea class="requirements" placeholder="Aspects" contenteditable ng-model="card.requirements"></textarea>
</article>
</div>

Resources