How to iterate array using ngFor loop in angular - arrays

'arr: { a: string[];b: string[];c: {id: number; name: string; }[]; } '
'''arr= {
a: ['rose', 'kelly', '35'],
b: ['marry', 'hadden', '40'],
c:[
{ id: 1, name: "Mark" },
{ id: 2, name: "John" },
{ id: 3, name: "Franc" },
{ id: 4, name: "Andrew " }
]
}
How to iterate above array using *ngFor loop in Angular'''

*ngFor directive expects the Array object to loop over. That's why you're receiving an error.
You can use keyvalue pair pipe to loop over this object, basically that will convert an object to an array, which would have key and value in separate object properties.
Since strictTemplate checking, typescript is throwing an error. To fix/suppress that error you can use define type of arr: {[key: string]: any}.
TS
arr: {[key: string]: any} = {
a: ...,
b: ...,
}
HTML
<div *ngFor="let item of arr | keyvalue">
<b>{{ item.key }}</b>
<div *ngFor="let val of item.value">
{{ val.name ?? val }}
</div>
</div>
Stackblitz

Related

Key value from variable and string in Angular Template

is there a way to compose the key value of an object in typescript from strings? Here's an example:
object[index].key=value
should be,
object[index].["String".array[i]]=value
The following does not work:
{{ object[index].{{"string".concat(Variable[index])}} }} <-- the rendering is perfectly fine but the execution is missing
I'm trying to read an object in two "ngFor" loops, with the first loop acting as a counter for an array whose element acts as a key for the object in the second loop.
thx
EDIT:
tage=['Montag','Dienstag','Mittwoch']
objectArray: {
id:Number;
name:String;
Montag:String;
Dienstag:String;
Mittwoch:String;
}[]=[
{id:0,name:'Klaus',Montag:'arbeiten',Dienstag:'rumgammeln',Mittwoch:'Frei'},
{id:1,name:'Dieter',Montag:'frei',Dienstag:'arbeiten',Mittwoch:'rumgammeln'},
{id:2,name:'Peter',Montag:'rumgammeln',Dienstag:'frei',Mittwoch:'arbeiten'},
]
Template
1:{{objectArray[0].Montag}}<br>
2:{{objectArray[0]['Montag']}}<br>
<br>
<ng-container *ngFor="let tag of tage; let i=index">
{{i}}:{{objectArray[0][tage[i]]}}<br> <------- NOT WORKING
</ng-container>
Error Message
(property) AppComponent.tage: string[]
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ id: Number; name: String; Montag: String; Dienstag: String; Mittwoch: String; }'.
No index signature with a parameter of type 'string' was found on type '{ id: Number; name: String; Montag: String; Dienstag: String; Mittwoch: String; }'.ngtsc(7053)
app.component.ts(8, 53): Error occurs in the template of component AppComponent.
I think you are looking for the keyvalue pipe which will let you access object's key/value in template. https://angular.io/api/common/KeyValuePipe
testObject: { [key: number]: string } =
{
1: 'Object Value 1',
2: 'Object Value 2',
3: 'Object Value 3'
};
testMap = new Map([
[2, 'Map Value 2'],
[null, 'Map Value 3'],
[1, 'Map Value 1'],
]);
arrTest = ["Array Item 1",
"Array Item 2",
"Array Item 3"]
<p>Object & KeyValue Pipe</p>
<div *ngFor="let item of testObject | keyvalue">
Key: <b>{{item.key}}</b> and Value: <b>{{item.value}}</b>
</div>
<p>Maps & KeyValue Pipe</p>
<div *ngFor="let item of testMap | keyvalue">
Key: <b>{{item.key}}</b> and Value: <b>{{item.value}}</b>
</div>
<p>Arrays & KeyValue Pipe</p>
<div *ngFor="let item of arrTest | keyvalue:descOrder">
Key: <b>{{item.key}}</b> and Value: <b>{{item.value}}</b>
</div>
Example: https://stackblitz.com/edit/angular-key-value-pipe-demo-kphrro?file=src%2Fapp%2Fapp.component.html

Angular Use Function in Container Context

