Angular 5 build form array from JSON result - arrays

I am having the following issue with Angular form arrays, I was wondering if someone could help me out as I am quite new with Angular?
Apologies I cannot provide a plunker due to the complexity of the project (lots of dependencies and complex code), but I will do my best to provide as much detail as I can!
I have a JSON response from a service call that contains a group of fields (called "myFields") such as:
0:
name: "field1"
1:
name: "field2"
I am getting this response from a call to an API, and I need to build a form using the fields from the reponse. I am currently looping through this response and attempting to build a form array as follows:
constructor(private formBuilder: FormBuilder){
this.myFormGroup = this.formBuilder.group({
aliases: this.formBuilder.array([
])
});
}
get aliases() {
return this.myFormGroup.get('aliases') as FormArray;
}
getServiceFields(){
*call to get fields and store in "myFields"*
for (let item of myFields) {
this.aliases.push(this.createGroup(item));
}
}
createGroup(item): FormGroup {
return this.formBuilder.group({
name: new FormControl(item.name)
});
}
And in my view I have:
<div [formGroup]="myFormGroup" class="example-form">
<div formArrayName="aliases" >
<div *ngFor="let field of myFormGroup.controls.aliases.controls;
let i=index">
<mat-form-field>
<input matInput placeholder="{{field.value.name}}"
formControlName="{{field.value.name"}}>
</mat-form-field>
The issue I am having is that nothing shows on the page and this is the error I see in the console window:
Error: Cannot find control with path: 'aliases -> name'
I will also attach a screenshot showing the structure of my FormGroup in the console window:
FormGroup structure
Hopefully this is enough information, if additional details are required I can provide them. Anyone have an idea where I am going wrong? Thanks!
Edit: I cannot hard code the formControlName (e.g formControlName="name") as I am looping through the list of controls in "aliases", this is why I am trying to use {{field.value.name}}

<div *ngFor="let field of myFormGroup.controls.aliases.controls;
let i=index">
<div [formGroup]="field">
<mat-form-field>
<input matInput placeholder="{{field.value.name}}"
formControlName="name">
</mat-form-field>
</div>
replace above code in your html.
Problem is you are not binding formgroup before formcontrolname. formcontrolname should work under formgroup.
Please let me know if you have any question.

Related

Angular losing binding after array update

I have a situation where i need to have a dynamic array, think like a booking form, where people are adding flights to the list but it could be 1 flight or 10 flights. i did up a dummy example where i have been able to replicate the issue, i'm hoping it's me and not an issue with the Angluar. anyway, here it is.
I start with an empty array with a button to add items, those items are bound with a *ngFor (code below) each of the fields below are in that array, the "Name" values i have populated 1-5 by just typing
I then Decide to delete number 3 which is successful
I then decide to add a new one, here is where everything goes wrong. as you can see below, it successfully adds the 5th item again, but the one that should have #5 in it, is now blank.
I then press "Create Array" which just dumps the array to console, and i see the below, the values are still in there, but not bound to the Input for that 1 item.
Ok, Now for the code:
This is my HTML Template file:
<form name="form" #f="ngForm">
Name: <input class="input" type="text" name="Name" [(ngModel)]="model.Name"
#Name="ngModel" />
Description: <input class="input" type="text" name="Description"
[(ngModel)]="model.Description" #Description="ngModel" />
<br>
<button (click)="addThought()">New Thought</button>
<div class="Thought" *ngFor="let Thought of myObject.Thoughts;let i=index">
Thought Name:<input name="Name-{{i}}" [(ngModel)]=Thought.Name
#Thought.Name="ngModel" type="Text" /><br>
Thought Description:<input name="Description-{{i}}"
[(ngModel)]=Thought.Description #Thought.Description="ngModel" type="Text"
/>
<br>
<br>
<button (click)="removeThought(Thought)">Remove Thought</button>
</div>
<button (click)="CreateThought()">Create Arrays</button>
</form>
and this is my component TS file:
export class CreateThoughtComponent implements OnInit {
model: any = {};
myObject: customObject = new customObject;
constructor(private guid: Guid, private staticData: StaticDataService) {
}
ngOnInit() {
}
CreateThought() {
console.log(this.myObject);
}
addThought() {
let thought: Thought = new Thought;
this.myObject.Thoughts.push(thought);
}
removeThought(t: Thought) {
this.myObject.Thoughts = this.myObject.Thoughts.filter(item => item !==
t);
}
}
And here is the declaration of the array within an object
export class customObject {
Name: string;
Description: string;
Thoughts: Thought[];
constructor() {
this.Thoughts = new Array<Thought>();
}
}
export class Thought {
Name: string;
Description: string;
}
Any help or suggestions would be greatly appreciated.
This is a tricky thing about Angular's change detection mechanism. You can solve your problem easily by creating a clone of your object. e.g.
addThought() {
let thought: Thought = new Thought;
this.myObject.Thoughts.push(thought);
// clone the object in order to force Angular to apply changes
this.myObject = JSON.parse(JSON.stringify(this.myObject));
}
I solved it by removing the name="Name-{{i}}" from the input's, and adding [ngModelOptions]="{standalone: true}" instead. at it seemed to be an issue with the dynamic way i was assigning the Name to the input using the "index"
I was able to solve it by randomly generating a guid for each name but that created a mire of other issues as well.
That being said, i have also tested DiabolicWord's solution above and it works, since it's so simple going to mark his as the answer.

