Can't type in HTML form text/password/number input field (website with Unity WebGL build) - unity3d

We are developing a site using Angular 9.
We have also integrated a Unity3D WebGL build in it.
When I try to type something in a text/password/number input field inside one of my forms, it doesn't write anything and the field doesn't seem to receive the input; also, the variable I bound the field to is not updated with the new value.
What makes it weirder is that:
I can select the input field (it gets highlighted as if I can start typing)
I can do CTRL+C on the field and what I copied somewhere else is pasted, as expected
I can use the type="number" arrow selectors to set the value of the field
I cannot type from the keyboard in the fields
I can interact as expected with other form tags, such as <select>
If I reload the page, it usually starts working as expected and I can type into the fields
Here is the code from my login form (component.ts above, template HTML below)
import { Component, OnInit } from '#angular/core';
import { AuthService } from '../auth.service'
import { first } from 'rxjs/operators';
import { Router, ActivatedRoute } from '#angular/router';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.less']
})
export class LoginComponent implements OnInit {
email: string = "";
password: string = "";
returnUrl: string = "home";
constructor(private router: Router, private route: ActivatedRoute, private authService: AuthService) { }
ngOnInit(): void {
let tmpReturnUrl = this.route.snapshot.queryParams["returnUrl"];
if (tmpReturnUrl != undefined)
{
console.log("true");
this.returnUrl = tmpReturnUrl;
}
else
console.log("false");
setInterval(() => {
console.log("EMAIL: " + this.email);
}, 1000);
}
onSubmit(){
this.authService.login(this.email, this.password)
.pipe(first())
.subscribe(
result => {
console.log("CAIOAOAOAOOA");
this.router.navigate([this.returnUrl]);
},
err => console.log(err)
);
}
}
<div class="card z-depth-5 w-50">
<div class="card-body">
<div class="card-title">Log in</div>
<div class="card-text">
<form #companyLoginForm="ngForm" (ngSubmit)="onSubmit()">
<mat-form-field>
<mat-label>Email: </mat-label>
<input matInput required type="text" name="email" id="email" [(ngModel)]="email">
</mat-form-field>
<mat-form-field>
<mat-label>Password: </mat-label>
<input matInput required type="password" name="password" id="password" [(ngModel)]="password">
</mat-form-field>
<button type="submit" [disabled]="!companyLoginForm.form.valid">Login</button>
</form>
<a routerLink="/company-register">
<button mdbBtn type="button" color="primary" class="relative waves-light">Sign Up</button>
</a>
</div>
</div>
</div>
And here, the code from another form where I also use type="number" and <select> (component.ts above, template HTML below)
import { Component, OnInit, Output } from '#angular/core';
import { BlockFormService } from '../block-form.service';
import { BlockData } from '../blockCardData';
import { BlockUtilsService } from '../block-utils.service';
import { ApiService } from '../../core/api.service'
import { NgForm } from '#angular/forms';
#Component({
selector: 'app-block-form',
templateUrl: './block-form.component.html',
styleUrls: ['./block-form.component.less']
})
export class BlockFormComponent implements OnInit {
updateComplete : Boolean = false;
materials : string[];
products : string[];
varieties : string[];
nations : string[];
// companies : {name: string, id: string}[] = [];
company : string = "";
colors : string[] = ["White", "Grey", "Black", "Brown", "Red", "Green", "Yellow", "Blue"];
blockData : BlockData = {_id : "", blockId: "", company: "", material: "", product: "",
variety: "", color: "", nation: "", modelName : "", imagePreview : "",
price: null, blockNumber: "",
length: null, height: null, width: null,
weight: null
};
imagePreview: File = null;
zipFile: File = null;
invalidUpload: boolean = false;
constructor( private blockFormService: BlockFormService, public blockUtils: BlockUtilsService, private companiesUtils: ApiService )
{ }
ngOnInit(): void {
this.materials = this.blockUtils.getMaterials();
this.colors = this.blockUtils.getColors();
this.companiesUtils.getLoggedCompany().subscribe(companiesResult => {
this.blockData.company = companiesResult._id;
this.company = companiesResult.name;
});
}
onImageSelected(event){
console.log(event.target.files[0]);
if (event.target.files[0].type === "image/png")
{
if (this.invalidUpload)
this.invalidUpload = false;
this.imagePreview = event.target.files[0];
}
else{
if (!this.invalidUpload)
this.invalidUpload = true;
event.target.value = null;
}
}
onMaterialSet(newMaterial): void{
console.log("Material set");
this.products = this.blockUtils.getProducts(newMaterial);
//console.log(this.products);
// if (this.products.length > 0)
// this.blockData.product = this.products[0];
// else
this.blockData.product = "";
this.onProductSet(this.blockData.product);
}
onProductSet(newProduct): void{
console.log("Product set");
this.varieties = this.blockUtils.getVarieties(this.blockData.material, newProduct);
// if (this.varieties.length > 0)
// this.blockData.variety = this.varieties[0];
// else
this.blockData.variety = "";
this.nations = this.blockUtils.getNations(this.blockData.material, this.blockData.product);
if (this.nations.length > 0)
this.blockData.nation = this.nations[0];
else
this.blockData.nation = "";
this.onVarietySet(this.blockData.variety);
}
onVarietySet(newVariety): void{
console.log("Variety set");
// this.nations = this.blockUtils.getNations(this.blockData.material, this.blockData.product);
// if (this.nations.length > 0)
// this.blockData.nation = this.nations[0];
// else
// this.blockData.nation = "";
}
onSubmit(blockForm : NgForm, imageField, zipField): void{
this.blockFormService.sendBlock(this.blockData, this.imagePreview, this.zipFile)
.subscribe(res => {
console.log("Sent!");
this.updateComplete = true;
});
this.blockData = {
_id: "", blockId: "", company: "", material: "", product: "",
variety: "", color: "", nation: "", modelName: "", imagePreview: "",
price: null, blockNumber: "",
length: null, height: null, width: null,
weight: null
};
blockForm.resetForm();
imageField.value = null;
zipField.value = null;
this.imagePreview = null;
this.zipFile = null;
}
}
<div class="form-group">
<div class="text-center" *ngIf='updateComplete'>
Block added successfuly
</div>
<form #blockForm="ngForm" (ngSubmit)="onSubmit(blockForm, imageField, zipField)">
<div class="container">
<div class="row">
<div class="col-3">
<mat-form-field>
<mat-label>Company: </mat-label>
<!-- <mat-select required [(ngModel)]="blockData.company" name="company-field"
id="company-field">
<mat-option selected [value]="company.id">{{company.name}}</mat-option>
</mat-select> -->
<input matInput disabled [value]="company" type="text" name="company-field" id="company-field">
</mat-form-field>
</div>
<div class="col-3">
<mat-form-field>
<mat-label>Material: </mat-label>
<mat-select #matField required [(ngModel)]="blockData.material" name="kind-field"
id="kind-field" (selectionChange)="onMaterialSet(blockData.material)">
<mat-option *ngFor="let mat of materials" [value]="mat">{{mat}}</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="col-3">
<mat-form-field>
<mat-label>Product: </mat-label>
<mat-select required [(ngModel)]="blockData.product" name="product-field"
id="product-field" (selectionChange)="onProductSet(blockData.product)">
<mat-option *ngFor="let prod of products" [value]="prod">{{prod}}</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="col-3">
<mat-form-field>
<mat-label>Block Number: </mat-label>
<input matInput required [(ngModel)]="blockData.blockNumber" type="text" name="blockNumber-field" id="blockNumber-field">
</mat-form-field>
</div>
</div>
<div class="row">
<div class="col-3">
<mat-form-field>
<mat-label>Variety: </mat-label>
<mat-select required [(ngModel)]="blockData.variety" name="variety-field" id="variety-field"
placeholder="Variety" (selectionChange)="onVarietySet(blockData.variety)">
<mat-option *ngFor="let variety of varieties" [value]="variety">{{variety}}</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="col-3">
<!-- <label for="color-field">Color: </label> -->
<mat-form-field>
<mat-label>Color: </mat-label>
<mat-select required [(ngModel)]="blockData.color" name="color-field" id="color-field" placeholder="Color">
<mat-option *ngFor="let col of colors" [value]="col">{{col}}</mat-option>
</mat-select>
</mat-form-field>
<!-- <input #colField required [(ngModel)]="blockData.color" type="text" name="color-field" id="color-field" placeholder="Color"> -->
<!-- <color-circle #colorField [colors]='["#f44336", "#e91e63", "#9c27b0", "#673ab7", "#3f51b5", "#2196f3", "#03a9f4", "#00bcd4", "#009688", "#4caf50", "#8bc34a", "#cddc39", "#ffeb3b", "#ffc107", "#ff9800", "#ff5722", "#795548", "#607d8b"]' name="color-field" id="color-field" (onChange)="blockData.color = $event.color.hex"></color-circle> -->
</div>
<div class="col-3">
<mat-form-field>
<mat-label>Nation: </mat-label>
<!-- <mat-select required [(ngModel)]="blockData.nation" name="nation-field"
id="nation-field">
<mat-option *ngFor="let nat of nations" [value]="nat">{{nat}}</mat-option>
</mat-select> -->
<input matInput disabled [(ngModel)]="blockData.nation" type="text" name="nation-field" id="nation-field">
</mat-form-field>
</div>
<div class="col-3">
<mat-form-field>
<mat-label>Price: </mat-label>
<input matInput required [(ngModel)]="blockData.price" type="number" name="price-field" id="price-field">
</mat-form-field>
</div>
</div>
<div class="row">
<div class="col-3">
<mat-form-field>
<mat-label>Length: </mat-label>
<input matInput required [(ngModel)]="blockData.length" type="number" name="length-field" id="length-field">
</mat-form-field>
</div>
<div class="col-3">
<mat-form-field>
<mat-label>Width: </mat-label>
<input matInput required [(ngModel)]="blockData.width" type="number" name="width-field" id="width-field">
</mat-form-field>
</div>
<div class="col-3">
<mat-form-field>
<mat-label>Height: </mat-label>
<input matInput required [(ngModel)]="blockData.height" type="number" name="height-field" id="height-field">
</mat-form-field>
</div>
<div class="col-3">
<mat-form-field>
<mat-label>Weight: </mat-label>
<input matInput required [(ngModel)]="blockData.weight" type="number" name="weight-field" id="weight-field">
</mat-form-field>
</div>
</div>
<div class="row">
<div class="col-3">
<div class="file-field">
<div class="btn btn-primary btn-sm float-left">
<label for="image-field">Upload preview image: </label>
<input #imageField (change)="onImageSelected($event)" name="image-field" id="image-field" type="file" accept=".png, image/png" placeholder="Upload your file">
</div>
</div>
</div>
<div class="col-3">
<div class="file-field">
<div class="btn btn-primary btn-sm float-left">
<label for="zip-field">Upload models' zip: </label>
<input #zipField (change)="zipFile = $event.target.files[0];" name="zip-field" id="zip-field" type="file" placeholder="Upload your file">
</div>
</div>
</div>
</div>
<button type="submit" [disabled]="!blockForm.form.valid || imagePreview == null || zipFile == null || blockData.company === ''">Submit</button>
</div>
</form>
</div>
I hope I was clear enough, any help is appreciated :)

