I have a situation where I receive an object of data from my database that contains records for versions of a file. There could be 5 versions (5 records) or a single version returned.
My issue is that I am using ngFor to loop over the array of data and print them to the table. In the event that there is only a single record returned, the data is no longer an array of objects and is just a single object.
<tbody class="ruleVersions">
<tr *ngFor="let m of mapData.ruleVersion.x">
<td class="small">V {{ m.RuleVersion }}.0</td>
<td [innerHtml]="m.CreatorNTID | ntidLink"></td>
<td class="small">{{ m.VersionNotes ? m.VersionNotes : 'N/A' }}</td>
<td class="small">{{ m.BasedOnRuleVersionID ? m.BasedOnRuleVersionID : 'N/A' }}</td>
<td class="small">{{ m.MetaInsertUtc }}</td>
</tr>
</tbody>
Multiple Records:
Single Record:
This poses a problem because the ngFor is set up to loop over arrays. Due to the single record not being an array of objects like when there are multiple, it throws this error:
Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays.
What is the best way to solve for this? Is there anything in angular I can use to treat the data as an array even if its not, perhaps with a pipe of some sort?
The only other way I can think of is passing the entire object through a function and having it push objects into arrays if its a single record. That then poses the problem of not wanting every single record to be an array.
Just curious if there are any built in angular ways to solve for this?
Update:
I solved this by creating a pipe.
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({ name: 'isArray' })
export class IsArrayPipe implements PipeTransform {
transform(x): any {
return Array.isArray(x) ? x : [x];
}
}
<tr *ngFor="let m of mapData.ruleVersion.x | isArray: x">
When you get your response, you can check if it's array or not. If it's not an object, you can set the object in an array, so something like this in service:
getData() {
return this.http.get('url')
.map(response => {
let res = response.json();
if(!Array.isArray(res)) {
return [res]
}
return res;
})
}
So now, no matter if you get an array or an object, it's always iterable with *ngFor as the component always receives an array.
Related
probably missing something obvious but my map function's index param will not increment up its just stuck as 0 or whatever static index I set it to, maybe I'm missing something because my data structure im passing in is an array of objects like:
(20) -> [{},{},{}]
and each object has quite a few properties
I'll provide my map function below for greater context
also I can manually change my index and my code works fine grabbing an object at whatever index I specify i.e '5' or '13' and my depth is correct because its displaying the property values as it should, wondering if I need to nest something to make this work?
if I console log my state I have this structure here just an array of objects
//storing to state
componentDidMount(){
fetch(`https://api.nasa.gov/neo/rest/v1/feed?start_date=${this.state.time[0]}&end_date=${this.state.time[1]}&api_key=ipAxYzaENbqRKb7GgzFPcH6QUBsHXY3QKB7uXOf5`
)
.then(response => response.json())
.then((data) => {
let ast = []
Object.values(data.near_earth_objects).forEach((arr)=>{
//push the two arrays together
ast.push(...arr)
})
this.setState({asteroids:[ast]})
});
}
render() {
return (
<div>
<h4>Near earth objects</h4>
<table>
<tbody>
<tr>
<th>Name</th>
<th>ID</th>
<th>Magnitude</th>
<th>Hazardous</th>
<th>Sentry Object</th>
</tr>
{
this.state.asteroids.map((arr,index)=>(
//for each item (arr) there will be properties
<tr key={arr.toString()}>
<td >{arr[index].name}</td>
<td >{arr[index].id}</td>
<td >{arr[index].absolute_magnitude_h}</td>
<td >{arr[index].is_potentially_hazardous_asteroid==true? "true": "false"}</td>
<td >{arr[index].is_sentry_object==true? "true": "false"}</td>
<td > index {index}</td>
</tr>
))
}
</tbody>
</table>
</div>
)
}
The error is in componentDidMount when you setState of asteroids your taking the array and putting it inside another array
this.setState({asteroids:[ast]})
// instead it should be
this.setState({asteroids: ast })
Checkout this codesandbox with a working example of your code https://codesandbox.io/s/empty-forest-jvwin?file=/src/App.tsx
As for why the index is stuck, it's because the asteroids array in your example has only 1 element (the array that is holding all the asteroids data).
I'm trying to use Google Books API. Firstly I created the interfaces that worked properly, then I created a method which return me an Array with a list of Books.
public getBooks(): Observable<Book[]>{
return this.http.get<Book[]>('https://www.googleapis.com/books/v1/volumes?q=filibusterismo');
}
(I had added the element which I want to search, "filibusterismo", in order to avoid mistakes, but I would change later.)
Then, I want to use the Array of Book, but I can not use a ngFor loop because getBook return me an observable Array of Books instead of an Array of Books. This was my original constructor
books:Book[];
constructor(private GoodreadsService:GoodreadsService) { }
ngOnInit() {
this.libro=this.GoodreadsService.getBooks();
}
I had tried to fix this using async.
*ngFor="let book of books | async"
But I have the same problem. So reading post in SO I learnt that I have to use subscribe in the ngOnInit(), like this.
this.GoodreadsService.getBooks().subscribe(res=>{this.books=res},
error => { // on failure
console.log('Error Occurred:');
});
The problem, is that whereas I does not return me any kind of error, if I write
console.log(this.libro1);
The console say that the array is undefined, whereas I had defined it as an array of Book. Furthermore, I still can not use an ngFor.
I also had tried to map the getBook method, in order that return me an Array instead of an observable array, but I could not do it in spite of reading a lot of question in SO.
So, would somebody tell me how can I fix this issue, in order I could use an ngFor in the HTML?
My HTML is this
<p>busqueda works!ss </p>
<table>
<thead>
<th>Name</th>
<th>Indexs</th>
</thead>
<tbody>
<div *ngFor="let book of books | async">
<h1 >
<p>prueba</p>
</h1>
</div>
</tbody>
</table>
Here's how you need to do it. Initialize the API call and subscribe to accordingly in order to send the request. After the response is returned then iterate over the items over it.
Please refer to the working sample stackblitz attached here.
Sample component.ts :
import { Component, OnInit } from "#angular/core";
import { SampleService } from "./sample.service";
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent implements OnInit {
books: any;
constructor(public sample: SampleService) {}
ngOnInit() {
this.sample.getBooks().subscribe(data => {
console.log(data.items)
this.books = data.items;
});
}
}
Sample HTML :
<table>
<thead>
</thead>
<tbody>
<div *ngFor="let book of books">
<h4>
<p>kind : {{book.kind}}</p>
<p>Id : {{book.id}}</p>
<p>etag : {{book.etag}}</p>
</h4>
</div>
</tbody>
</table>
Service code which return json data
export class EmployeeServiceService {
constructor(private _http:Http) { }
GetEmployees() :Observable<IEmployee[]>{
debugger;
return this._http.get("http://localhost:2724/api/employee/1")
.map((response:Response)=> <IEmployee[]>response.json())
}
}
In this component class im not able to convert the json data please any one help me to solve my issue
export class EmployeeListComponent implements OnInit {
Employees:IEmployee[];
constructor( private _EmployeeService: EmployeeServiceService) {
}
ngOnInit() {
this._EmployeeService
.GetEmployees().subscribe((employeeData)=> this.Employees = employeeData);
}
}
html
<tbody>
<tr *ngFor="let employee of Employees">
<td>{{employee.Address}}</td>
<td>{{employee.City}}</td>
<td>{{employee.EmployeeID}}</td>
<td>{{employee.FirstName}}</td>
<td>{{employee.LastName}}</td>
</tr>
<tr *ngIf="!Employees|| Employees.length== 0">
<td>No employee to display!!!</td>
</tr>
</tbody>
enter code here
This error occurs whenever you are trying to bind an Object with ngFor. ngFor directive supports only arrays.
As per the request, it seems it returns only one Object,
return this._http.get("http://localhost:2724/api/employee/1")
if you need to bind only one Object no need to use ngFor.
Also you need to initialize employees with an empty array.
Employees:IEmployee[] = [];
if you still want to bind one Object to the table, push the Object to the array as follows,
this.Employees.push(employeeData);
Two options
(i) Change your request to return all employees
return this._http.get("http://localhost:2724/api/employee/all")
(ii) Push the Object to an array as follows,
ngOnInit() {
this._EmployeeService
.GetEmployees().subscribe((employeeData)=> this.Employees.push(employeeData);
}
I have a list of objects stored in firebase database:
I am getting this list using angular http get request. After getting it I want to iterate on a li in html template using ngFor="let subject of subjects" which gives Error:
ERROR Error: Cannot find a differ supporting object '[object Object]'
of type 'object'. NgFor only supports binding to Iterables such as
Arrays.
As I searched online, I came to know that ngFor can only be used on arrays while I am getting nested objects. Can anyone please suggest me what should I do iterate these objects.
So far I have tried Array.of which converts whole list into someArray[0]. I also tried to manually change the unique ids of objects into array indexes [0, 1, 2] which worked but when I add new subject using http post firebase automatically assigns unique id to new subject making it uniteratable.
In simple words tell me to convert nested objects into an arrayList or how can I change the firebase default behavior of assigning unique id in angular (I found something like firebase push function which I couldn't understand).
Update:
(Code from ExaminerService)
getBatchSubjects(batch){
return this.http.get(firebaseLinkGoesHere);
}
(Code from Component)
onBatchSelected(event){
this.selectedBatch = event.target.value; //getting value of a html select
this.examinerService.getBatchSubjects(this.selectedBatch)
.subscribe(
(response : Response) => {
this.selectedBatchData = response.json();
}
),(error) => {
console.log(error);
}
}
(Code from HTML Template)
<div class="batchDetails">
<table class="table table-striped">
<thead>
<tr>
<th class="text-center">S.No</th>
<th>Subject Name</th>
<th>Facilitator</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let subject of selectedBatchData.subjects; let i = index">
<td class="text-center"> {{i + 1}} </td>
<td> {{subject.name}} </td>
<td> {{subject.facilitator}} </td>
</tr>
</tbody>
</table>
</div>
You can use Object.values to turn the properties into an array. This can be done in several ways.
Method 1 - By applying the map operator to the observable data (see this stackblitz):
import { Observable } from "rxjs/Rx";
public subjects: Array<any>;
this.examinerService.getBatchSubjects(this.selectedBatch)
.map(x => (Object as any).values(x.json().subjects))
.subscribe(
(values) => { this.subjects = values; },
(error) => { console.log(error); }
);
Method 2 - With a property getter or a method of the component class (see this stackblitz):
get subjects(): Array<any> {
return (Object as any).values(this.selectedBatchData.subjects);
}
Method 3 - By converting the values in the subscribe callback (see this stackblitz):
public subjects: Array<any>;
...
this.selectedBatchData = response.json();
this.subjects = (Object as any).values(this.selectedBatchData.subjects);
Template
In the template, the ngFor directive would iterate over the subjects array:
<tr *ngFor="let subject of subjects; let i = index">
<td class="text-center"> {{i + 1}} </td>
<td> {{subject.name}} </td>
<td> {{subject.facilitator}} </td>
</tr>
try following code snippet.
ngFor="let subject of subjects|async
Update
In your ExaminerService you should import FirebaseListObservable in order to define return type FirebaseListObservable<any[]>
import { AngularFireDatabase, FirebaseListObservable } from 'angularfire2/database';
export class ExaminerService{
constructor(private db: AngularFireDatabase) {}
getBatchSubjects(batch){
return this.db.list('/subjects');
}
}
In your Component should look like this
export class ExaminerComponent implements OnInit {
movies: any[];
constructor(private examinerDb: ExaminerService) { }
ngOnInit() {
this.examinerDb.get().subscribe((snaps) => {
this.selectedBatchData = snaps;
});
}
}
Angular has pipe "keyvalue" which will let you parse your object into pairs like subject.key and subject.value
i am trying to a multiple deletion by selecting checkboxes but i get the error Foods.filter is not a function error in my console when i try to make a multiple deletion. When i introduced the pipe in the html table, that was when the error was raised. But without the pipe, it works fine. Could it be that the filter is not receiving the array?
html
<tbody>
<tr *ngFor="let key of Foods | keys; let i = index" >
<td>{{i + 1}}</td>
<td><input #{{Foods[key].id}} [(ngModel)]="Foods[key].selected" type="checkbox"></td>
</tbody>
component
delete() {
this.RemoveFood = this.Foods.filter(Foods => Foods.selected);
for (var food in this.RemoveFood) {
this.httpService.deleteFood(this.RemoveFood[food].id)
}
}
Food seems to be an Object and not an array, so there is no filter method implemented.
delete() {
Object.keys(this.Foods) //get an array of the object keys
.filter(key => this.Foods[key].selected) //filter selected foods
.forEach(key=>{ //for each food, delete
let id= this.Foods[key].id;
this.httpService.deleteFood(id);
});
}
Note that it might be long to remove several foods one by one, maybe you should think to a batch delete method on the server.