I want to set up a checkbox structure and I want to handle it dynamically. I have an object response returned from my service. However, when I use this function in context, I get a string return and I cannot use ngFor.
form.demo.component.ts
getElementChoicesByNKey(nkey:string):choiceModel[]{
var element = this.findInComponentsByNKey(nkey);
var res = element.Choices;
return res;
}
this function gives the correct return.
form.demo.component.html
...
<ng-container *ngIf="item.Type == 'Choice'">
<ng-container *ngTemplateOutlet="choiceTheme; context:{ Id: item.Id, Label: item.Label, Choices: getElementChoicesByNKey(item.Id) }"></ng-container>
</ng-container>
...
<ng-template #choiceTheme let-Id="Id" let-Label="Label" let-Choices="Choices">
<nz-form-item>
<nz-form-label nzFor="Id">{{Label}}</nz-form-label>
<nz-form-control [formControlName]="Id" nzErrorTip="{{ 'form.requiredText' | translate }}">
<p>{{Choices.length}}</p>
<div *ngFor="let item of Choices">
<p>test</p>
</div>
</nz-form-control>
</nz-form-item>
</ng-template>
...
here Choices appears as string and won't let me use ngFor. I am getting an error as below.
Cannot find a differ supporting object '[
{ label: 'Apple', value: 'Apple', checked: true },
{ label: 'Pear', value: 'Pear', checked: false },
{ label: 'Orange', value: 'Orange', checked: false } ]' of type 'string'. NgFor only supports binding to Iterables such as Arrays.
What is the point I missed? Thank you for your help.
Use
*ngFor="let item of getChoices(Choices)" in template
and in component.ts
getChoices(choiceStr) {
return JSON.parse(choiceStr);
}
Since you are getting Choices as a string, parse the string to an array inorder for ngFor to work

How to find objects with the same property values in an Array of Objects in typescript?

I have an array of objects
var myArray = [
{id: 1, name: 'Foo Bar', email: 'foo#bar.com'},
{id: 2, name: 'Bar Foo', email: 'bar#foo.com'},
{id: 3, name: 'Joe Ocean', email: 'joe#ocean.com'},
{id: 3, name: 'Jenny Block', email: 'foo#bar.com'},
];
I am expecting the following output:
commonIdsObjects = [
{id: 3, name: 'Joe Ocean', email: 'joe#ocean.com'},
{id: 3, name: 'Jenny Block', email: 'foo#bar.com'},
]
I assume that you want the output to be a single array containing all the duplicate entries, even if some of those entries have different ids. For example, if you had added {id: 2, name: 'Fishy Joe', email: 'com#foo.bar'} to myArray, the resulting commonIdsObjects would be an array of four items: two for id: 2 and two for id: 3. If this is not what you want then you should take care to specify exactly the expected behavior.
Anyway, assuming you have a type corresponding to the elements of myArray, like this:
type Elem = typeof myArray[number];
And assuming your target runtime has access to the Object.values() and Array.prototype.flat() methods, then you can write
const commonIdsObjects = Object.values(
myArray.reduce<{ [k: number]: Elem[] }>(
(a, v) => ((a[v.id] || (a[v.id] = [])).push(v), a), {}
)
).filter(c => c.length > 1).flat(1);
What we're doing is using myArray.reduce() to build an object whose keys correspond to your elements' id values, and whose values are arrays of elements with those id. We convert this object into an array of arrays of elements, keep only those whose lengths are more than one (i.e., any id with more than one element corresponding to it), and flatten into a single array.
This will produce the desired result:
console.log(JSON.stringify(commonIdsObjects));
// [{"id":3,"name":"Joe Ocean","email":"joe#ocean.com"},
// {"id":3,"name":"Jenny Block","email":"foo#bar.com"}]
If you don't have access to Object.values() and [].flat() you can use Object.keys() and [].reduce() instead:
type ElemsById = { [k: string]: Elem[] }
const commonIdsObjects2 = ((o: ElemsById) => Object.keys(o).map(k => o[k]))(
myArray.reduce<ElemsById>(
(a, v) => ((a[v.id] || (a[v.id] = [])).push(v), a), {}))
.filter(c => c.length > 1).reduce<Elem[]>((a, v) => (a.push(...v), a), []);
console.log(JSON.stringify(commonIdsObjects2)); // same
which is essentially the same algorithm. Or you could do this algorithm the purely-imperative-programming way with various for loops:
const elemsById: ElemsById = {};
for (let v of myArray) {
if (!elemsById[v.id]) {
elemsById[v.id] = []
}
elemsById[v.id].push(v);
}
const commonIdsObjects3: Elem[] = []
for (let k in elemsById) {
if (elemsById[k].length <= 1) {
continue;
}
for (let v of elemsById[k]) {
commonIdsObjects3.push(v);
}
}
console.log(JSON.stringify(commonIdsObjects3)); // same
Okay, hope that helps; good luck!
Playground link to code
var myArray = [
{ id: 1, name: "Foo Bar", email: "foo#bar.com" },
{ id: 2, name: "Bar Foo", email: "bar#foo.com" },
{ id: 3, name: "Joe Ocean", email: "joe#ocean.com" },
{ id: 3, name: "Jenny Block", email: "foo#bar.com" }];
const commonIdsObjects = myArray.filter(x => x.id === 3);
console.log(commonIdsObjects);