Finally I found the source of the problem. In our website we have integrated a Unity3D WebGL build and, if I moved from the web page with Unity to the login page, the Unity process was still running. Unity had the focus of every input of the keyboard, so it was catching all the inputs.
We resolved it by quitting the Unity application when we change page. This way, input fields can receive inputs from the keyboard again.
Another solution, maybe (I have not tested it), could be to not make Unity get the inputs, as discussed in this Unity forum's thread or by setting WebGLInput.captureAllKeyboardInput to false.

Related

Reset autocomplete input after selection, then show all options

I have an autocomplete input, the problem is whenever I reset the input value it doesn't reset selection options. It only shows the latest selection.
Here's the GIF to illustrate the problem: https://recordit.co/aVXqlPYHFQ
I tried with this.form.get('panelForm').setValue(''); it reset the input but doesn't reset the options
Here's my code
<form *ngIf="options.length">
<mat-form-field [formGroup]="form">
<input type="text" [placeholder]="'Panel'" aria-label="Panel" matInput [formControl]="myControl" [formControlName]="'panelForm'" [matAutocomplete]="auto">
<button mat-button mat-icon-button matSuffix (click)="clear()" *ngIf="form.get('panelForm')">
<mat-icon>close</mat-icon>
</button>
<mat-autocomplete autoActiveFirstOption #auto="matAutocomplete" (optionSelected)='selectPanel($event.option.value)'>
<mat-option *ngFor="let option of filteredOptions | async" [value]="option.name">
{{option.name}} ({{option.genesCount}})
</mat-option>
</mat-autocomplete>
</mat-form-field>
</form>
myControl = new FormControl();
filteredOptions: Observable<Panel[]>;
public form: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.filteredOptions = this.myControl.valueChanges.pipe(
startWith(''),
map(value => { return this._filter(value)})
);
this.form = this.fb.group({
panelForm: new FormControl({ value: '', disabled: this.options.length < 1})
});
}
private _filter(value: string): Panel[] {
const filterValue = value.toLowerCase();
return this.options.filter(option => option.name && option.name.toLowerCase().includes(filterValue));
}
clear(){
this.form.get('panelForm').setValue('');
}
You don't need to use Form control if you are using Form control name (inside a FormGroup).
I recommend using the FormControl and set:
ngOnInit() {
this.myControl = new FormControl({'', disabled: this.options.length < 1});
this.filteredOptions = this.myControl.valueChanges.pipe(
startWith(''),
map(value => { return this._filter(value)})
);
}
clear() {
this.myControl.patchValue('');
}
Should work
Try to reset the value directly in html:
<form *ngIf="options.length">
<mat-form-field [formGroup]="form">
<input #myInput type="text" [placeholder]="'Panel'" aria-label="Panel" matInput [formControl]="myControl" [formControlName]="'panelForm'" [matAutocomplete]="auto"> // <-- set name here 'myInput'
<button mat-button mat-icon-button matSuffix (click)="clear()" *ngIf="form.get('panelForm')">
<mat-icon>close</mat-icon>
</button>
<mat-autocomplete autoActiveFirstOption #auto="matAutocomplete" (optionSelected)="selectPanel($event.option.value);myInput.value=''"> // <-- reset value here
<mat-option *ngFor="let option of filteredOptions | async" [value]="option.name">
{{option.name}} ({{option.genesCount}})
</mat-option>
</mat-autocomplete>
</mat-form-field>
</form>

