While working on Angular 2 form submit I run into a problem. When I create an object inside a component everything works well and my form gets submit via post method. But when I am using an object from a class outside the component my form sends a get request with url http://localhost:4200/blog?title=sss&content=ssssss
Does anyone know why this is happening?
Template:
<form (ngSubmit)="onSubmit()" #f="ngForm">
<!-- <form #f="ngForm" (ngSubmit)="onSubmit(f)">-->
<div class="form-group">
<label for="title">Tytuł</label>
<textarea class="form-control" id="title" rows="1"
ngModel name = "title" required minlength="3" #title="ngModel"></textarea>
<span class="help-block" *ngIf="!title.valid && title.touched">Wprowadzono za krótki tekst (minum to 3 znaki).</span>
<label for="content">Zawartość:</label>
<textarea class="form-control" id="content" rows="3"
ngModel name = "content" required minlength="3" #content="ngModel"></textarea>
<span class="help-block" *ngIf="!content.valid && content.touched">Wprowadzono za krótki tekst (minum to 3 znaki).</span>
</div>
<button type="submit" class="btn btn-primary"
[disabled] ="!f.valid"
>Wyślij</button>
</form>
Component:
import {Component, OnInit, ViewChild} from '#angular/core';
import {NgForm} from "#angular/forms";
import {Blog} from "../../shared/blog";
import {BlogService} from "../../services/blog.service";
#Component({
selector: 'app-blog-form',
templateUrl: './blog-form.component.html',
styleUrls: ['./blog-form.component.css']
})
export class BlogFormComponent implements OnInit {
#ViewChild('f') form: NgForm;
errorMessage: string;
/* this works well
blog = {
title: '',
content: '',
dateCreated: ''
}*/
//this doesn't work
blog: Blog;
ngOnInit(){}
onSubmit(){
this.blog.title = this.form.value.title;
this.blog.content = this.form.value.content;
}
}
The Blog class. I tried both this:
export class Blog {
constructor(public title = '', public content = '', public dateCreated = ''){}}
And this:
export class Blog {
constructor(public title : string, public content : string, public dateCreated : string){}}
Thanks for any help :)
I am not sure why this is happening but try not using this.form.value.
onSubmit(){
this.blog.title = this.form.title;
this.blog.content = this.form.content;
console.log(this.blog);
}
Use of value posts back your page. Now this code should work.
Related
I'm having some trouble with displaying error messages for the maxlength attribute in Angular.
Problem
Since the maxlength attribute don't allow more characters than the specified amount, I'm having trouble displaying my error message. Is there any way to turn of the default behavior (allow the user to type more characters), in order to display my error message.
Code for textarea
<textarea maxlength="10"
[(ngModel)]="title.value"
#title="ngModel"></textarea>
Code for Angular validation
<div *ngIf="title.errors && (title.dirty || title.touched)"
class="alert alert-danger">
<div [hidden]="!title.errors.maxlength">
Only 10 characters allowed.
</div>
</div>
If you want me to provide any additional information, please let me know.
you can work with Reactive forms to validate properly your form,
here is a simple example how to use reactive forms :
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
#Component({
selector: 'title-form',
template: `
<form novalidate [formGroup]="myForm">
<label>
<span>Full title</span>
<input type="text" placeholder="title.." formControlName="title">
</label>
<div*ngIf="myForm.controls['title'].touched && myForm.get('title').hasError('required')">
Name is required
</div>
<div *ngIf="myForm.controls['title'].touched && myForm.controls['title'].hasError('maxlength')">
Maximum of 10 characters
</div>
</form>
`
})
export class TitleFormComponent implements OnInit {
myForm: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.myForm = this.fb.group({
title: ['', [Validators.required, Validators.maxLength(10)]],
});
}
}
hope it helps u :)
You can achieve it by setting the condition directly on the length of the input. A span tag with *ngIf can show/hide the error message:
HTML
<textarea class="form-control" id="title"
type="number" name="title" [(ngModel)]="titleModel"></textarea>
<span style="color:red" *ngIf="titleModel?.length > 10">
10 max
</span>
Class:
...
titleModel = 'I have more than 10 characters'
...
DEMO
How to validate a form in angular 4 by clicking on external link (ie out side from tag). If the form is valid do some actions with form data else show validation messages. If form is valid I don't want to submit the form just need to get the form field values.
This is my answer post to another question:
The easy way is to use reactive forms, like this:
Code:
import {ReactiveForm, FormBuilder, Validators} from '#angular/form';
export class SignupFormComponent implements OnInit {
userForm: FormGroup;
firstName: string;
constructor(private _formBuilder:FormBuilder){}
ngOnInit() {
this.userForm = this._formBuilder.group({
'firstName': ['',[Validators.required,Validators.minLength(5)]]
});
}
onSubmit() {
console.log(this.firstName);
}
}
HTML:
<form [formGroup]="userForm" (ngSubmit)="onSubmit()" name="userForm">
<div class="form-group">
<label>First Name</label>
<input type="text" [(ngModel)]="firstName" class="form-control" formControlName="firstName">
<p *ngIf="userForm.controls.firstName.invalid && (userForm.controls.firstName.dirty || userForm.controls.firstName.touched)"> Error message </p>
</div>
<button type="submit" class="btn btn-primary" [disabled]="userForm.invalid">Submit </button>
</form>
I'm trying achieve a nested form with validation in Angular 2, I've seen posts and followed the documentation but I'm really struggling, hope you can point me in the right direction.
What I am trying to achieve is having a validated form with multiple children components. These children components are a bit complex, some of them have more children components, but for the sake of the question I think we can attack the problem having a parent and a children.
What am I trying to accomplish
Having a form that works like this:
<div [formGroup]="userForm" novalidate>
<div>
<label>User Id</label>
<input formControlName="userId">
</div>
<div>
<label>Dummy</label>
<input formControlName="dummyInput">
</div>
</div>
This requires having a class like this:
private userForm: FormGroup;
constructor(private fb: FormBuilder){
this.createForm();
}
private createForm(): void{
this.userForm = this.fb.group({
userId: ["", Validators.required],
dummyInput: "", Validators.required]
});
}
This works as expected, but now I want to decouple the code, and put the "dummyInput" functionality in a separate, different component. This is where I get lost. This is what I tried, I think I'm not far from getting the answer, but I'm really out of ideas, I'm fairly new to the scene:
parent.component.html
<div [formGroup]="userForm" novalidate>
<div>
<label>User Id</label>
<input formControlName="userId">
</div>
<div>
<dummy></dummy>
</div>
</div>
parent.component.ts
private createForm(): void{
this.userForm = this.fb.group({
userId: ["", Validators.required],
dummy: this.fb.group({
dummyInput: ["", Validators.required]
})
});
children.component.html
<div [formGroup]="dummyGroup">
<label>Dummy Input: </label>
<input formControlName="dummyInput">
</div>
children.component.ts
private dummyGroup: FormGroup;
I know something is not right with the code, but I'm really in a roadblock. Any help would be aprreciated.
Thanks.
you can add an Input in your children component to pass the FormGroup to it.and use FormGroupName to pass the name of your FormGroup :)
children.component.ts
#Input('group');
private dummyGroup: FormGroup;
parent.component.html
<div [formGroup]="userForm" novalidate>
<div>
<label>User Id</label>
<input formControlName="userId">
</div>
<div formGroupName="dummy">
<dummy [group]="userForm.controls['dummy']"></dummy>
</div>
</div>
Not going to lie, don't know how I didn't find this post earlier.
Angular 2: Form containing child component
The solution is to bind the children component to the same formGroup, by passing the formGroup from the parent to the children as an Input.
If anyone shares a piece of code to solve the problem in other way, I'll gladly accept it.
The main idea is that you have to treat the formGroup and formControls as variables, mainly javascript objects and arrays.
So I'll put some code in to make my point. The code below is somewhat like what you have. The form is constructed dynamically, just that it is split into sections, each section containing its share of fields and labels.
The HTML is backed up by typescript classes. Those are not here as they do not have much special. Just the FormSchemaUI, FormSectionUI and FormFieldUI are important.
Treat each piece of code as its own file.
Also please take note that formSchema: FormSchema is a JSON object that I receive from a service. Any properties of the UI classes that you do not see defined are inherited from their base Data clases. Those are not presented here.
The hierarchy is: FormSchema contains multiple sections. A section contains multiple fields.
<form (ngSubmit)="onSubmit()" #ciRegisterForm="ngForm" [formGroup]="formSchemaUI.MainFormGroup">
<button kendoButton (click)="onSubmit(ciRegisterForm)" [disabled]="!canSubmit()"> Register {{registerPageName}} </button>
<br /><br />
<app-ci-register-section *ngFor="let sectionUI of formSchemaUI.SectionsUI" [sectionUI]="sectionUI">
</app-ci-register-section>
<button kendoButton (click)="onSubmit(ciRegisterForm)" [disabled]="!canSubmit()"> Register {{registerPageName}} </button>
</form>
=============================================
<div class="row" [formGroup]="sectionUI.MainFormGroup">
<div class="col-md-12 col-lg-12" [formGroupName]="sectionUI.SectionDisplayId">
<fieldset class="section-border">
<legend class="section-border">{{sectionUI.Title}}</legend>
<ng-container *ngFor='let fieldUI of sectionUI.FieldsUI; let i=index; let even = even;'>
<div class="row" *ngIf="even">
<ng-container>
<div class="col-md-6 col-lg-6" app-ci-field-label-tuple [fieldUI]="fieldUI">
</div>
</ng-container>
<ng-container *ngIf="sectionUI.Fields[i+1]">
<div class="col-md-6 col-lg-6" app-ci-field-label-tuple [fieldUI]="sectionUI.FieldsUI[i+1]">
</div>
</ng-container>
</div>
</ng-container>
</fieldset>
</div>
</div>
=============================================
{{fieldUI.Label}}
=============================================
<ng-container>
<div class="row">
<div class="col-md-4 col-lg-4 text-right">
<label for="{{fieldUI.FieldDisplayId}}"> {{fieldUI.Label}} </label>
</div>
<div class="col-md-8 col-lg-8">
<div app-ci-field-edit [fieldUI]="fieldUI" ></div>
</div>
</div>
</ng-container>
=============================================
<ng-container [formGroup]="fieldUI.ParentSectionFormGroup">
<ng-container *ngIf="fieldUI.isEnabled">
<ng-container [ngSwitch]="fieldUI.ColumnType">
<input *ngSwitchCase="'HIDDEN'" type="hidden" id="{{fieldUI.FieldDisplayId}}" [value]="fieldUI.Value" />
<ci-field-textbox *ngSwitchDefault
[fieldUI]="fieldUI"
(valueChange)="onValueChange($event)"
class="fullWidth" style="width:100%">
</ci-field-textbox>
</ng-container>
</ng-container>
</ng-container>
=============================================
export class FormSchemaUI extends FormSchema {
SectionsUI: Array<FormSectionUI>;
MainFormGroup: FormGroup;
static fromFormSchemaData(formSchema: FormSchema): FormSchemaUI {
let formSchemaUI = new FormSchemaUI(formSchema);
formSchemaUI.SectionsUI = new Array<FormSectionUI>();
formSchemaUI.Sections.forEach(section => {
let formSectionUI = FormSectionUI.fromFormSectionData(section);
formSchemaUI.SectionsUI.push(formSectionUI);
});
formSchemaUI.MainFormGroup = FormSchemaUI.buildMainFormGroup(formSchemaUI);
return formSchemaUI;
}
static buildMainFormGroup(formSchemaUI: FormSchemaUI): FormGroup {
let obj = {};
formSchemaUI.SectionsUI.forEach(sectionUI => {
obj[sectionUI.SectionDisplayId] = sectionUI.SectionFormGroup;
});
let sectionFormGroup = new FormGroup(obj);
return sectionFormGroup;
}
}
=============================================
export class FormSectionUI extends FormSection {
constructor(formSection: FormSection) {
this.SectionDisplayId = 'section' + this.SectionId.toString();
}
SectionDisplayId: string;
FieldsUI: Array<FormFieldUI>;
HiddenFieldsUI: Array<FormFieldUI>;
SectionFormGroup: FormGroup;
MainFormGroup: FormGroup;
ParentFormSchemaUI: FormSchemaUI;
static fromFormSectionData(formSection: FormSection): FormSectionUI {
let formSectionUI = new FormSectionUI(formSection);
formSectionUI.FieldsUI = new Array<FormFieldUI>();
formSectionUI.HiddenFieldsUI = new Array<FormFieldUI>();
formSectionUI.Fields.forEach(field => {
let fieldUI = FormFieldUI.fromFormFieldData(field);
if (fieldUI.ColumnType != 'HIDDEN')
formSectionUI.FieldsUI.push(fieldUI);
else formSectionUI.HiddenFieldsUI.push(fieldUI);
});
formSectionUI.SectionFormGroup = FormSectionUI.buildFormSectionFormGroup(formSectionUI);
return formSectionUI;
}
static buildFormSectionFormGroup(formSectionUI: FormSectionUI): FormGroup {
let obj = {};
formSectionUI.FieldsUI.forEach(fieldUI => {
obj[fieldUI.FieldDisplayId] = fieldUI.FieldFormControl;
});
let sectionFormGroup = new FormGroup(obj);
return sectionFormGroup;
}
}
=============================================
export class FormFieldUI extends FormField {
constructor(formField: FormField) {
super();
this.FieldDisplayId = 'field' + this.FieldId.toString();
this.ListItems = new Array<SelectListItem>();
}
public FieldDisplayId: string;
public FieldFormControl: FormControl;
public ParentSectionFormGroup: FormGroup;
public MainFormGroup: FormGroup;
public ParentFormSectionUI: FormSectionUI;
public ValueChange: EventEmitter<any> = new EventEmitter<any>();
static buildFormControl(formFieldUI:FormFieldUI): FormControl {
let nullValidator = Validators.nullValidator;
let fieldKey: string = formFieldUI.FieldDisplayId;
let fieldValue: any;
switch (formFieldUI.ColumnType) {
default:
fieldValue = formFieldUI.Value;
break;
}
let isDisabled = !formFieldUI.IsEnabled;
let validatorsArray: ValidatorFn[] = new Array<ValidatorFn>();
let asyncValidatorsArray: AsyncValidatorFn[] = new Array<AsyncValidatorFn>();
let formControl = new FormControl({ value: fieldValue, disabled: isDisabled }, validatorsArray, asyncValidatorsArray);
return formControl;
}
}
To get a reference to the parent form simply use this (maybe not available in Angular 2. I've tested it with Angular 6):
TS
import {
FormGroup,
ControlContainer,
FormGroupDirective,
} from "#angular/forms";
#Component({
selector: "app-leveltwo",
templateUrl: "./leveltwo.component.html",
styleUrls: ["./leveltwo.component.sass"],
viewProviders: [
{
provide: ControlContainer,
useExisting: FormGroupDirective
}
]
})
export class NestedLevelComponent implements OnInit {
//form: FormGroup;
constructor(private parent: FormGroupDirective) {
//this.form = form;
}
}
HTML
<input type="text" formControlName="test" />
import { Directive } from '#angular/core';
import { ControlContainer, NgForm } from '../../../node_modules/#angular/forms';
#Directive({
selector: '[ParentProvider]',
providers: [
{
provide: ControlContainer,
useFactory: function (form: NgForm) {
return form;
},
deps: [NgForm]
}`enter code here`
]
})
export class ParentProviderDirective {
constructor() { }
}
<div ParentProvider >
for child
</div>
An alternative to the FormGroupDirective (as described in #blacksheep's answer) is the use of ControlContainer like so:
import { FormGroup, ControlContainer } from "#angular/forms";
export class ChildComponent implements OnInit {
formGroup: FormGroup;
constructor(private controlContainer: ControlContainer) {}
ngOnInit() {
this.formGroup = <FormGroup>this.controlContainer.control;
}
The formGroup can be set in the direct parent or further up (parent's parent, for example). This makes it possible to pass a from group over various nested components, without the need for a chain of #Input()s to pass the formGroup along. In any parent set the formGroup to make it available via ControlContainer in the child:
<... [formGroup]="myFormGroup">
I have a form with username and password, when a user hits the submit button, a error message is displayed stating if one or both fields are blank, I'm trying to add a route to the button so it takes them to the home page, however, when I add a route to the button, it ignores the validation as aspect and will work if fields are blank. I've tried to disable the button so that it's only enabled when the form is valid but I'm getting a bit confused between ngForm and ngFormModel
Thanks
This is my html form
<form [ngFormModel]="loginForm" (ngSubmit)="loginUser(loginForm.value)">
<div class="form-group" [ngClass]="{ 'has-error' : !username.valid && submitAttempt }">
<label class="control-label" for="username">Username</label>
<em *ngIf="!username.valid && submitAttempt">Required</em>
<input id="username" type="text" class="form-control" placeholder="Username" ngControl="username">
</div>
<div class="form-group" [ngClass]="{ 'has-error' : !password.valid && submitAttempt }">
<label class="control-label" for="password">Password</label>
<em *ngIf="password.hasError('required') && submitAttempt">Required</em>
<em *ngIf="password.hasError('minlength') && submitAttempt">Must be at least 8 characters</em>
<input id="password" type="password" class="form-control" placeholder="Password" ngControl="password">
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Register</button>
</div>
</form>
And this is my component
import {Component, EventEmitter} from "angular2/core";
import {Router} from "angular2/router";
import {ROUTER_DIRECTIVES} from "angular2/router";
import {Control, ControlGroup, FormBuilder, Validators, FORM_DIRECTIVES} from 'angular2/common';
//import {UsernameValidators} from './usernameValidators';
#Component({
selector: 'LoginHTML',
templateUrl: 'dev/LoginComponents/login-form.component.html',
directives : [FORM_DIRECTIVES]
})
export class LoginHTMLComponent{
loginForm: ControlGroup;
username: Control;
password: Control;
submitAttempt: boolean = false;
form: ControlGroup;
constructor( private builder: FormBuilder, private _router: Router) {
this.username = new Control('', Validators.required)
this.password = new Control('', Validators.compose([Validators.required, Validators.minLength(8)]))
this.loginForm = builder.group({
username: this.username,
password: this.password
});
}
loginUser(user) {
this.submitAttempt = true;
this._router.navigate(['Dashboard']);
}
}
You should be free to do anything with your code, so the form won't try to intervene your loginUser function to navigate to Dashboard. But it does provide you information about form validity that you can make use of:
loginUser(user) {
this.submitAttempt = true;
if (this.loginForm.valid) {
this._router.navigate(['Dashboard']);
}
}
i try to make a validation for my input fields.
this is a piece of code that I used:
DepartmentComponent
import {
FORM_DIRECTIVES,
FormBuilder,
ControlGroup,
Validators ,
AbstractControl
} from 'angular2/common';
#Component({
selector: 'my-departments',
providers: [HTTP_PROVIDERS, DepartmentService],
directives: [FORM_DIRECTIVES, Alert],
styleUrls: ['app/department.component.css'],
templateUrl: 'app/department.component.html',
pipes:[SearchPipe]
})
export class DepartmentComponent implements OnInit {
myForm: ControlGroup;
departmentName: AbstractControl;
departmentLocation: AbstractControl;
constructor(private _router: Router, private _departmentService: DepartmentService, fb: FormBuilder) {
this.myForm = fb.group({
'departmentName': ['', Validators.required],
'departmentLocation': ['', Validators.required]
});
this.departmentName= this.myForm.controls['departmentName'];
this.departmentLocation= this.myForm.controls['departmentLocation'];
}
DepartmentComponent template
<form [ngFormModel]="myForm"
(ngSubmit)="addDepartment(newItem)" [hidden]="!showAddView" align="center">
<div>
<label for="editAbrv">Department name:</label><br>
<input type="text" [(ngModel)]="newItem.departmentName" [ngFormControl]="myForm.controls['departmentName']" >
<div *ngIf="departmentName.hasError('required')" class="ui error message"><b style="color:red;">Name is required</b></div>
</div>
<div>
<label for="editAbrv">Department Location:</label><br>
<input type="text" [(ngModel)]="newItem.departmentLocation" [ngFormControl]="myForm.controls['departmentLocation']" >
<div *ngIf="departmentLocation.hasError('required')" class="ui error message"><b style="color:red;">Location is required</b></div>
</div>
<div>
<button type="submit" class="ui button">Submit</button>
<button><a href="javascript:void(0);" (click)="showHide($event)" >
Cancel
</a></button>
</div>
</form>
The problem is that I got an error: .hasError is not a function. hasError function is in my html file (which you can see) I really don't see where I'm wrong. I did everything like is described in tutorial, but can't figure out why is this happen. Thanks for advice!
you should use *ngIf="myForm.controls['departmentLocation'].hasError('required')"
or any better luck with
this.departmentName= this.myForm.controls.find('departmentName'); ?
In place of hasError you should use errors
i.e it should be
myForm.controls['departmentLocation'].errors['required']
i.e with *ngIf
*ngIf="myForm.controls['departmentLocation'].errors['required']"
This is similar to another answer I've provided here: Form Builder with hasError() for validation throws an error of ERROR TypeError: Cannot read property 'hasError' of undefined.
The gist is that TypeScript getter's can be used to solve this in a clean way.
In your component class:
get departmentLocation() {
return this.myForm.get( 'departmentLocation' );
}
In your component template:
<input type="text" formControlName="departmentLocation">
<p class="ui error message" *ngIf="departmentLocation.hasError('required')">Department location is required</p>
Also important to note that using ngModel with Reactive Forms in Angular 6+ is depracated, so it's been removed from the examples above.
i was getting compilation error for using
loginForm.get('email').hasError('required')
This fixed it
loginForm.get('email')?.hasError('required')
use it like this
<mat-error *ngIf="!loginForm.get('password')?.hasError('required')
&& loginForm.get('password')?.hasError('whitespace')">