how to do ngFor inside ngFor dynamically in angular 8?

Hi what i trying to achieve is ngFor with dynamic value inside ngFor, is this possible? i try using ngModel inside it too and it didn't work out. Here is what i do :
inside my home.component.ts :
import { Component, OnInit } from '#angular/core';
import {CdkDragDrop, moveItemInArray} from '#angular/cdk/drag-drop';
export interface Condition {
value: string;
viewValue: string;
}
export interface ListProduk {
value: string;
viewValue: string;
}
export interface DragBox {
value: string;
viewValue: string;
}
export interface ListModel {
value: string;
viewValue: string;
single_item: string;
}
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
conditions: Condition[] = [
{ value: 'if', viewValue: 'IF' },
{ value: 'else', viewValue: 'ELSE' },
{ value: 'then', viewValue: 'THEN' },
{ value: 'if else', viewValue: 'IF ELSE' },
{ value: 'or', viewValue: 'OR' },
{ value: 'and', viewValue: 'AND' }
];
listProduks: ListProduk[] = [
{ value: 'mcm-508', viewValue: 'MCM-508' },
{ value: 'bl-100 pl', viewValue: 'BL-100 PL' },
{ value: 'bl-150 bl', viewValue: 'BL-150 BR' },
{ value: 'bl-302gs', viewValue: 'BL-302GS' },
{ value: 'bl-52gl', viewValue: 'BL-52GL' }
];
listModels: ListModel[] = [
{ value: 'conditions', viewValue: 'Condition', single_item:'condition' },
{ value: 'listProduks', viewValue: 'List Produk', single_item:'listProduk' },
]
constructor() { }
ngOnInit() {
}
drop(event: CdkDragDrop<string[]>) {
moveItemInArray(this.listModels, event.previousIndex, event.currentIndex);
}
}
and then here is my home.component.html :
<p>home works!</p>
<div cdkDropList cdkDropListOrientation="horizontal" class="example-list" (cdkDropListDropped)="drop($event)">
<div class="example-box" *ngFor="let listModel of listModels" cdkDrag>
<mat-form-field>
<mat-label>Pick {{listModel.value}} :</mat-label>
<mat-select>
<mat-option *ngFor="let {{listModel.single_item}} of {{listModel.value}}" [value]="{{listModel.single_item}}.value">
test
</mat-option>
</mat-select>
</mat-form-field>
<div>
<i class="material-icons">
arrow_right_alt
</i>
</div>
</div>
</div>
i try to do loop the mat-select dynamically, since i want it loop an array that have different name, i need value in listModel array to print to *ngFor inside mat-select. Which is this line :
<mat-option *ngFor="let {{listModel.single_item}} of {{listModel.value}}" [value]="{{listModel.single_item}}.value">
test
</mat-option>
how to do this properly?
UPDATED QUESTION After update my code with Ahmed comment, which is my Html is looked like this :
<p>home works!</p>
<div cdkDropList cdkDropListOrientation="horizontal" class="example-list" (cdkDropListDropped)="drop($event)">
<div class="example-box" *ngFor="let listModel of listModels" cdkDrag>
<mat-form-field>
<mat-label>Pick {{listModel.value}} :</mat-label>
<mat-select>
<mat-option *ngFor="let a of listModel.value" [value]="a.value">
{{a.viewValue}}
</mat-option>
</mat-select>
</mat-form-field>
<div>
<i class="material-icons">
arrow_right_alt
</i>
</div>
</div>
</div>
and this give me an error like this :
ERROR Error: Cannot find a differ supporting object 'conditions' of
type 'string'. NgFor only supports binding to Iterables such as
Arrays.
what did i missed?
You can display this using a function, which would return the correct array. We are calling this in the template. BE CAREFUL, I would never recommend calling a function in template, if it is just all possible. This can seriously hurt performance in an app. But if you don't have much content on this page, it is pretty safe to use. So I would suggest the following:
<div *ngFor="let value of getList(listModel.value)">
and the function would return the correct array:
getList(value) {
return this[value]
}
You could also make a slight change to the model and pass an optional parameter with the array with the correct array to the object itself. You can do this in OnInit:
ngOnInit() {
this.listModels.forEach(x => {
x.customArray = this[x.value]
})
}
and use it like normal iteration in *ngFor:
<div *ngFor="let value of listModel.customArray">
Here's a STACKBLITZ with both options