Angular model driven form click on any button will execute the submit

When i click on any button inside a form, all functions registered inside the form would be executed.
item.components.ts
import { Component, OnInit } from '#angular/core';
import { ItemsService } from '../../share/items.service';
import { MatDialogRef } from '#angular/material';
import { Item } from 'src/app/model/item';
#Component({
selector: 'app-item',
templateUrl: './item.component.html',
styleUrls: ['./item.component.scss']
})
export class ItemComponent implements OnInit {
constructor(public service:ItemsService,
public dialogRef: MatDialogRef<ItemComponent>) { }
ngOnInit() { }
onSubmit(model: Item, isValid: boolean){
console.log('!!! submit !!!');
}
addProperty(){
console.log('!!! add prop !!!');
}
onClear(){
console.log('!!! clear !!!');
}
onClose(){
console.log('!!! close !!!');
}
}
item.ts
export interface Item {
id: BigInteger | null
artNr: string;
name: string;
parentGroup: string;
childGroup: string;
properties: Array<{key: string, text: string}>;
}
items.service.ts
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders, HttpErrorResponse} from '#angular/common/http';
import { Observable, from } from 'rxjs';
import { FormGroup, FormControl, Validators, FormBuilder, FormGroupDirective, FormArray } from '#angular/forms'
import { Address } from '../model/address';
import { Item } from '../model/item';
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'my-auth-token'
})
};
interface ResponseItem{
results:Item[];
}
#Injectable({
providedIn: 'root'
})
export class ItemsService {
constructor(private http:HttpClient, private formBuilder:FormBuilder) { }
ngOnInit(){
this.initializeItemFormGroup();
}
//** ITEM AREA */
itemForm: FormGroup;
initializeItemFormGroup(){
this.itemForm = this.formBuilder.group({
id: [null],
artNr: ['', [Validators.required]],
name: ['', [Validators.required]],
parentGroup: ['', [Validators.required]],
childGroup: [''],
properties: this.formBuilder.array([this.addPropertyGroup()] )
});
}
addPropertyGroup(){
return this.formBuilder.group({
key: [''],
text: ['']
})
}
removeProperty(index){
this.property.removeAt(index);
}
addProperty() {
this.property.push(this.addPropertyGroup());
}
get property():FormArray {
return <FormArray>this.itemForm.get('properties');
}
}
item.component.html
<form [formGroup]="service.itemForm" class="normal-form" (ngSubmit)="onSubmit(service.itemForm.value, service.itemForm.valid)">
<mat-grid-list cols="2">
<mat-grid-tile>
<div class="controles-container">
<input type="hidden" formControlName="id" >
<mat-form-field>
<input formControlName="artNr" matInput placeholder="EA0123" >
<mat-error>Artikelnummer zwingend erforderlich</mat-error>
</mat-form-field>
<mat-form-field>
<input formControlName="name" matInput placeholder="Artikelbezeichnung" >
<mat-error>Bezeichnung ist erforderlich</mat-error>
</mat-form-field>
<mat-form-field>
<input formControlName="parentGroup" matInput placeholder="Karosserie" >
</mat-form-field>
<mat-form-field>
<input formControlName="childGroup" matInput placeholder="Schrauben" >
</mat-form-field>
</div>
</mat-grid-tile>
<mat-grid-tile>
<button mat-icon-button (click)="addProperty()"><mat-icon>add</mat-icon></button>
<div class="make-scrollable" formArrayName="properties">
<div *ngFor="let property of service.itemForm.controls.properties?.value; let i=index;" class="panel panel-default">
<ng-container [formGroupName]="i">
<mat-form-field>
<input formControlName="key" matInput placeholder="Titel" >
</mat-form-field>
<mat-form-field>
<input formControlName="text" matInput placeholder="Beschreibender Text" >
</mat-form-field>
</ng-container>
</div>
</div>
</mat-grid-tile>
<mat-grid-tile [colspan]="2">
<div class="buttom-row">
<button mat-raised-button color="primary" type="submit" [disabled]="service.itemForm.invalid">Speichern</button>
<button mat-raised-button color="warn" (click)="onClear()">Bereinigen</button>
</div>
</mat-grid-tile>
</mat-grid-list>
</form>
When i click any Button, the console is logging (currently I tried to add a property: (click)="addProperty()")
Console output:
item.component.ts:32 !!! add prop !!!
item.component.ts:26 !!! submit !!!
item.component.ts:44 !!! close !!!
But i expect the following output
Console output:
item.component.ts:32 !!! add prop !!!
Thanks to Julius Dzidzevičius.
He told me to set the button type="button"
Now everything works fine!!!
See below
´´´HTML
<form [formGroup]="service.itemForm" class="normal-form" (ngSubmit)="onSubmit(service.itemForm.value, service.itemForm.valid)">
<mat-grid-list cols="2">
<mat-grid-tile>
<div class="controles-container">
<input type="hidden" formControlName="id" >
<mat-form-field>
<input formControlName="artNr" matInput placeholder="EA0123" >
<mat-error>Artikelnummer zwingend erforderlich</mat-error>
</mat-form-field>
<mat-form-field>
<input formControlName="name" matInput placeholder="Artikelbezeichnung" >
<mat-error>Bezeichnung ist erforderlich</mat-error>
</mat-form-field>
<mat-form-field>
<input formControlName="parentGroup" matInput placeholder="Karosserie" >
</mat-form-field>
<mat-form-field>
<input formControlName="childGroup" matInput placeholder="Schrauben" >
</mat-form-field>
</div>
</mat-grid-tile>
<mat-grid-tile>
<button mat-icon-button type="button" (click)="addProperty()"><mat-icon>add</mat-icon></button>
<div class="make-scrollable" formArrayName="properties">
<div *ngFor="let property of service.itemForm.controls.properties?.value; let i=index;" class="panel panel-default">
<ng-container [formGroupName]="i">
<mat-form-field>
<input formControlName="key" matInput placeholder="Titel" >
</mat-form-field>
<mat-form-field>
<input formControlName="text" matInput placeholder="Beschreibender Text" >
</mat-form-field>
</ng-container>
</div>
</div>
</mat-grid-tile>
<mat-grid-tile [colspan]="2">
<div class="buttom-row">
<button mat-raised-button color="primary" type="submit" [disabled]="service.itemForm.invalid">Speichern</button>
<button mat-raised-button type="button" color="warn" (click)="onClear()">Bereinigen</button>
</div>
</mat-grid-tile>
</mat-grid-list>
</form>
´´´´

