Binding Input Controls to different elements inside FormArray - javascript

I'm facing the following issue and I am unsure how to solve it, here is a minimal example https://stackblitz.com/edit/angular-ivy-vwq15f .
In this example I am trying to bind the two input field (name and gender)
<div formArrayName="formArray" class="column">
<div [formGroupName]="inspectedForm">
<h1>Input field for Person {{inspectedForm +1}}</h1>
<label for="name">name</label>
<input formControlName="name" type="text" class="input" placeholder="Name">
<label for="gender">gender</label>
<input formControlName="gender" >
</div>
dynamically to an element inside a FormArray. By clicking on the person (Person #1) I want to be able to change the specific properties that person with the provided input fields. The solution should be capable of selecting a person to edit the properties WITHOUT generating new Input fields.
I guess I must somehow bind to the formControlName of the specific Input like [formControlName="blabla" but I am very unsure.
Thanks a lot in advance :)
My html looks like this
<form [formGroup]="parentForm">
<div *ngFor="let rule of arrayForm.controls; let i = index" class="button is-fullwidth not-clickable">
<button (click)="changeInspectedElement(i)">Person {{i +1}}</button>
</div>
<button (click)="addElement()">Add Element</button>
<div formArrayName="formArray" class="column">
<div [formGroupName]="inspectedForm">
<h1>Input field for Person {{inspectedForm +1}}</h1>
<label for="name">name</label>
<input formControlName="name" type="text" class="input" placeholder="Name">
<label for="gender">gender</label>
<input formControlName="gender" >
</div>
</div>
</form>
<p>
{{parentForm.value | json}}
</p>
My .ts looks like this
import { Component } from '#angular/core';
import { FormArray, FormBuilder, FormGroup } from '#angular/forms';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
parentForm: FormGroup
inspectedForm: number = 0
constructor(private _fb: FormBuilder,) {
this.parentForm = this._fb.group({
formArray: this._fb.array([
this.createArrayForm()
])
})
}
createArrayForm() {
return this._fb.group({
name: [''],
gender: ['']
})
}
changeInspectedElement(index) {
let value = this.arrayForm.value[index]
this.inspectedForm = index
}
addElement() {
this.arrayForm.push(this.createArrayForm())
}
get arrayForm() {
return this.parentForm.get('formArray') as FormArray
}
}
Please let me know if there is anything unclear about my question.

You has a FormArray of FormGroup, so, you can create a function that return this formGroup
getGroup(index)
{
return (this.parentForm.get('formArray') as FormArray).at(index) as FormGroup
//or using your function arrayForm
//return this.arrayForm.at(index) as FormGroup
}
So, you can feel free to use this function in html to indicate FormGroup
<div [formGroup]="getGroup(inspectedForm)">
<input formControlName="name" type="text" class="input" placeholder="Name">
<input formControlName="gender" >
Yes, it's not necesary use FormArray. futhermore, in the .html has no reference to parentForm. Well the only you need is that your buttons change the variable inspectedForm.
<button (click)="inspectedForm=i">Person {{i +1}}</button>
your forked stackblitz

Related

What is the easiest way to make editForm in Angular?