Is it Item based or content based Collaborative filtering?

(Updated code)
Hi all i want to filtering items for count and quality using the filter functionality in meanjs app. then i tried many ways but unable to get the solution if any one knows the solution please help me.....
here is my plunker sample
Count All
<div class="col-md-2 form-group form-group-default">
<label>Quality</label> <select data-ng-model="searchtable.quality" id="quality" ng-options="item.quantity as item.quantity for item in descriptionyarnqualitys" class="form-control" placeholder="Quality"required><option value="">All</option></select>
</div>
The quality property is a sub property of colorshades and not of the order itself. Use searchtable.colorshades.quality model name and it works
dynamically extract the 'quality' value from the order list:
$scope.getDescriptionyarnqualitys = function() {
var qualities = {};
angular.forEach($scope.sryarnorders, function(order) {
angular.forEach(order.colorshades, function(shade) {
qualities[shade.quality]=shade.quality;
});
})
return qualities;
};
In the HTML you can call the function to extract the available qualities:
<select data-ng-model="searchtable.colorshades.quality" id="quality" ng-options="name for (name, value) in getDescriptionyarnqualitys()" class="form-control" placeholder="Quality" required>
Hope this help.

Post full form data to a service in Angular

I have a form that contains a lot of fields and I want to post all the form fields to a service using a post method. But I would like to send the whole form object and not to write one property by one. If I try to post the object that contains all my fields $scope.formData it also contains all the angular stuff inside like errors. What I need is a collection of field names and values. How can I achieve this with minimum coding?
Edit:
I ended up writing my own function:
function getAngularFormFields(form) {
var dictionary = { form: {} };
for (var key in form) {
if (form.hasOwnProperty(key) && !key.indexOf('$') == 0) {
dictionary.form[key] = form[key].$modelValue;
}
}
return dictionary;
}
Normally if you need to post a form you could just use the default method provided by your browser. This will send the form data, via POST, to your URL.
<form action="yourUrlHere" method="POST">
First name: <input type="text" name="fname">
Last name: <input type="text" name="lname">
<input type="submit" value="Submit">
</form>

AngularJS: list all form errors

