Angular 2 Dynamic Form - Implement Cancel functionality - javascript

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

Related

Grab template form input in Vue 3 class components (like done in Angular)

I started vue 3 today, and opted for the class-based approach in the cli. I'm from the Angular background, so forgive me for thinking like Angular. Every example I see (even in the docs) is still using the Vue({...}) thing, however, I wanna do something like this (still thinking Angular-ish)
In angular, I can do this
<form #formData="ngForm" (ngSubmit)="onSubmit(formData.value)">
<input (ngModel)="name" name="name" placeholder="name">
</form>
Then in component
...
export class AppComponent {
public name!: string;
onSubmit(formData: string) {
console.log(formData)
}
}
What would be the vue 3 class components approach like the above?
I currently have this in vue 3
export default class Welcome extends Vue {
name!: string;
onSubmit(formData: any) {
console.log(formData)
}
}
<template>
<div>
<form #submit.prevent="onSubmit">
<p>
<label for="name">Name</label> <br>
<input type="text" placeholder="name" id="name" name="name" v-model="name"/>
</p>
<button type="submit">Send</button>
</form>
</div>
</template>
What changes do I need to do to the above to connect the form in template to the component?
Without a third party library there isn't the same type of functionality that angular provides. Angular is doing a bunch of additional things to enhance the form object for validation and value tracking, and Vue natively does not do that. However you could instead put your data properties in an object to group them together. That way when you need to access them in something like the submit event to perhaps send all the values to an API, you can simply refer to that object instead of having to handle/build each property separately:
Class:
export class AppComponent {
// create object with bound form properties
public values: { name: string; } = { name: '' };
onSubmit() {
console.log(this.values); // { name: '' }
// axios.post('/api', this.values).then(res => console.log(res.data));
}
}
Template:
<template>
<div>
<form v-on:submit.prevent="onSubmit">
<p>
<label for="name">Name</label> <br>
<input type="text" placeholder="name" id="name" name="name" v-model="values.name"/>
</p>
<button type="submit">Send</button>
</form>
</div>
</template>
That being said, if you do need advanced form features like validation, sanitization, and similar there are plenty of libraries that do it well.
Hopefully that helps!
The native FormData constructor takes a <form> element, which creates the values of the form for all named inputs (<input>s or <textarea>s with name attribute).
So you could update your onSubmit method to retrieve the form data values:
export default class Welcome extends Vue {
name!: string;
onSubmit(formData: any) {
const form = e.target
const formData = new FormData(form)
}
}
Example using the Options API:
<template>
<div>
<form #submit.prevent="onSubmit">
<p>
<label for="name">Name</label> <br>
<input type="text" placeholder="name" id="name" name="name" v-model="name"/>
</p>
<button type="submit">Send</button>
</form>
</div>
</template>
<script>
export default {
methods: {
onSubmit(e) {
const form = e.target
const formData = new FormData(form)
console.log({ formData: Array.from(formData.entries()) })
}
}
}
</script>
demo

Binding Input Controls to different elements inside FormArray

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

Angular Validation: Validate/Highlight child input on button click

Using Angular 6 here:
I have a parent component and within that I have a child component. The child component has a text fields. The parent has a submit button.
On the submit click button, I want to validate all the inputs of the child as they are required and throw error accordingly.
I am using reactive forms and was successfully able to pass form info from parent to child. But I am not sure how to highlight my text input when the submit button is clicked.
I have used $touched property on my child, which works and shows the required error. But I want the error to also show in case user just clicked the button.
Here is some relevant code.
--Parent--
<form class="form-horizontal" [formGroup]="myForm" (ngSubmit)="onSubmit()">
<child [myForm]="myForm"></child>
<button type="submit">Submit</button>
</form>
<br>
Form Valid: <pre>{{myForm.valid}}</pre>
export class AppComponent {
myForm: FormGroup
constructor(private fb: FormBuilder) {
this.myForm = this.fb.group({
uname: ['', Validators.required]
});
}
onSubmit() {
console.log('value: ', this.myForm.value);
console.log('the whole form and its controls: ', this.myForm)
}
}
--Child--
<div class="form-group" [formGroup]="myForm">
<label for="myForm" class="col-sm-3 control-label">Name:</label>
<div class="col-sm-8" [ngClass]="{ 'has-error': myForm.controls?.uname.errors }">
<input type="text" formControlName="uname" placeholder="Enter Name...">
<em *ngIf="myForm.hasError('required', 'uname') && myForm.controls?.uname.touched">*Required</em>
</div>
</div>
export class ChildComponent {
#Input() myForm: FormGroup;
ngOnInit() {
}
}
I have also created a demo to show this at:
https://stackblitz.com/edit/angular-dbevnj
FYI, this is just a sample I created to show my issue. I would be having 2-3 child components and few form controls on each.
Any inputs how to get this resolved?
We resolved this by calling markAsTouched on all form controls when submitting the form.
In your case you can add this.myForm.get('uname').markAsTouched(); to your onSubmit() method.

Angular ReferenceError : Calling typescript function from html form or button