In my database i have many users which has many recipes.
Every recipe has some properties and collection of ingredients.
Below is screenshot
Recipe with all properties
So when user display recipe to edit on page should appear (form) recipe with loaded current data. This is kind of working because i can see the data but i think it's no done good.
I have form which is working fine without array (ingredients). Could you tell me how i should add ingredients to my edit form?
I'd be grateful if you see at my code and give me feedback and hints what i should change.
export class RecipeEditComponent implements OnInit {
#ViewChild('editForm') editForm: NgForm;
recipe: IRecipe;
photos: IPhoto[] = [];
ingredients: IIngredient[] = [];
uploader: FileUploader;
hasBaseDropZoneOver = false;
baseUrl = environment.apiUrl;
currentMain: IPhoto;
constructor(private route: ActivatedRoute, private recipeService: RecipeService,
private toastr: ToastrService) { }
ngOnInit(): void {
this.loadRecipe();
}
loadRecipe() {
this.recipeService.getRecipe(this.route.snapshot.params.id).subscribe(recipe => {
this.recipe = recipe;
this.initializeUploader();
})
}
updateRecipe(id: number) {
this.recipeService.editRecipe(id, this.recipe).subscribe(next => {
this.toastr.success('Recipe updated successfully');
this.editForm.reset(this.recipe);
}, error => {
this.toastr.error(error);
});
}
}
HTML
<div class="container mt-4 border" *ngIf="recipe">
<form #editForm="ngForm" id="editForm" (ngSubmit)="updateRecipe(recipe.id)" >
<h5 class=" text-center mt-2">Recipe details:</h5>
<div class="form-group mt-3">
<label for="city">Name</label>
<input class="form-control" type="text" name="name" [(ngModel)]="recipe.name">
</div>
<div class="form-group">
<app-ingredient-editor [ingredients] = "recipe.ingredients"></app-ingredient-editor>
<div *ngFor="let ingredient of recipe.ingredients; let i = index">
<input class="form-control" type="text" name="{{ingredient.name}}" [(ngModel)]="ingredient.name">
<input class="form-control" type="text" name="{{ingredient.amount}}" [(ngModel)]="ingredient.amount">
</div>
</div>
<div class="form-group">
<br>
<p>Add recipes</p>
</div>
<h5 class=" text-center mt-4">Description</h5>
<angular-editor cols=100% rows="6" [placeholder]="'Your description'" [(ngModel)]="recipe.description" name="description"></angular-editor>
</form>
<button [disabled]="!editForm.dirty" form="editForm" class="btn btn-success btn-block mb-5 mt-5">Save changes</button>
</div>
For now it's look like:
Form on page
When i delete ingredient name while changing on the console i have following error:
recipe-edit.component.html:12 ERROR Error: If ngModel is used within a form tag, either the name attribute must be set or the form
control must be defined as 'standalone' in ngModelOptions.
Problem is that part of code:
<div *ngFor="let ingredient of recipe.ingredients; let i = index">
<input class="form-control" type="text" name="{{ingredient.name}}" [(ngModel)]="ingredient.name">
<input class="form-control" type="text" name="{{ingredient.amount}}" [(ngModel)]="ingredient.amount">
</div>
</div>
But i don't know how to make it working..
How to add add array to template-driven form?
In my case i need to display current ingredients and be able to edit them.
I have tried something like this :
<input class="form-control" type="text" name="ingredient[i].name" [(ngModel)]="ingredient[i].name">
<input class="form-control" type="text" name="ingredient[i].amount" [(ngModel)]="ingredient[i].amount">
But id doesn't work
The problem is that the property name on the form must be defined in order for angular to know which input to update. You're binding name to the same property that the editable model is set to which means the user can edit it and in fact delete it, which isn't good.
The solution is to change it to a unique value that doesn't change. This should work:
<div *ngFor="let ingredient of recipe.ingredients; let i = index">
<input class="form-control" type="text" name="name{{ingredient.id}}" [(ngModel)]="ingredient.name">
<input class="form-control" type="text" name="amount{{ingredient.id}}" [(ngModel)]="ingredient.amount">
</div>
</div>
Link to stackblitz showing it working: https://stackblitz.com/edit/angular-10-base-template-q243lw?file=src%2Fapp%2Fapp.component.html
Edit: fixed bug in original post and added link to stackblitz

angular 6 :- Reactive form validation is not working propery