Background:
I am currently working on an application with tabs; and I'd like to list the fields / sections that fail validation, to direct the user to look for errors in the right tab.
So I tried to leverage form.$error to do so; yet I don't fully get it working.
If validation errors occur inside a ng-repeat, e.g.:
<div ng-repeat="url in urls" ng-form="form">
<input name="inumber" required ng-model="url" />
<br />
</div>
Empty values result in form.$error containing the following:
{ "required": [
{
"inumber": {}
},
{
"inumber": {}
}
] }
On the other hand, if validation errors occur outside this ng-repeat:
<input ng-model="name" name="iname" required="true" />
The form.$error object contains the following:
{ "required": [ {} ] }
yet, I'd expect the following:
{ "required": [ {'iname': {} } ] }
Any ideas on why the name of the element is missing?
A running plunkr can be found here:
http://plnkr.co/x6wQMp
As #c0bra pointed out in the comments the form.$error object is populated, it just doesn't like being dumped out as JSON.
Looping through form.$errors and it's nested objects will get the desired result however.
<ul>
<li ng-repeat="(key, errors) in form.$error track by $index"> <strong>{{ key }}</strong> errors
<ul>
<li ng-repeat="e in errors">{{ e.$name }} has an error: <strong>{{ key }}</strong>.</li>
</ul>
</li>
</ul>
All the credit goes to c0bra on this.
Another option is to use one of the solutions from this question to assign unique names to the dynamically created inputs.
I made a function that you pass the form to. If there are form errors it will display them in the console. It shows the objects so you can take a look. I put this in my save function.
function formErrors(form){
var errors = [];
for(var key in form.$error){
errors.push(key + "=" + form.$error);
}
if(errors.length > 0){
console.log("Form Has Errors");
console.log(form.$error);
}
};
Brett DeWoody's answer is correct. I wanted to do the logic in my controller though. So I wrote the below, which is based off of the answer user5045936 gave. This may also help some of you who want to go the controller route. By the way Im using the toaster directive to show my users validation messages.
if (!vm.myForm.$valid) {
var errors = [];
for (var key in vm.myForm.$error) {
for (var index = 0; index < vm.myForm.$error[key].length; index++) {
errors.push(vm.myForm.$error[key][index].$name + ' is required.');
}
}
toaster.pop('warning', 'Information Missing', 'The ' + errors[0]);
return;
}
If you have nested forms then you will find this helpful:
function touchErrorFields(form) {
angular.forEach(form.$error, function (field) {
angular.forEach(field, function(errorField) {
if (!errorField.hasOwnProperty('$setTouched')) {
touchErrorFields(errorField);
} else {
errorField.$setTouched();
}
})
});
}

ng repeat not updating

Hi I am a newbie to angular js and I am hoping someone can help me out with the following problem.
I have a numeric field called numAdults and I need to show a set of field (such as name, address, telephone etc ) numAdult times to get those information for each of those person.
Here is the jsfiddle for the problem jsfiddle link
Here is also an overview of code of the controller
function bookingController($scope){
$scope.numAdults = 1;
$scope.personLoop = function(){
console.log('personLoop called')
return new Array($scope.numAdults);
//return new Array(2);
}
the html
<label for="book_num_adults">Number of adults:</label>
<input id="book_num_adults" type="text" ng-model="numAdults">
<div class="row" ng-repeat="t in personLoop()" style="border:2px solid red;margin-top:10px">
<h4>Person {{$index+1}}</h4>
<input placeholder="name"><br>
<input placeholder="address"><br>
<input placeholder="telephone"><br>
</div>
Can you also help me with how to transform this as an module ( not just a controller based )
Thank you in advance!
Your Fiddle was riddled with errors...
http://jsfiddle.net/bRgTR/5/
Under Frameworks & Extensions, you need to change the 2nd dropdown from "onLoad" to one of the "No wrap" options
Your controller definition is mangled. It's supposed to be: .controller('name', ['depname', function (depname) { })]); -- you had your closing array misplaced.
You should really use semi-colons.
You don't create an array of 5 items in JavaScript like this: var a = new Array(5), that creates an array that contains a 5. Instead, you should do var a = []; a.length = 5;

Resources