So I'm pretty new to using Angular and I have a new Angular component, one TS file and one HTML file. In the HTML File, I have a form that is supposed to call a function in the corresponding typescript file when the submit button is pressed. Seems simple, but I'm constantly getting the following error :
Uncaught ReferenceError: validateLogin is not defined
at :4200/HTMLInputElement.onclick (http:/localhost:4200/validateLogin();?email=email%40lol.com&password=password:13:283)
onclick # validateLogin();?email=email%40lol.com&password=password:13
VM569:1 Uncaught ReferenceError: validateLogin is not defined
at :1:1
Two ReferenceErrors, one for the attempt with onclick and once for the attempt with the action attribute in form. On submit, I want the div to disappear, and the text "Success" to display. Here is the two files :
import { Component } from '#angular/core';
import { NullAstVisitor } from '#angular/compiler';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'TaManagementApp';
toggleLoginHTML = true;
constructor() {
this.toggleLoginHTML = true;
}
validateLogin() {
// if ()
this.toggleLoginHTML = false;
}
}
And
<!--The content below is only a placeholder and can be replaced.-->
<div *ngIf=toggleLoginHTML>
<div style="text-align:center">
<h1>
TA Management System
</h1>
</div>
<button onclick='validateLogin()'></button>
<div style="text-align:center">
<form action="javascript:validateLogin()">
Email: <input type="email" name="email" value="email"><br>
Password: <input type="password" name="password" value="password"><br>
<input type="submit" value="Submit" onclick='validateLogin()'>
</form>
</div>
</div>
<div *ngIf=!toggleLoginHTML>
<h1>
Success
</h1>
</div>
Any insight for a beginner would be appreciated, thank you!
Like user184994 and qiAlex said, you should use Angular built in click binding by using (click)="validateLogin()" instead of onclick.
However I wanted to suggest you to try an take a look at the Angular Reactive Form Guide in which they explain fairly well how to implement a straightforward binding between your form and your model and possibly build very complex forms.
Also take a look at the Form Validation Guide for some deeper information on how to validate your form inputs.
Instead of
<button onclick='validateLogin()'></button>
...
<input type="submit" value="Submit" onclick='validateLogin()'>
Try
<button (click)="validateLogin()"></button>
...
<input type="submit" value="Submit" (click)="validateLogin()">

Creating a Dynamic Form in a Aurelia View

Problem Overview
I have a controller with a view model which contains initially an empty array which will be used for storing 'Test Inputs'. I want to provide the user a button to add a new Test Input on the form which adds a new Test Input object to the array and displays the fields needed to edit its properties.
This works when the button is pressed for the first time (but with incorrect binding) but fails to create additional form elements when pressed again.
The Controller with View Model
import {inject} from 'aurelia-framework';
import {HttpClient, json} from 'aurelia-fetch-client';
import {Router} from 'aurelia-router';
import 'fetch';
import toastr from 'toastr';
#inject(HttpClient, Router)
export class create {
constructor(http, router) {
this.vm = {
test: {
id: 0,
description: "",
testOutput: {
id: 0,
valueType: "",
value: ""
},
testInputs: []
}
};
}
}
The user will be able to add a Test Input to the array by pressing a button which delegates to this function:
addTestInput() {
this.vm.test.testInputs.push({
argumentName: "",
valueType: "",
value: ""
});
}
This function pushes to the Test Inputs array in my view model object a new testInput object.
View
In my view I have added a repeat for binding for each object in the TestInputs array. The loop is intending to create the form elements for editing the properties of each Test Input object in the TestInputs array.
<p if.bind="vm.test.testInputs.length === 0">This test has no inputs. Click the button below to add one.</p>
<template if.bind="vm.test.testInputs.length > 0" repeat.for="testInput of vm.test.testInputs">
<div class="form-group">
<label for="testInputArgumentName${$index}">Argument Name</label>
<input value.bind="testInput.argumentName" type="text" class="form-control" id="testInputArgumentName${$index}" placeholder="Argument Name">
</div>
<div class="form-group">
<div class="form-group">
<label for="testInputValueType${$index}">Expected Value Type</label>
<select class="form-control" value.bind="testInput.valueType">
<option repeat.for="valueType of valueTypeList" value.bind="valueType">${valueType}</option>
</select>
</div>
</div>
<div class="form-group">
<label for="testInputValue${$index}">Expected Value</label>
<template if.bind="testInput.valueType === 'Boolean'">
<select class="form-control" value.bind="testInput.valueType">
<option>true</option>
<option>false</option>
</select>
</template>
<template if.bind="testInput.valueType !== 'Boolean'">
<input value.bind="testInput.value" type="text" class="form-control" id="testInputValue${$index}" placeholder="Expected Value">
</template>
</div>
</template>
<button click.delegate="addTestInput()" type="submit" class="btn btn-success">Add Test Input</button> <button type="submit" class="btn btn-success">Create Test</button>
When I first press the Add Test Input button the form elements are added to the page as expected. However if I press the button again the additional from elements for the new object added to the array are not created.
Also the fields seem to be binding to the local loop variable testInput rather than the specific object in the array.
Attempted Solutions
I have had a go using the suggestions at:
Blog Post on Dynamic Forms in Aurelia
Two Way Binding Array in Aurelia
Aurelia Gitter Chat Log - Help
from jsobell
But they don't seem to have worked for me. Anyone have any ideas?
Your problem is simple. You cannot use if and repeat on the same element. Also in your case are redundant with the p on the first line.
Simple do this:
<template repeat.for="testInput of vm.test.testInputs">
...
</template>
You can find more info here

Categories