Form reset issue after get response via API in react js

I am submitting form via redux API call and getting response but on success response I am trying to reset form but that giving issue -
setState(...): Cannot update during an existing state transition (such as withinrenderor another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to 'componentWillMount'.
here is my from file -
=========================
import React from 'react';
import DefaultLayout from '../Layout/DefaultLayout';
//import $ from 'jquery';
import { connect } from 'react-redux';
import { contactRequest } from '../actions/signupActions';
import { bindActionCreators } from 'redux';
import validator from 'validator';
class Contactus extends React.Component {
constructor(props){
super(props);
document.title = "Contact Us";
this.errorMapping = {"100": "Your message has been submitted.",
"102": "Name cannot be empty.",
"104": "Email cannot be empty.",
"103": "Hotel cannot be empty.",
"105": "Incorrect email format.",
"106": "Phone cannot be empty."}
this.state = {name: '',email:'',message :''};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.baseState = this.state
}
handleInputChange(event) {
this.setState({ [event.target.name]: event.target.value});
}
handleSubmit(event) {
event.preventDefault();
this.props.contactRequest(this.state);
}
render(){
const style_1 = {height: '240px'};
const style_2 = {marginRight: '15px'};
const style_3 = {width: 'auto'};
return(
<DefaultLayout>
<section id="content">
<div className="content-wrap">
<div className="container clearfix">
<div className="col-md-6 bottommargin">
<section id="google-map" className="gmap" style={style_1} ></section>
</div>
<div className="col-md-6">
Click here to Send an Email
Send an Email
<div className="modal fade" id="contactFormModal" tabIndex="-1" role="dialog" aria-labelledby="contactFormModalLabel" aria-hidden="true">
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-body">
<div className="contact-widget">
<div className="contact-form-result">
{this.props.resultMessage && this.props.resultMessage.status.map((msg, idx) => {
if(msg === 100) {
this.setState({name : "",email : ""});
return <span key={idx} id="succ_msg">{this.errorMapping[msg]}</span>
} else {
return <span key={idx} id="err_msg">{this.errorMapping[msg]}</span>
}
})
}
</div>
<form className="nobottommargin" id="r_contactform" name="r_contactform" method="post" onSubmit={this.handleSubmit} ref={(el) => this.myFormRef = el} >
<div className="form-process"></div>
<div className="col_half">
<label htmlFor="template-contactform-name">Name <small>*</small></label>
<input type="text" id="template-contactform-name" name="name" value={this.state.name} className="sm-form-control required" onChange={this.handleInputChange} />
</div>
<div className="col_half col_last">
<label htmlFor="template-contactform-email">Email <small>*</small></label>
<input type="email" id="template-contactform-email" name="email" value={this.state.email} className="required email sm-form-control" onChange={this.handleInputChange} />
</div>
<div className="clear"></div>
<div className="clear"></div>
<div className="col_half">
<label htmlFor="template-contactform-message">Message <small>*</small></label>
<textarea className="required sm-form-control" id="template-contactform-message" name="message" value={this.state.message} rows="6" cols="30" onChange={this.handleInputChange}></textarea>
<span className={this.state.messageError ? 'help-block error': '' }>{ this.state.messageError ? this.state.messageError: '' }</span>
</div>
<div className="col_full">
<button className="button button-3d nomargin" type="submit" id="contactform-submit" name="contactform-submit">Send Message</button>
</div>
</form>
</div>
</div>
<div className="modal-footer">
<button type="button" className="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</DefaultLayout>
);
}
}
function mapStateToProps(state){
console.log("View data :"+JSON.stringify(state.Contactdata));
return {
resultMessage: state.Contactdata
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({contactRequest: contactRequest}, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps) (Contactus);
getting response in mapStateToProps function and trying to update state where I have applied condition if(msg === 100) .
Please let me know where I am doing wrong.
thanks
The right place to setState is not in the map function, since you are receiving props from redux, you can do it in the componentWillReceiveProps fucntion
class Contactus extends React.Component {
constructor(props){
super(props);
document.title = "Contact Us";
this.errorMapping = {"100": "Your message has been submitted.",
"102": "Name cannot be empty.",
"104": "Email cannot be empty.",
"103": "Hotel cannot be empty.",
"105": "Incorrect email format.",
"106": "Phone cannot be empty."}
this.state = {name: '',email:'',message :''};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.baseState = this.state
}
componentWillReceiveProps(nextProps) {
nextProps.resultMessage.status.forEach((msg, idx) => {
if(msg === 100) {
this.setState({name : "",email : ""});
}
}
}
handleInputChange(event) {
this.setState({ [event.target.name]: event.target.value});
}
handleSubmit(event) {
event.preventDefault();
this.props.contactRequest(this.state);
}
render(){
const style_1 = {height: '240px'};
const style_2 = {marginRight: '15px'};
const style_3 = {width: 'auto'};
return(
<DefaultLayout>
<section id="content">
<div className="content-wrap">
<div className="container clearfix">
<div className="col-md-6 bottommargin">
<section id="google-map" className="gmap" style={style_1} ></section>
</div>
<div className="col-md-6">
Click here to Send an Email
Send an Email
<div className="modal fade" id="contactFormModal" tabIndex="-1" role="dialog" aria-labelledby="contactFormModalLabel" aria-hidden="true">
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-body">
<div className="contact-widget">
<div className="contact-form-result">
{this.props.resultMessage && this.props.resultMessage.status.map((msg, idx) => {
if(msg === 100) {
return <span key={idx} id="succ_msg">{this.errorMapping[msg]}</span>
} else {
return <span key={idx} id="err_msg">{this.errorMapping[msg]}</span>
}
})
}
</div>
<form className="nobottommargin" id="r_contactform" name="r_contactform" method="post" onSubmit={this.handleSubmit} ref={(el) => this.myFormRef = el} >
<div className="form-process"></div>
<div className="col_half">
<label htmlFor="template-contactform-name">Name <small>*</small></label>
<input type="text" id="template-contactform-name" name="name" value={this.state.name} className="sm-form-control required" onChange={this.handleInputChange} />
</div>
<div className="col_half col_last">
<label htmlFor="template-contactform-email">Email <small>*</small></label>
<input type="email" id="template-contactform-email" name="email" value={this.state.email} className="required email sm-form-control" onChange={this.handleInputChange} />
</div>
<div className="clear"></div>
<div className="clear"></div>
<div className="col_half">
<label htmlFor="template-contactform-message">Message <small>*</small></label>
<textarea className="required sm-form-control" id="template-contactform-message" name="message" value={this.state.message} rows="6" cols="30" onChange={this.handleInputChange}></textarea>
<span className={this.state.messageError ? 'help-block error': '' }>{ this.state.messageError ? this.state.messageError: '' }</span>
</div>
<div className="col_full">
<button className="button button-3d nomargin" type="submit" id="contactform-submit" name="contactform-submit">Send Message</button>
</div>
</form>
</div>
</div>
<div className="modal-footer">
<button type="button" className="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</DefaultLayout>
);
}
}
This is the reason, you are doing setState inside render method:
{this.props.resultMessage && this.props.resultMessage.status.map((msg, idx) => {
if(msg === 100) {
this.setState({name : "",email : ""});
.....
When msg==100 will be true, you are doing setState, again it will trigger re-rendering, again setState ....... infinite loop.
Remove that setState it will work.
Use componentWillReceiveProps lifecycle method, and do the setState inside that.
componentWillReceiveProps(newProps) {
newProps.resultMessage.status.forEach((msg, idx) => {
if(msg === 100) {
this.setState({
name : "",
email : ""
});
}
}
}

Cannot find a differ supporting object '[object Object]' of type, Angular 2

I'm newbie in Angular 2 and trying to write a simple ng-form following by official tutorial.
If I'm using simple array from tutorial, it works fine:
powers = ['Really Smart', 'Super Flexible',
'Super Hot', 'Weather Changer'];
But when I'm changing it on my custom array from http
public departments = [];
constructor(http: Http) {
http.get('/api/departments')
.map((res: Response) => res.json())
.subscribe((departments: Array<Object>) => this.departments = departments);
}
I'm getting an error:
error_handler.js:51 Error: Uncaught (in promise): Error: Error in ./AddClientComponent class AddClientComponent - inline template:41:12 caused by: Cannot find a differ supporting object '[object Object]' of type 'departments'. NgFor only supports binding to Iterables such as Arrays.
So where is my mistake and what am I missing? thanks in advance.
AddClientComponent
import 'rxjs/add/operator/map';
import {Component} from '#angular/core';
import {Http, Response} from '#angular/http';
import { DepartmentsComponent } from '../departments/departments.component';
#Component({
selector: 'app-add-client',
templateUrl: './add-client.component.html',
styleUrls: ['./add-client.component.css']
})
export class AddClientComponent {
public departments = [];
public firstName = '';
public lastName = '';
public id = null;
constructor(http: Http) {
http.get('/api/departments')
.map((res: Response) => res.json())
.subscribe((departments: Array<Object>) => this.departments = departments);
}
model = new Employee(
this.id,
this.firstName,
this.lastName,
this.departments
);
submitted = false;
onSubmit() { this.submitted = true; }
active = true;
}
export class Employee {
constructor(
public id: number,
public firstName: string,
public lastName: string,
public departments: any
) { }
}
html
<div class="container">
<div [hidden]="submitted">
<h1>Employee Form</h1>
<form *ngIf="active" (ngSubmit)="onSubmit()" #employeeForm="ngForm">
<div class="form-group">
<label for="firstName">First Name</label>
<input type="text" class="form-control" id="firstName"
required
[(ngModel)]="model.firstName"
name="firstName"
#firstName="ngModel" >
<div [hidden]="firstName.valid || firstName.pristine"
class="alert alert-danger">
First Name is required
</div>
</div>
<div class="form-group">
<label for="lastName">Last Name</label>
<input type="text" class="form-control" id="lastName"
required
[(ngModel)]="model.lastName"
name="lastName"
#lastName="ngModel" >
<div [hidden]="lastName.valid || lastName.pristine"
class="alert alert-danger">
Last Name is required
</div>
</div>
<div class="form-group">
<label for="departments">Department</label>
<select class="form-control" id="departments"
required
[(ngModel)]="model.departments"
name="departments"
#departments="ngModel" >
<option
*ngFor="let department of departments"
[value]="department">{{department.name}}
</option>
</select>
<div [hidden]="departments.valid || departments.pristine"
class="alert alert-danger">
Department is required
</div>
</div>
<button type="submit"
class="btn btn-default"
[disabled]="!employeeForm.form.valid">Submit
</button>
<!--<button type="button"-->
<!--class="btn btn-default"-->
<!--(click)="newHero()">New Hero-->
<!--</button>-->
</form>
</div>
<div [hidden]="!submitted">
<h2>You submitted the following:</h2>
<div class="row">
<div class="col-xs-3">First Name</div>
<div class="col-xs-9 pull-left">{{ model.firstName }}</div>
</div>
<div class="row">
<div class="col-xs-3">Last Name</div>
<div class="col-xs-9 pull-left">{{ model.lastName }}</div>
</div>
<div class="row">
<div class="col-xs-3">Department</div>
<div class="col-xs-9 pull-left">{{ model.departments }}</div>
</div>
<br>
<button class="btn btn-default" (click)="submitted=false">Edit</button>
</div>
</div>
Use a different name for
#departments="ngModel"
I think it overloads the departments property of the class used in *ngFor
Try changing type
public departments: Array<any> = [];
constructor(http: Http) {
http.get('/api/departments')
.map((res: Response) => res.json())
.subscribe((departments: Array<any>) => this.departments = departments);
}

angular2: how to use input fields in sub components of forms

Look at my plunkr:
http://plnkr.co/edit/hB34VjxP98uz1iAYS7Dw?p=preview
Name is included in myform.form, but Name1 of component inner is not. How do I include Name1 in myform?
<div class="container">
<div [hidden]="submitted">
<h1>Hero Form</h1>
<form #heroForm="ngForm">
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" required
[(ngModel)]="modelname"
name="name" #name="ngModel" >
<div [hidden]="name.valid || name.pristine" class="alert alert-danger">
Name is required
</div>
<inner></inner>
</div>
</form>
{{heroForm.form.value | json}}
</div>
Template of inner.component:
<label for="name">Name1</label>
<input type="text" class="form-control" required
[(ngModel)]="modelname1"
name="name1" #name1="ngModel" >
<div [hidden]="name1.valid || name1.pristine" class="alert alert-danger">
Name1 is required
</div>
See this issue:
https://github.com/angular/angular/issues/9600
I have fixed your code to accomplish your end goal. Now both Name and Name1 are included in the form and values are coming up on the display for you
I have created a fork from your plnkr and fixed your code to support your use case. Please take a look : http://plnkr.co/edit/UHkwJ9?p=preview
inner.component.html is changed as :
<label for="name">Name1</label>
<input type="text" class="form-control" required
[(ngModel)]="modelname1" [formModel]="form" [registerModel]="name2"
name="name2" #name2="ngModel" >
<div [hidden]="name2.valid || name2.pristine" class="alert alert-danger">
Name1 is required
</div>
hero-form.component.ts is changed as :
<inner [form]="heroForm"></inner>
New Directive is added which will register a new control in the existing form reference :
import { Directive, ElementRef, Input, OnInit } from '#angular/core';
import { NgModel, NgForm } from '#angular/forms';
#Directive({
selector: '[formModel]'
})
export class FormModelDirective implements OnInit {
private el: HTMLInputElement;
#Input('formModel') public form: NgForm;
#Input('registerModel') public model: NgModel;
constructor(el: ElementRef) {
this.el = el.nativeElement;
}
ngOnInit() {
if (this.form && this.model) {
this.form.form.addControl(this.model.name, this.model.control);
}
}
}
Output Image :
Output of the plnkr code
Reference : plnkr.co/edit/GG2TVHHHGbAzoOP5mIRr?p=preview