converting angular dynamic table to angular2 reactive form - arrays

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.

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>

getting the updated value of inputs in ng-repeat | Angular Js

I have a inputs in a table filled out with ng-repeat, i want to be able to get the updated values by one click for all inputs.
My View:
<tr ng-repeat="sf in selectedFacture">
// displaying default values in the input
<td><input class="facture_item_name" ng-model="sf.facture_item_name" value="{{sf.facture_item_name}}" type="text"/></td>
<td><input class="fcls_crt" ng-model="sf.fcls_crt" value="{{sf.fcls_crt}}" type="number"/></td>
<td><input class="fpiece" ng-model="sf.fpiece" value="{{sf.fpiece}}" type="number"/></td>
<td colspan="4"><input type="text" class="form-control note" ng-model="sf.note" value="{{sf.note}}"/></td>
<tr>
<td ng-click="updateFacture(sf.id,sf,sf.facture_type,sf.item_id)">SUBMIT</td>
</tr>
</tr>
JS:
// getting new values and send them to server side
$scope.updateFacture=function(id,sf,type,item_id){
var url = '../php/history.php';
var func = "updateFacture";
sf = sf || {};
var editedQuanCls= sf.fcls_crt,
editedQuan_piece= sf.fpiece,
editedQuan_cls_crt_gate= sf.fcls_crt_gate,
editedQuan_piece_gate= sf.fpiece_gate,
editedNote= sf.note;
var data = {"function": func,
"factureId":id,
"item_id":item_id,
"facture_type":facture_type,
"editedQuanCls":editedQuanCls,
"editedQuan_cls_crt_gate":editedQuan_cls_crt_gate,
"editedQuan_piece":editedQuan_piece,
"editedQuan_piece_gate":editedQuan_piece_gate,
"editedNote":editedNote};
var options = {
type : "get",
url : url,
data: data,
dataType: 'json',
async : false,
cache : false,
success : function(response,status) {
alert("success")
},
error:function(request,response,error){
alert("errro: " + error)
}
};
$.ajax(options);
}
I tried to put the updated button in a td aside to the inputs and it works fine, but this will update each row separately, but my need is to updated them all in one click.
I'll attach a screen shot of my view.
Many Thanks in advance
<input class="facture_item_name" ng-model="sf.facture_item_name" value="{{sf.facture_item_name}}" ng-change="updateValues(sf.facture_item_name)" type="text"/>
$scope.updateValues=function(value){
$scope.sf.facture_item_name=value;
}
What you need is a wrapper function.
First add a button on the page that covers the All option like:
<button ng-click="updateAllFacture()">SUBMIT ALL</button>
Then add the wrapper function. All this does is loop through each item in the list and call the update function.
The wrapper function would look like:
$scope.updateAllFacture=function(){
angular.forEach($scope.res, function(sf, index) {
$scope.updateFacture=function(sf.id,sf,sf.facture_type,sf.item_id );
});
};
If you have an awful lot of items then there will be a lot of calls back to your api. Consider submitting all the inputs in the form as a post instead - then there will be just one call back, but you will need to program your controller for that.

Iterate with ngFor on objects obtained from Firebase

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

Update a specific table row based on unique id

