Iterate with ngFor on objects obtained from Firebase - arrays

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

Related

Angular return me an Array as undefined, but I had defined previously

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>

error: Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays how to solve

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);
}

Angular2 - ngFor single object vs array

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.

converting angular dynamic table to angular2 reactive form

I am trying to convert a working example of an angular dynamic table into an angular2 reactive form. I want to achieve the same functionality in an angular 2 form:
submit 3 new input values which get added to a table
new values and also get added to the formArray each time
I think I’m close but I think there must be something in how I’m writing the initData function that is making it fail. Am I right in thinking I can pass the new Data entry as an argument of initData function in order to update the formArray?
Any help or direction appreciated!
thanks
live Plunk here
app.component.html
<div formArrayName="personalDetails ">
<input [(ngModel)]="newData.fname" type="text" formControlName="fname">
<input [(ngModel)]="newData.lname" type="text" formControlName="lname">
<input [(ngModel)]="newData.email" type="text" formControlName="email">
</div>
<button (click)=addNew()>Add</button>
<table >
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let personalDetail of myForm.controls.personalDetails.controls" >
<td>{{personalDetail.fname}}</td>
<td>{{personalDetail.lname}}</td>
<td>{{personalDetail.email}}</td>
</tr>
</tbody>
</table>
</form>
app.component.ts
export class AppComponent implements OnInit {
public myForm: FormGroup;
public newData: Data;
constructor(private _fb: FormBuilder) { }
ngOnInit() {
this.newData = new Data();
this.myForm = this._fb.group({
personalDetails : this._fb.array([
this.initData(entryObj),
])
});
}
// create the new formGroup to push to the personalDetails array
initData(entryObj:any) {
return this._fb.group({
fname: [entryObj.fname],
lname: [entryObj.lname],
email: [entryObj.email]
});
}
// add new set of Data
// the new entry variable will be the argument for initData
addNew() {
if (this.newData) {
var entry = {
'fname': this.newData.fname,
'lname': this.newData.lname,
'email': this.newData.email
};
// add to the form controls
const control = <FormArray>this.myForm.controls['personalDetails '];
control.push(this.initData(entry));
}
}
}
export class Data {
public fname: string;
public lname: string;
public email: string;
}
In terms of approach I’ve tried to take reference from the scotch example, the major difference being the requirement here is that new formGroup values are submitted at the same time as augmenting the form array.

Conditional css class assign in angular 2

I have a grid with checkbox, I am putting checked values in an array (currentFocusedRow), now I want to assign activeRow class to the checked row:
// app.ts
class contact {
public contactlist = [{icontact_id: "contact1"}, {icontact_id: "contact2"}, {icontact_id: "contact3"}, {...}];
public currentFocusedRow = ["contact2", "contact3"];
}
// app.html
<tr *ngFor="let contact of contactlist"
[class.activeRow]="contact.icontact_id == currentFocusedRow">
...
</tr>
now since currentFocusedRow is an array i can't simply check like that (contact.icontact_id == currentFocusedRow) there should be something to check if the value is present in that array or not like indexOf.
I think this should work for you:
Typescript
isActive(id) {
return this.currentFocusedRow.indexOf(id) !== -1
}
HTML
<tr *ngFor="let contact of contactlist"
[class.activeRow]="isActive(contact.icontact_id)">
</tr>
You can use index of the object that is current object, by using:
<tr *ngFor="let contact of contactlist; let i = index">
Which will enable you to get those indexes.
Apart from that, you can just use NgClass directive.
Checking with indexOf in directive itself worked, this is pretty amazing that we can use this JS methods directly in DOM:
<tr *ngFor="let contact of contactlist;"
[class.activeRow]="currentFocusedRow.indexOf(contact.icontact_id) != '-1'">
</tr>

Resources