need regarding angular 6 reactive form validation, I have studied from official angular website
I want to validate my form and display the different error message to different error
Component code
this.pinForm = new FormGroup({
'defaultPin': new FormControl(this.pin.oldPin, [
Validators.required,
Validators.minLength(4)
])
});
Html code
<form [formGroup]="pinForm" #formDir="ngForm">
<div class="form-group">
<label for="defaultPin">Default Pin</label>
{{formDir.valid}}
<input type="text" name="defaultPin" class="form-control" id ="defaultPin" aria-describedby="defaultPin" placeholder="Please enter your old Pin"
formControlName = "defaultPin" />
<small id="defaultPin" class="form-text text-muted">Check your Email address for default pin.</small>
{{defaultPin}}
<div *ngIf="defaultPin.invalid && (defaultPin.dirty || defaultPin.touched)"
class="alert alert-danger">
enter code here
<div *ngIf="defaultPin.errors.required">
Name is required.
</div>
<div *ngIf="defaultPin.errors.minlength">
Name must be at least 4 characters long.
</div>
</div>
</div>
But when I run I'm getting this error
ERROR TypeError: Cannot read property 'invalid' of undefined
at Object.eval [as updateDirectives] (AddPinComponent.html:21)
at Object.debugUpdateDirectives [as updateDirectives] (core.js:10756)
at checkAndUpdateView (core.js:10153)
at callViewAction (core.js:10394)
at execComponentViewsAction (core.js:10336)
at checkAndUpdateView (core.js:10159)
at callViewAction (core.js:10394)
at execEmbeddedViewsAction (core.js:10357)
at checkAndUpdateView (core.js:10154)
at callViewAction (core.js:10394)
Please help me
You are using reactive form and Template-driven together.
Use Only Reactive form.
Do changes in your files. (modify as per your requirement).
Component.Html
<form [formGroup]="pinForm">
<div class="form-group">
<label for="defaultPin">Default Pin</label>
<input type="text" name="defaultPin" class="form-control" id="defaultPin" aria-describedby="defaultPin" placeholder="Please enter your old Pin"
formControlName="defaultPin" />
<span class="text-danger" *ngIf="pinForm.controls['defaultPin'].hasError('required') && (pinForm.controls['defaultPin'].dirty || pinForm.controls['defaultPin'].touched)">This field is required</span>
</div>
</form>
Component.ts
import { FormGroup, FormBuilder, Validators } from '#angular/forms';
export class AppComponent {
pinForm: FormGroup;
constructor(fb: FormBuilder) {
this.pinForm = fb.group({
'defaultPin': [null, Validators.required],
});
}
}
module.ts
// Import ReactiveFormModule in your module.ts file
import { FormsModule, ReactiveFormsModule } from '#angular/forms';
#NgModule({
imports: [ FormsModule, ReactiveFormsModule ],
If you still have problem refer Stackblitz
When you write formControlName = "defaultPin" you provides name for FormControl but in order to access such properties as invalid, errors etc you have to FormControl instance from FormGroup like:
pinForm.get('defaultPin')
So simply add the following getter to your component:
get defaultPin() {
return this.pinForm.get('defaultPin')
}
You need to reference your pinForm to get its controller:
*ngIf="!pinForm.controls.defaultPin.valid && (pinForm.controls.defaultPin.dirty || pinForm.controls.defaultPin.touched)"
So, this is what your form is supposed to look like:
<form [formGroup]="pinForm" #formDir="ngForm">
<div class="form-group">
<label for="defaultPin">Default Pin</label>
{{formDir.valid}}
<input type="text" name="defaultPin" class="form-control" id ="defaultPin" aria-describedby="defaultPin" placeholder="Please enter your old Pin"
formControlName = "defaultPin" />
<small id="defaultPin" class="form-text text-muted">Check your Email address for default pin.</small>
{{defaultPin}}
<div *ngIf="!pinForm.controls.defaultPin.valid && (pinForm.controls.defaultPin.dirty || pinForm.controls.defaultPin.touched)")"
class="alert alert-danger">
enter code here
<div *ngIf="pinForm.controls.defaultPin.errors.required">
Name is required.
</div>
<div *ngIf="pinForm.controls.defaultPin.errors.minlength">
Name must be at least 4 characters long.
</div>
</div>
</div>
Also, you should initiate your formGroup in your components ngOnInit():

Angular 4.0: iterate over multiple input fields

I'd like to iterate over multiple input fields which are defined like this:
<input placeholder="Name" name="name" value="x.y">
<input placeholder="Description" name="description" value"x.z">
<!-- And more fields -->
or like this:
<input *ngFor="let x of y" name="{{x}}" value="{{x.y}}">
Now I want to interate over them, edit their value and give the edited values back to the input field.
Is this what you are looking for ?
<input *ngFor="let x of y" [name]="x.name" [(ngModel)]="x.value">
Try the following (*component.html):
<div>
<input type="text" *ngFor="let key of myObjectKeys"
[placeholder]="key"
[name]="key"
[(ngModel)]="myObject[key]" />
</div>
<div>
{{ diagnostic() }}
</div>
inside the *component.ts class body:
myObject = { Name: '', Description: '' }
myObjectKeys = Object.keys(this.myObject)
diagnostic() {
return JSON.stringify(this.myObject)
}
... you could also use a pipe to get object keys:
https://stackoverflow.com/a/35536052/2644437
*note: to use ngModel directive in angular 4 you also need FormsModule to be imported in the module your component is declared:
import { FormsModule } from '#angular/forms';
//...
imports: [
//...
FormsModule
]

TypeError: Cannot read property 'value' of undefined in Angular 4

