Alternative to breaking a loop in Angular while iterating through an array? - arrays

I have read online that it is impossible to break *ngFor in Angular but was wondering if anyone has an alternative to this? I have been stuck on this for days on end.
I would like to loop a list all animal names for the user and when the user clicks a specific animal name, info appears below the button they have just clicked.
Please see the example below:
<div ="container" *ngFor="let animal of (this.animals || [])">
<h5 (click)="getAnimalInfo(animal.animalName)">{{animal.animalName}}</h5>
<ng-container *ngIf="getAnimalInfo(animal.animalName)">
<div class="container" *ngFor="let animalInfo of (this.animalInfo || []);">
<li {{animalInfo.title}} </li><br>
<li {{animalInfo.paragraph}} </li><br>
</div>
</ng-container>
</div>
Currently, when I click an animal name item my original list, it will display the details below every single animal name on the list instead of just the one I have clicked on.
How can I fix this so that the loop knows to break when I select one animal name?
N.B I am aware that if i take my second for loop out of the original for loop, this can work, however I want the data to populate directly under the original button and be toggle-able rather than having the info appear at the very bottom of the full list.

I don't know how to break loop in *ngFor loop but for you requirement i have given the solution below.
First of all i have defined dummy animals array and animalInfo array in ts file. You can replace with your original array.
animals = [{ animalName: 'dog' }, { animalName: 'cat' }];
animalInfo = [{ paragraph: 'dog paragraph', title: 'dog' }, { paragraph: 'cat paragraph', title: 'cat' }];
then i have declared selectedAnimalInfo which we can use to store the click animal info.
selectedAnimalInfo = {};
I am expecting animalName parameter in animal array is same as title parameter of animalInfo arrar.
After that your getAnimalInfo method will be look like below.
getAnimalInfo(animalName: string) {
this.selectedAnimalInfo = {
[animalName]: this.animalInfo.find((animal) => animal.title === animalName)
};
}
And at the end html code
<div class="container" *ngFor="let animal of (this.animals || [])">
<h5 (click)="getAnimalInfo(animal.animalName)">{{animal.animalName}}</h5>
<ng-container *ngIf="selectedAnimalInfo[animal.animalName]">
<div class="container">
<li> {{selectedAnimalInfo[animal.animalName].title}} </li><br>
<li> {{selectedAnimalInfo[animal.animalName].paragraph}} </li><br>
</div>
</ng-container>
</div>
Hope it will full fill you requirement.

Related

Displaying all but [0] element of map key arrays

The premise of this question is that in the following TS block, I am creating an array that is made from the given map's keys and console logging to ensure that the arrays are created as needed.
public certKeys: String[];
public certMap: Map<String, DataObject[]> = new Map();
public allData: DataObject[];
#Input()
set data(data: DataObject[]) {
if (!data) { return; }
new Set(data.map(i => i.certTypeDescription)).forEach(i => {
this.certMap.set(i, []);
});
data.forEach(i => {
this.certMap.get(i.certTypeDescription).push(i);
});
this.certKeys = Array.from(this.certMap.keys());
this.allData = data;
console.log(this.certMap);
}
Now when this translates to the HTML portion of this, I am wanting to display the most recent record (or the [0] element) of each key array. This is already being accomplished. However, the other portion is that in the accordion drop down, I need to retrieve the rest of the elements save for the [0] element. below you will see what I have so far:
<app-basic-card myTitle="Data">
<i cardIcon class="uxd uxd-mode-edit uxd-lg uxd-pointer text-primary" (click)="openEditDialog()"></i>
<div cardBody class="accordion" *ngIf="allData; else loading">
<p *ngIf="allData?.length === 0">
No allData found...
</p>
<mat-accordion *ngIf="allData?.length>0">
<mat-expansion-panel *ngFor="let cert of certKeys">
<mat-expansion-panel-header>
<mat-panel-title class="list-group list-group-flush">
<ng-container>
<div>
<div class="w-50 float-left">{{cert}}</div>
<div class="w-50 float-right">
<i class="uxd uxd-lg" [ngClass]="getCertIcon(certMap.get(cert)[0]?.certificationResult)"></i>
{{getDateTaken(certMap.get(cert)[0].certificationDate)}}
</div>
</div>
</ng-container>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-container>
<div *ngFor = "let certKeys of allData">
<div class="w-50 float-left">{{cert}}</div>
<div class="w-50 float-right">
<i class="uxd uxd-lg" [ngClass]="getCertIcon(certMap.get(cert).certificationResult)"></i>
{{getDateTaken(certMap.get(cert).certDate)}}
</div>
</div>
</ng-container>
</mat-expansion-panel>
</mat-accordion>
</div>
<ng-template cardLoading #loading class="text-center">
<mat-spinner class="loading-spinner" style="margin:0 auto;" diameter="50"></mat-spinner>
</ng-template>
</app-basic-card>
My question is how do I accomplish retrieving every element but the [0] element of each key array? There is something that I very obviously am missing. I would appreciate any answers that are given and resources that may point me in the right direction. I thank you all for your time.
I don't know Angular tbh, but if you can modify the array you could use slice method, which returns a shallow copy of the original array, so the original will stay untouched.
Can you change this line:
<div *ngFor = "let certKeys of allData">
Into this:
<div *ngFor = "let certKeys of allData.slice(1)">
?
Working snippet of the slice() function.
const items = [1, 2, 3, 4];
const itemsWithoutTheFirstItem = items.slice(1);
// Target array contains all but first item
console.log(itemsWithoutTheFirstItem);
// Original array stays the same
console.log(items);
I think of two options right now. The first one is using *ngIf or a simple Pipe.
First option:
<mat-expansion-panel *ngFor="let cert of certKeys; let index = index">
<mat-expansion-panel-header *ngIf="index > 0">
...
</mat-expansion-panel>
Second option:
Create a Angular Pipe, which returns a new array except the first entry:
#Pipe({name: 'ignoreFirst'})
export class IgnoreFirstEntryPipe implements PipeTransform {
transform(arr: any[]) {
// Returns a new array without the original first entry
return arr.slice(1);
}
}
And in your html:
<mat-expansion-panel *ngFor="let cert of certKeys | ignoreFirst">
<mat-expansion-panel-header>
...
</mat-expansion-panel>

