Here's my class:
interface IHobby {
name: string;
type: string;
}
class User {
constructor(public name: string, public hobbies: IHobby[]) { }
}
Now I'm trying to do a template form in Angular 4. I've noticed that in order to display the hobbies, I need to iterate through them. So, here's my HTML:
<div *ngFor="let h of user.hobbies; let i = index">
#{{i}}<br />
<input [(ngModel)]="h.name" name="hobby_name[{{i}}]" /> <br /><br />
<input [(ngModel)]="h.type" name="type[{{i}}]" />
<br /><br />
<button class="btn btn-warn" (click)="remove(i)">Remove</button>
<br /><br />
</div>
While this works, I'm not quite sure if:
I'm doing this the correct Angular 4 way - should I use ngModel for each item in the array?
Is it OK to define names as name="hobby_name[{{i}}]"? Because name="hobby_name_{{i}}" works as well. What is the correct, HTML5 and Angular 4 way?
Here's a working sample: https://plnkr.co/edit/chOsfhBThD3dA9FFqNPX?p=preview
You don't really need to use name attribute since you're binding the inputs directly to the model. If you want to see instant changes in your model, you can keep using [(ngModel)], but if you want to update your model on form submit only, then you should not use ngModel. I would do a reactive form like this:
<div [formGroup]="form">
<div formArrayName="hobbiesFormArray">
<div *ngFor="let h of user.hobbies; let i = index" [formGroupName]="i">
#{{i}}<br />
<input formControlName="name" /> <br /><br />
<input formControlName="type" />
<br /><br />
<button class="btn btn-warn" (click)="remove(i)">Remove</button>
<br /><br />
</div>
</div>
</div>
And then in your component I would do something like:
form: FormGroup;
constructor(fb: FormBuilder) {
this.form = this.fb.group({
name: '',
hobbies: this.fb.array([])
});
}
ngOnChanges() {
this.form.reset({
name: this.user.name
});
this.setHobbies(this.user.hobbies);
}
get hobbiesFormArray(): FormArray {
return this.form.get('hobbiesFormArray') as FormArray;
};
setHobbies(hobbies: IHobby[]) {
const hobbiesFormArray = this.fb.array(hobbies.map(hobby => this.fb.group(hobby)));
this.form.setControl('hobbiesFormArray', this.fb.array());
}
Then in your onSubmit method I would convert the form model back to the user object:
const formModel = this.form.value;
const newHobbies: IHobby[] = formModel.hobbiesFormArray
.map(() => (hobby: IHobby) => Object.assign({}, hobby))
const newUser: User = {
name: formModel.name as string,
hobbies: newHobbies
};
And save newUser object with whatever means you have.
Related
I have the following event in onSubmit using React and I am changing it from Javascript to TypeScript.
const submitUserHandler = (e) => {
e.preventDefault();
dispatch(
authenticate({
name: e.target.personname.value,
pw: e.target.password.value,
})
);
};
I have tried assigning 'e: React.ChangeEvent' to the event, but it prompts an error like this:
Property 'personname' does not exist on type 'EventTarget & HTMLInputElement'.
How could I specify the type also including personname and password?
Thanks!
You can do something like this
<form
ref={formRef}
onSubmit={(e: React.SyntheticEvent) => {
e.preventDefault();
const target = e.target as typeof e.target & {
personname: { value: string };
password: { value: string };
};
const email = target.personname.value; // typechecks!
const password = target.password.value; // typechecks!
// etc...
}}
>
<div>
<label>
Email:
<input type="personname" name="personname" />
</label>
</div>
<div>
<label>
Password:
<input type="password" name="password" />
</label>
</div>
<div>
<input type="submit" value="Log in" />
</div>
</form>
For details, reference
I've to set reactive form validation for a form where the inputs are made with data looping :
my form builder would look like this :
constructor(private formBuilder: FormBuilder) {
this.userForm = this.formBuilder.group({
'inputOne': ['', [Validators.required]],
'inputOne': ['', [Validators.required]],
...
'inputN': ['', [Validators.required]]
});
}
and my template would look like this :
<form [formGroup]="userForm">
<div class="form-group" *ngFor="let item of items; let i=index;">
<label for="lastName">{{item.name}}</label>
<input class="form-control" name="lastName" id="lastName" type="text" [formControlName]="item.name">
</div>
</form>
where items are loaded dynamically from my my backend
How to populate controls dynamically in Angular reactive forms?
sounds like you want a form array here, not a group...
constructor(private formBuilder: FormBuilder) {
// build an empty form array
this.userForm = this.formBuilder.array([]);
}
// call this whenever you need to add an item to your array
private addItem(item) {
// build your control with whatever validators / value
const fc = this.formBuilder.control(i.lastName, [Validators.required]);
this.userForm.push(fc); // push the control to the form
}
// call this function whenever you need to reset your array
private resetFormArray(items) {
this.userForm.clear(); // clear the array
items.forEach(i => this.addItem(i)); // add the items
}
<form [formGroup]="userForm">
<div class="form-group" *ngFor="let item of items; let i=index;">
<label for="lastName">{{item.name}}</label>
<input class="form-control" name="lastName" id="lastName" type="text" [formControlName]="i">
</div>
</form>
notice you're using the index for the form control name here
I am creating a simple React app that allows the user to add contacts to a master list. My components state looks like this:
state = {
contact: {
fname: "",
lname: "",
address: "",
phone: "",
email: "",
id: ""
}
};
So far, I have been able to effectively add properties such as name, email, etc using values sent from inputs.
this.setState({
contact: {
...this.state.contact,
[e.target.name]: e.target.value // e is an event sent from an input
}
});
};
That's all fine and dandy, but I need each contact to have a unique ID. I have been trying to tack on the ID to the contact object before I send it up the component hierarchy.
const unId = new Date().getTime();
this.setState({
contact: {
...this.state.contact,
id: unId
}
});
This code is producing some strange issues and I'm not sure why. When I run it for the first time, the id is generated, but not assigned to the contact. The second time I run it, the id produced the first time is assigned to the second contact. In other words, the id property is updating the state later one cycle behind the time it should.
I'm not very familiar with synchronicity or anything in React, so I would really appreciate any help I could get.
Does this example help you? If not, can you comment what do you exactly want?
https://codesandbox.io/s/priceless-mccarthy-7i69e
import React, { Component } from "react";
class App extends Component {
state = {
contact: {
fname: "",
lname: "",
address: "",
phone: "",
email: "",
id: new Date().getTime()
}
};
handleInputChange = e => {
this.setState({
contact: {
...this.state.contact,
[e.target.name]: e.target.value
}
});
};
handleSubmit = e => {
console.log(this.state);
};
render() {
const { fname, lname, address, phone, email, id } = this.state.contact;
return (
<div>
<label>fname</label>
<input
type="text"
value={fname}
name="fname"
onChange={this.handleInputChange}
/>
<br />
<label>lname</label>
<input
type="text"
value={lname}
name="lname"
onChange={this.handleInputChange}
/>
<br />
<label>address</label>
<input
type="text"
value={address}
name="address"
onChange={this.handleInputChange}
/>
<br />
<label>phone</label>
<input
type="text"
value={phone}
name="phone"
onChange={this.handleInputChange}
/>
<br />
<label>email</label>
<input
type="text"
value={email}
name="email"
onChange={this.handleInputChange}
/>
<br />
<label>id</label>
<input
type="text"
value={id}
name="id"
onChange={this.handleInputChange}
/>
<button type="button" onClick={this.handleSubmit}>
Submit
</button>
<hr />
{
JSON.stringify(this.state)
}
</div>
);
}
}
export default App;
The ID isn't created until the next render because the component's state isn't initialized when it gets created. You need to initialize its state.
Either in a class constructor
constructor() {
super();
this.state = {
id: new Date().getTime()
}
}
or a state attribute
state = {
id: new Date().getTime()
}
Here is a working codepen example
I am trying to add data to my collection inside an object from my TypeScript code. I am successfully getting the name and type from the view in the html binding So I was wondering how I can edit the this.newList.socialData model via code before adding the new list to my database.
Html:
<mat-form-field>
<input matInput placeholder="List Name" [(ngModel)]="newList.name" name="name" type="text" class="form-control rounded-0" required>
</mat-form-field>
<mat-form-field>
<input matInput placeholder="List Type" [(ngModel)]="newList.type" name="type" type="text" class="form-control rounded-0" required>
</mat-form-field>
<button (click)="addList()" type="button" class="btn btn-primary float-right">Create New</button>
Declaration:
newList: List = {} as List
TypeScript:
addList() {
let tt = {} as SocialData;
//tt.socialId = 'jj'
//this.newList = {} as List;
// I would like to add test data to socialData here
this.newList.socialData.push(tt);
this.listService.addList(this.newList)
.subscribe(
res => {
this.fetchLists();
},
err => console.log(err)
)
}
Model:
export class List {
name: String;
type: String;
inputValue: String;
socialData: [SocialData]
}
export class SocialData {
socialId: String
}
I guess to just want to add a new item to the socialData array.
You need to make 2 changes in your code:
1. Declaration
export class List {
name: String;
type: String;
inputValue: String;
// socialData: [SocialData]; // This is wrong
socialData: SocialData[]; // declares a type of array
constructor() {
this.socialData = []; // Initialize the array to empty
}
}
2. Creating the instance:
// let tt = {} as SocialData; // This will just cast an empty object into List
newList: List = new List(); // Will create an instance of List
Just make these changes and the code should work.
I have a GET XMLHttpRequest done against a service giving me as response the following JSON payload :
{_id: "5aa1358d32eba9e34dd9f280", source: "Avengers", target: "Iron Man", enemies: "Actor"}
I have the object hero in src/app :
export class Hero {
_id: number;
target: string;
source: string;
enemies: string;
}
The code in the TS component to get the data :
ngOnInit(): void {
this.route.params.subscribe((params: Params) => {
const id = params['id'];
this.getHero(id);
});
}
getHero(id: string): void {
this.heroService.getHero(id)
.subscribe(hero => this.hero = hero);
}
Code of the method in the service:
getHero(id: string): Observable<Hero> {
// Todo: send the message _after_ fetching the hero
this.messageService.add(`HeroService: fetched hero id=${id}`);
const url = `${this.herodetailUrl}/${id}`;
return this.http.get<Hero>(url);
}
Here is my code in the view :
<label>name:
<input [(ngModel)]="hero.target" (input)="hero.target =
$event.target.value" placeholder="target" /> </label>
<label>group:
<input [(ngModel)]="hero.source" (input)="hero.source =
$event.target.value" placeholder="source" /> </label>
<label>enemies:
<input [(ngModel)]="hero.enemies" (input)="hero.enemies =
$event.target.value" placeholder="enemies" /> </label>
The binding doesn't work
In my form elements i see the name of my placeholder. Not the value
Regards
Laurent
<label>name:
<input [(ngModel)]="hero.target" (input)="hero.target =
$event.target.value" placeholder="target" /> </label>
<label>group:
<input [(ngModel)]="hero.source" (input)="hero.source =
$event.target.value" placeholder="source" /> </label>
<label>enemies:
<input [(ngModel)]="hero.enemies" (input)="hero.enemies =
$event.target.value" placeholder="enemies" /> </label>
You don't need (input)="hero.target = $event.target.value" when you use as this [(ngModel)]
The JSON is an array of an object, So i modified my view as is : {{ hero[0].target | uppercase }} and so on for each form component. Now it works as is :
> <div><span>id: </span>{{hero[0]._id}}</div> <div>
> <label>name:
> <input [(ngModel)]="hero[0].target" placeholder="target"/>
> </label>
> <label>group:
> <input [(ngModel)]="hero[0].source" placeholder="source"/>
> </label>
> <label>enemies:
> <input [(ngModel)]="hero[0].enemies" placeholder="enemies"/>
> </label>
Now the binding works