I am coming to the error in Angular 4. (Angular-CLI), that says:
TypeError: Cannot read property 'value' of undefined. So, I am new to Angular 4 coming from Angular 1. So, I will be thankful for your help. Thanks in advance.
Here is my code.
app.component.html
<!-- using semantic-ui -->
<form class="ui large form segment">
<h3 class="ui header">Add a link</h3>
<!-- added the title -->
<div class="field">
<label for="title">Title:</label>
<input name="title" #newtitle>
</div>
<!-- added the link -->
<div class="field">
<label for="link">Link:</label>
<input name="link" #newLink>
</div>
<!--added the button -->
<button (click)="addArticle(newTitle, newLink)" class="ui positive right floated button">
Submit Link
</button>
</form>
app.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
addArticle(title: HTMLInputElement, link: HTMLInputElement):
boolean {
console.log(`Adding article title: ${title.value}
and link: ${link.value}`);
return false;
}
}
You casing of the reference variable and the parameter passed do not match. Change <input name="title" #newtitle> to <input name="title" #newTitle> since your are calling addArticle with newTitle with a capital T.

Angular 2 Dynamic Form - Implement Cancel functionality

I'm trying to implement Cancel functionality which should revert back to the previous values in the form, which were present during the last time the form was submitted. I'm trying to create a copy of the data object that's being passed to the form and in the Submit function i'm replacing the new values to the copy and in Cancel function, i'm replacing the copy values to the original values. But i'm not getting the previous values when i call the cancel function.
Working code with errors: http://plnkr.co/edit/SL949g1hQQrnRUr1XXqt?p=preview
I implemented the cancel functionality based on the template driven code of Angualr 2 forms: http://plnkr.co/edit/LCAPYTZElQDjrSgh3xnT?p=preview
Typescript class code:
import { Component, Input, OnInit } from '#angular/core';
import { FormGroup, REACTIVE_FORM_DIRECTIVES } from '#angular/forms';
import { QuestionBase } from './question-base';
import { QuestionControlService } from './question-control.service';
#Component({
selector: 'dynamic-form',
templateUrl: 'app/dynamic-form.component.html',
directives: [REACTIVE_FORM_DIRECTIVES],
providers: [QuestionControlService]
})
export class DynamicFormComponent implements OnInit {
#Input() questions: QuestionBase<any>[] = [];
form: FormGroup;
payLoad:object;
questiont: QuestionBase<any>;
constructor(private qcs: QuestionControlService) { }
ngOnInit() {
this.form = this.qcs.toFormGroup(this.questions);
console.log("Form Init",this.questions);
this.questiont=this.questions;
}
onSubmit() {
this.payLoad = JSON.stringify(this.form.value);
this.payLoad2=this.payLoad;
this.questiont=this.questions;
console.log("Original Data",this.questions);
console.log("Duplicate Data",this.questiont);
}
cancel(){
this.questions=this.questiont;
console.log("Original Data",this.questions);
console.log("Duplicate Data",this.questiont);
console.log("Canceled");
}
}
HTML code:
<div>
<form [formGroup]="form">
<div *ngFor="let question of questions" class="form-row">
<label [attr.for]="question.key">{{question.label}}</label>
<div [ngSwitch]="question.controlType">
<input *ngSwitchCase="'textbox'" [formControlName]="question.key"
[id]="question.key" [type]="question.type" [(ngModel)]="question.value">
<select [id]="question.key" [(ngModel)]="question.value" *ngSwitchCase="'dropdown'" [formControlName]="question.key" >
<option *ngFor="let opt of question.options" [ngValue]="opt.key" >{{opt.value}}</option>
</select>
</div>
<div class="errorMessage" *ngIf="!form.controls[question.key].valid">{{question.label}} is required</div>
</div>
<div class="form-row">
<button type="submit" [disabled]="!form.valid" (click)="onSubmit()">Save</button>
<button type="button" class="btn btn-default" (click)="cancel()">Cancel</button>
</div>
</form>
<div *ngIf="payLoad" class="form-row">
<strong>Saved the following values</strong><br>{{payLoad}}
</div>
</div>
Anyone got this problem or tried to implement this.
Form reset will be implemented in RC5.
Your approach does not work with objects:
this.questiont=this.questions; //you are sharing refrence to the same object
You should clone object instead (there are many ways to do that, i'm using JSON):
this.questiont = JSON.parse(JSON.stringify(this.questions));

Categories