Update only selected array item in *ngFor array Angular2

I have an *ngFor loop to display libraries. When you click on a library, categories of that library appear beneath in a tree-like structure. The categories are also being displayed with an *ngFor loop. When I have one library expanded and click on another one, the categories in BOTH libraries are updating to the categories in the library that was just clicked. The functionality I am looking for is to only update the selected library categories and leave the others alone. There is a post here that seems to be close to my problem but I couldn't get it to work.
Using *ngFor to loop through an array and *ngIf to select which elements from the array to list
Here is my code:
library.component.html
<div *ngFor="let messageLibrary of onHoldMessageLibraryService.data" class="library mgn-top10 ft-size14">
<a (click)="onHoldMessageLibraryService.getSelectedMessageLibrary(messageLibrary)"><i class="fa fa-folder-o mgn-right10" aria-hidden="true"></i>{{messageLibrary.Name}}</a>
<library-category *ngIf="messageLibrary.treeIsExpanded" (displayMessagesFromSelectedCategory)="getOnHoldMessages()"></library-category>
</div>
library.service
public treeIsExpanded: boolean;
public selectMessages: boolean;
public data: TreeData[];
public selectedName: string;
public selectedValue: number;
public getSelectedMessageLibrary(messageLibrary): void {
this.selectedMessagesLibrary = messageLibrary;
this.selectedMessagesLibrary.treeIsExpanded = !this.selectedMessagesLibrary.treeIsExpanded;
}
public dummyData(): void {
let myTree: TreeData[] = new Array();
myTree.push(
{
Name: 'Banking Library',
Id: 1,
Category: [{
Name: 'Credit Cards',
Id: 11,
Category: null
}]
},
{
Name: 'Automobile Library',
Id: 2,
Category: [{
Name: 'Cars',
Id: 12,
Category: null
}]
},
{
Name: 'Coffee Library',
Id: 3,
Category: [{
Name: 'Americano',
Id: 13,
Category: null
}]
}
)
this.data = myTree;
}
library-category.component.html
<ul *ngFor="let category of onHoldMessageLibraryService.selectedMessagesLibrary.Category">
<li><i class="fa fa-folder-o mgn-right10" aria-hidden="true"></i>{{category.Name}}</li>
</ul>
tree-data.ts
export class TreeData {
Name: string;
Id: number;
Category: TreeData[];
}
public getSelectedMessageLibrary(messageLibrary): void {
this.selectedMessagesLibrary = messageLibrary;
this.selectedMessagesLibrary.treeIsExpanded = !this.selectedMessagesLibrary.treeIsExpanded; //this line is not giving desired result
}
to be replaced with
public getSelectedMessageLibrary(messageLibrary): void {
messageLibrary.treeIsExpanded = !messageLibrary.treeIsExpanded;
}
And it will be good if you use trackBy with NgFor

Resources