I have two components, the parent is called Layout and the child Report. In Report I have a table that loops through and renders a list of car accidents. I have a handler - handleIncidentReport in Report that calls a function in Layout to update this.state.obds (car messages) which then obviously updates the child Report.
My question is what is the proper ReactJS way to have it so only the row clicked has its {this.props.incident_report} updated, and not the other dynamically created rows with the same yield statement (not sure of the correct terminology).
I'm less than a week into ReactJS and I know I could hack it together but I want to know the proper way.
Here is a chunk of the two files -
Report
handleIncidentReport: function(e) {
var accident_id = $(e.target).closest('tr').data('accident-id')
this.props.changeIncidentReport(e, accident_id)
},
render: function() {
var self = this;
var accidents = [];
for (var i = 0; i < this.props.accidents.length; i++) {
var incident = this.props.accidents[i];
accidents.push([
<tr key={i} onClick={self.handleIncidentReport} data-accident-id={incident.id} >
<td>{incident.owner.first_name} {incident.owner.last_name}</td>
<td>{incident.car.title}</td>
<td>{moment(incident.created_at).format("MMM D, YYYY - hh:mm A")}</td>
</tr>,
<tr className="incident-report">
<td colSpan="3">
<Obds obds={this.props.incident_report} />
</td>
</tr>
]);
};
return (
<div className="report">
<table className="table">
<thead>
<tr>
<th>Name</th>
<th>Car</th>
<th>Date</th>
</tr>
</thead>
<tbody>
{accidents}
</tbody>
</table>
</div>
);
}
Layout
changeIncidentReport: function(e, accident_id) {
var $tr = $(e.target).closest('tr');
$.ajax({
method: "GET",
data: {accident_id},
url: "/superadmin/emergency_analysis/get_incident",
datatype: 'jsonp'
}).success(function(incident){
this.setState({
incident_report: incident
});
}.bind(this));
},
You should make it so that handleIncidentReport returns a function that is configured (through closures) with the index of each of the rows, something like:
handleIncidentReport: function(accident_id) {
return function(e) {
this.props.changeIncidentReport(e, accident_id)
}
},
(If you aren't sure how closures work you should check out this excellent post: How do JavaScript closures work?)
Then update your render to use
<tr key={i} onClick={self.handleIncidentReport(i)} data-accident-id={incident.id} >
and each of the rows will have a unique handler all of their own that will be called when they're clicked on.
This should also get rid of some of that jQuery which figures out which accident_id was clicked on too.

Angular JS, bind scope property to function

I'm new to Angular JS. I am creating an app that stores data in the web storage. WHen an enquiry form is submitted, it gets the current arrays from the web storage, strigifies the new form data, adds it to an array, and posts it back to web storage. Works great and all gets saved correctly.
$scope.submit = function () {
// get object from form data
var formData = { firstName: $scope.firstName, lastName: $scope.lastName, date: getDateFromatted(), posted: false };
addStoredData(formData, ENQUIRY_STORE);
}
function getStoredData(storeName) {
// get or create enquiry store
var storedData = (typeof localStorage.getItem(storeName) !== 'undefined' && localStorage.getItem(storeName)) || "[]";
// parse store into object
storedData = JSON.parse(storedData);
return storedData;
}
function addStoredData(data, storeName) {
var storedData = getStoredData(storeName);
var count = storedData.length;
// form data into next submission slot
storedData[count] = data;
// turn back into JSON
storedData = JSON.stringify(storedData);
// slap it back in web storage
localStorage.setItem(storeName, storedData);
}
On my HTML page I have this;
<table>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Date</th>
</tr>
<tr ng-repeat="e in enquiries">
<td>{{ e.firstName }}</td>
<td>{{ e.lastName }}</td>
<td>{{ e.date }}</td>
</tr>
</table>
So I want to be able to do this...
// list of stored enquiries
$scope.enquiries = function () {
return getStoredData(ENQUIRY_STORE);
}
But it doesn't bind and there are no rows. If I do this...
$scope.enquiries = getStoredData(ENQUIRY_STORE);
it works, but then I have to keep setting it when a new enquiry is submitted. Maybe Anulgar doesn't let you have use functions to return data for binding, but I thought it did.
Any help much appreciated. Thanks.
In the first way ($scope.enquiries = function() ...) it doesn't bind because you are making an ngRepeat over a function (it doesn't throw an error because a function has actually a length).
However, even if you did <tr ng-repeat="e in enquiries()"> you would have a infinite digest error because enquiries() is returning a different object each time and angular is waiting to have the same result twice to stop the digests cycle.
The best way would be:
$scope.submit = function() {
...
setEnquiries();
}
...
function setEnquiries() {
$scope.enquiries = getStoredData(ENQUIRY_STORE);
}
And keep looping through enquiries:
<tr ng-repeat="e in enquiries">
$scope.selectedENQUIRY_STORE = null;
$scope.getStoredData= function (e) {
$scope.selectedENQUIRY_STORE = e;
};
it binds when you clicked on that enquiry form on name
You simply have to retrieve the data again, upon submitting, so move your $scope.enquiries() function into your $scope.submit() function.
$scope.submit = function () {
// get object from form data
var formData = { firstName: $scope.firstName, lastName: $scope.lastName, date: getDateFromatted(), posted: false };
addStoredData(formData, ENQUIRY_STORE);
// FIRE UP THE FUNCTION
$scope.enquiries();
}
Note that you should put getStoredData() and other data related function into an angular service.
Luckily, there's already a good module for dealing with LocalStorage operations :
https://github.com/tymondesigns/angular-locker

Resources