Angular 4 ngFor looping through array of objects with HTML

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>

Dynamic Filter value in ng-repeat

is it possible to use a dynamic value for a filter variable in this manner:
<div ng-repeat="playlist in playlists" >
<a class="view-detail" ng-href="#/playlist/{{playlist.id}}">{{playlist.playlistName}}</a>
<a ng-href="#/playlist/{{playlist.id}}">check out this playlist >></a>
<span ng-repeat="genre in genres | filter:{name:'playlist.playlistGenre'}">
<img ng-src="{{genre.image}}">
</span>
</div>
As such its a repeat within a repeat - its not showing up like this, however if i hardcode in one of the values that the filter variable would return, then it works -
Or should i rethink my approach -
many thanks
filter:{name:'playlist.playlistGenre'}
That looks for genres whose name is the string 'playlist.playlistGenre'. What you want, if I understand correctly, is to display the genres whose name has the same value as the field playlistGenre of the playlist.
So you want
filter:{name:playlist.playlistGenre}
That said, this is quite inefficient. I guess there is a single genre that has a given name. So, instead of looping through all genres for each playlist, you'd better set the genre directly in the playlist, or at least use a hash that contains the genre for every name:
$scope.genresByName = {};
genres.forEach(function(genre) {
$scope.genresByName[genre.name] = genre;
});
and then
<div ng-repeat="playlist in playlists" >
<a class="view-detail" ng-href="#/playlist/{{playlist.id}}">{{playlist.playlistName}}</a>
<a ng-href="#/playlist/{{playlist.id}}">check out this playlist >></a>
<span><img ng-src="{{genresByName[playlist.playlistGenre].image}}"></span>
</div>

Getting a matching element from a different array

I have an array called groupUsers and another array within an array called tasks.users which look like this:
this.groupUsers = [
{$id: "simpleLogin:1", name: "bob"},
{$id: "simpleLogin:2", name: "joe"}];
this.tasks = [
{title: "do something", users:["simpleLogin:1","simpleLogin:2"]},
{title: "do something else", users:["simpleLogin:2"]}];
What I am trying to do is to get the user's name from groupUsers to display when I show the tasks rather than their id, but I am unsure in how to go about getting it. I assume it involves a filter?
With the following code I can display the tasks and the user id's assigned to each task, just not the names.
<div class="taskContainer" ng-repeat="task in todo.tasks | orderBy:'-when'">
<div><span ng-repeat="users in task.users">{{users}} </span></div>
<span> {{task.title}} </span>
</div>
You can expand to this function to fit your needs, right now it will return one name of the passed in id, $filter is an angular-service, remember to include it in your controller
$scope.getName = function(id){
return $filter('filter')(groupUsers, { name: id})[0].name;
};
here is your html
<div class="taskContainer" ng-repeat="task in todo.tasks | orderBy:'-when'">
<div><span ng-repeat="users in task.users">{{getName(users[0])}} </span></div>
<span> {{task.title}} </span>
</div>

How do I only add complete items to this list

I'm using an input field to accept comma-separated values then dynamically generating list items for each comma-separated value. However, I don't want items added to the list until they have a comma after them. For example, if I enter "Dog, Cat, Fish" in the input field, I don't want the following as I'm typing "Cat"
Dog
Ca
I want "Cat" to be added only once I've put a comma after it to indicate it is a complete entry.
Here's the code I'm using to achieve this.
<input ng-list ng-model="labels" placeholder="Enter labels">
<div>
<ul>
<li ng-repeat="label in labels track by $index">
{{ label }}
</li>
</ul>
</div>
Link to running example
What's the angular way to achieve this?
One way to do this would be to use custom filters to filter the ng-repeat items instead of relying on the ng-list directive.
See the associated PLUNKER of the code below.
HTML
<input ng-model="labels" placeholder="Enter labels" />
<div>
<ul>
<li ng-repeat="label in labels | completeList">
{{ label }}
</li>
</ul>
</div>
JAVASCRIPT
filter('completeList', function() {
return function(items) {
var list = [];
if(items) {
list = items.split(',');
var last = list[list.length - 1];
if(items.charAt(items.length - 1) != ',' || last.length === 0)
list.pop();
}
return list;
};
});

Resources