Handling Angular2 - 4 errors with nested form groups in reactive forms - validation

Guys I need some help with Angular 4 reactive forms. I have nested form-groups also works fine except Validation. But my app is crashing when I try to add some handling validation in my html markup.
my component code looks like:
createForm() {
let options: any = {};
for (let option of this.annoucementOptions) {
options[option.title] = new FormControl(false);
}
let optionsFormGroup: FormGroup = new FormGroup(options);
this.annoucementForm = this.fb.group({
address: this.fb.group({
country: ['', [Validators.maxLength(50)]],
state: ['', [Validators.maxLength(50)]],
city: ['', [Validators.required, Validators.maxLength(50)]],
street: ['', [Validators.required, Validators.maxLength(50)]],
apartment: ['', [Validators.required, Validators.maxLength(50)]],
}),
description: this.fb.group({
title: ['', [Validators.required, Validators.maxLength(80)]],
shortDescription: [''],
livingPlaces: [''],
room: [''],
options: optionsFormGroup
}),
priceBlock: this.fb.group({
price: ['', [Validators.required, Validators.maxLength(80)]],
type: ['', [Validators.required, Validators.maxLength(80)]],
}),
});
}
and this is a piece of my template code:
<form class="form form-lg form-def" *ngIf="annoucementForm" (ngSubmit)="saveDetails(annoucementForm)" name="annoucementForm" [formGroup]="annoucementForm">
<div class="form-block" formGroupName="address">
<h2 class="form-block-heading flag-label primary">Address</h2>
<ul class="row form-list">
<li class="col-md-6 col-lg-4 form-list-item">
<md-input-container class="d-block">
<input type="text" mdInput placeholder="*Country" formControlName="country">
</md-input-container>
<div class="form-error text-danger"
*ngIf="annoucementForm.get('country').touched "
>
<p *ngIf="annoucementForm.get('country').hasError('maxlength')">
*This field be more than 35 characters long.
</p>
</div>
</li>
</ul>

Use
annoucementForm.get('address.country')
or
annoucementForm.get(['address', 'country'])
instead of
annoucementForm.get('country')

Related

Getting error while using Angular Form Array

Why am I getting this error, and how to get it fixed:
TS2740: Type 'AbstractControl<any, any>' is missing the following properties from type 'FormGroup': controls, registerControl, addControl, removeControl, and 2 more.
At this point it is giving error in my html:
my html:
<button type="button" (click)="addName()">Add Name</button>
<div formArrayName="names">
<div *ngFor="let nm of getNames.controls">
<div [formGroup]="nm"> //THIS LINE IS GIVING ERROR
<input formControlName="fname" type="text" placeholder="First Name">
<input formControlName="lname" type="text" placeholder="Last Name">
</div>
</div>
</div>
<div>
<button type="submit">Submit</button>
</div>
my ts:
export class TestComponent implements OnInit {
constructor(private fb: FormBuilder) { }
form = this.fb.group({
names: this.fb.array([])
});
get getNames(){
return this.form.controls["names"] as FormArray;
}
addName(){
const newName = this.fb.group({
fname: ['', Validators.required],
lname: ['', Validators.required]
});
this.getNames.push(newName);
}
onSubmit():void{
console.log(this.form.value)
}
ngOnInit(): void {
}
}
I got it resolved by using $any in html as below.
<div *ngFor="let nm of $any(getName).controls; let i=index">
hi i us this way try it
<div *ngFor="let nm of getName.controls; let i=index" [formGroupName]="i">

How to use input type hidden in Angular FormBuilder?

I am running an application using Angular CLI 9.0.7. In this application I use FormBuilder to with two input fields of type hidden. When I run the application I get the message:
Error: Cannot find control with name: 'codigoIbgeMunicipioForm'
How can I do to use input hidden in my form?
This is my component source code where I defined the input hidden fields.
private adicionarEnderecoFormGroup(): FormGroup {
return this.formBuilder.group({
cepForm: ['', [Validators.required, CepValidator.cepValido]],
numeroEnderecoForm: ['', Validators.required],
complementoForm: [''],
tipoLogradouroForm: ['', Validators.required],
logradouroForm: ['', Validators.required],
bairroForm: ['', Validators.required],
cidadeForm: ['', Validators.required],
estadoEnderecoForm: ['', Validators.required],
codigoIbgeMunicipioForm: [''], // <- look the field id defined here
paisForm: ['BR'], // <- this field is hidden type too
}, { validators: LojistaEnderecoValidator.enderecoDuplicado(this.enderecos) });
}
And this my Html file
<div FormGroupName="enderecoFormGroup">
<input formControlName="codigoIbgeMunicipioForm" id="codigoIbgeMunicipioForm" type="hidden" />
<input formControlName="paisForm" id="paisForm" type="hidden" />
I didn't find what was wrong, so I removed the component from the project, created the component again, pasted the code back, compiled and it worked ...
Strange, but it worked.

Vue 2 Imported Component Property or Method not defined

I'm attempting to nest some components - ultimately I would like to have a component which displays Posts, with a PostItem component used to render each post. Inside the PostItem, I want a list of related comments, with CommentItem to render each comment. I have the posts displayed using the PostItem with no errors, but as soon as I add the Comments I get errors. So to simplify, I've pulled the CommentsList component out and I'm just trying to display it on the page, manually loading in all comments - it's an exact copy of PostsList, except with comment replacing post, but it generates an error:
[Vue warn]: Property or method "commment" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
found in
---> <Comment> at resources/assets/js/components/CommentItem.vue
<CommentsList> at resources/assets/js/components/CommentsList.vue
<Root>
CommentsList.vue
<template>
<div class="media-list media-list-divided bg-lighter">
<comment v-for="comment in comments"
:key="comment.id"
:comment="comment">
</comment>
</div>
</template>
<script>
import comment from './CommentItem'
export default {
components: { comment },
data: function() {
return {
comment: {
id: 1,
content: "",
edited: false,
created_at: new Date().toLocaleString(),
user: {
id: 1,
name: '',
}
},
comments: [
{
id: 1,
content: "",
edited: false,
created_at: new Date().toLocaleString(),
user: {
id: 1,
name: '',
}
}
]
};
},
created() {
this.fetchCommentsList();
},
methods: {
fetchCommentsList() {
axios.get('/api/comments').then((res) => {
//alert(JSON.stringify(res.data[0], null, 4));
this.comments = res.data;
});
},
createComment() {
axios.post('api/comments', {content: this.comment.content, user_id: Laravel.userId, vessel_id: Laravel.vesselId })
.then((res) => {
this.comment.content = '';
// this.comment.user_id = Laravel.userId;
// this.task.statuscolor = '#ff0000';
this.edit = false;
this.fetchCommentsList();
})
.catch((err) => console.error(err));
},
deleteComment(id) {
axios.delete('api/comments' + id)
.then((res) => {
this.fetchCommentsList()
})
.catch((err) => console.error(err));
},
}
}
</script>
CommentItem.vue
<template>
<div>
<a class="avatar" href="#">
<img class="avatar avatar-lg" v-bind:src="'/images/user' + comment.user.id + '-160x160.jpg'" alt="...">
</a>
<div class="media-body">
<p>
<strong>{{ commment.user.name }}</strong>
</p>
<p>{{ comment.content }}</p>
</div>
</div>
</template>
<script>
export default {
name: 'comment',
props: {
comment: {
required: true,
type: Object,
default: {
content: "",
id: 1,
user: {
name: "",
id: 1
}
}
}
},
data: function() {
return {
}
}
}
</script>
I'm pretty new to Vue - I've read so many tutorials and have been digging through the documentation and can't seem to figure out why its working for me with my PostsList component thats an exact copy. It also seems odd that I need both comment and comments in the data return - something that my working Posts version requires.
I'll drop in my working Posts components:
PostsList.vue
<template>
<div>
<div v-if='posts.length === 0' class="header">There are no posts yet!</div>
<post v-for="post in posts"
:key="post.id"
:post="post">
</post>
<form action="#" #submit.prevent="createPost()" class="publisher bt-1 border-fade bg-white">
<div class="input-group">
<input v-model="post.content" type="text" name="content" class="form-control publisher-input" autofocus>
<span class="input-group-btn">
<button type="submit" class="btn btn-primary">New Post</button>
</span>
</div>
<span class="publisher-btn file-group">
<i class="fa fa-camera file-browser"></i>
<input type="file">
</span>
</form>
</div>
</template>
<script>
// import CommentsManager from './CommentsManager.vue';
import post from './PostItem.vue';
export default {
components: {
post
},
data: function() {
return {
post: {
id: 1,
content: "",
edited: false,
created_at: new Date().toLocaleString(),
user: {
id: 1,
name: '',
}
},
posts: [
{
id: 1,
content: "",
edited: false,
created_at: new Date().toLocaleString(),
user: {
id: 1,
name: '',
}
}
]
};
},
created() {
this.fetchPostsList();
},
methods: {
fetchPostsList() {
axios.get('/api/posts').then((res) => {
//alert(JSON.stringify(res.data[0], null, 4));
this.posts = res.data;
});
},
createPost() {
axios.post('api/posts', {content: this.post.content, user_id: Laravel.userId, vessel_id: Laravel.vesselId })
.then((res) => {
this.post.content = '';
// this.post.user_id = Laravel.userId;
// this.task.statuscolor = '#ff0000';
this.edit = false;
this.fetchPostsList();
})
.catch((err) => console.error(err));
},
deletePost(id) {
axios.delete('api/posts' + id)
.then((res) => {
this.fetchPostsList()
})
.catch((err) => console.error(err));
},
}
}
</script>
PostItem.vue
<template>
<div class="box">
<div class="media bb-1 border-fade">
<img class="avatar avatar-lg" v-bind:src="'/images/user' + post.user.id + '-160x160.jpg'" alt="...">
<div class="media-body">
<p>
<strong>{{ post.user.name }}</strong>
<time class="float-right text-lighter" datetime="2017">24 min ago</time>
</p>
<p><small>Designer</small></p>
</div>
</div>
<div class="box-body bb-1 border-fade">
<p class="lead">{{ post.content }}</p>
<div class="gap-items-4 mt-10">
<a class="text-lighter hover-light" href="#">
<i class="fa fa-thumbs-up mr-1"></i> 0
</a>
<a class="text-lighter hover-light" href="#">
<i class="fa fa-comment mr-1"></i> 0
</a>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'post',
props: {
post: {
required: true,
type: Object,
default: {
content: "",
id: 1,
user: {
name: "",
id: 1
}
}
}
},
data: function() {
return {
}
}
}
</script>

Angular 2 MDF validator to require atleast one of two fields

I have two input fields: email and phone, if user enters input in email then phone input is not required and vice versa. Atleast one of the fields is required at a time. I have model driven form and I am trying to write a custom validator function for this. So far I have got:
In my validator.ts:
import { FormGroup, FormControl } from '#angular/forms';
export function requiredOptional(emailVal: string, phoneVal: string) {
return (group: FormGroup): {[key: string]: any} => {
let email = group.controls[emailVal];
let phone = group.controls[phoneVal];
if (!email.value|| !phone.value) {
return {
isRequired: true
};
}
}
}
In my testForm.component.ts:
export class TestFormComponent{
testForm: FormGroup;
constructor(public fb: FormBuilder) {
this.testForm= fb.group({
email: ['', emailValidator],
phone: ['', phoneValidator],
password: ['', Validators.required],
confirmPassword: ['', Validators.required]
}, {validator: matchingPasswords('password', 'confirmPassword')},
{validator: requiredOptional('email', 'phone')}
)
}
onSubmit(value: Object): void {
console.log(value);
}
}
and in my testForm.component.html:
<form [formGroup]="testForm" novalidate (ngSubmit)="testForm.valid && onSubmit(testForm.value)">
.......
<input type="email" placeholder="Email*" formControlName="email">
<div class="form-text error" *ngIf="testForm.controls.email.touched">
<div *ngIf="testForm.hasError('isRequired')">Email or Phone is required.</div>
<div *ngIf="testForm.controls.email.hasError('invalidEmail')">Email is invalid.</div>
</div>
.......
<input type="text" placeholder="Mobile Number*" formControlName="phone">
<div class="form-text error" *ngIf="testForm.controls.phone.touched">
<div *ngIf="testForm.hasError('isRequired')">Email or Phone is required.</div>
<div *ngIf="testForm.controls.phone.hasError('invalidPhone')">Phone is invalid.</div>
</div>
.......
<button [disabled]="!testForm.valid" type="submit" class="btn btn-primary pull-right">Sign up</button>
</form>
But this is not working, I am getting error in testForm.component.ts: Supplied parameters do not match any signature of call targets
You can define multiple validating functions by composing like following:
export class TestFormComponent{
testForm: FormGroup;
constructor(public fb: FormBuilder) {
this.testForm= fb.group({
email: ['', emailValidator],
phone: ['', phoneValidator],
password: ['', Validators.required],
confirmPassword: ['', Validators.required]
}, {validator: Validators.compose([matchingPasswords('password', 'confirmPassword'), requiredOptional('email', 'phone')]})
}
onSubmit(value: Object): void {
console.log(value);
}
}
Focus on following change:
{validator: Validators.compose([matchingPasswords('password', 'confirmPassword'), requiredOptional('email', 'phone')]}
Instead of setting matchingPasswords validator, we can set array of validating functions by using Validators.compose

Meteor: use dynamic parameters in a template call

In my Meteor app i have the TAPi18n package and aldeed:autoform. Im trying to make a form validation using i18n for the placeholder field but i don't know how to do it.
So, I was wondering if it is possible to pass a dynamic parameter (that's how I call it) to a template, using Spacebars. I mean, without using helpers in the JS files, something like this:
<template name="Publish">
<div class="col-md-8 col-lg-8 col-md-offset-2 col-lg-offset-2 well">
{{#autoForm collection="Publications" id="insertPublicationForm" type="insert"}}
<fieldset>
<legend>{{_ "publish_title"}}</legend>
<div class="col-md-12 col-lg-12">
<div class="form-group">
{{> afFieldInput name='name' placeholder={{_ "name"}} }}
</div>
<div class="form-group">
{{> afFieldInput name='description' placeholder={{_ "description"}} }}
</div>
<div class="form-group">
{{> afFieldInput name='price' placeholder={{_ "price"}} }}
</div>
</div>
<div class="form-group">
<button type="submit" id="add_publication" class="btn btn-success center-block">{{_ "publish"}}</button>
</div>
</fieldset>
{{/autoForm}}
</div>
</template>
I could register a helper for every translation but i don't like too much the idea.
I also know that i could use the label field in the SimpleSchema, like this:
Meteor.startup(function() {
Publications.attachSchema(new SimpleSchema({
name: {
type: String,
label: TAPi18n.__("name"),
max: 200
},
description: {
type: String,
label: TAPi18n.__("description")
},
price: {
type: Number,
label: TAPi18n.__("price"),
min: 0
}
}));
});
And then use the afQuickField template instead of afFieldInput.
But i don't want to use a label, i want to use the placeholder of the input.
There is any way to do this?
Well, i don't know why i didn't see it before, but i can do this in the SimpleSchema:
Meteor.startup(function() {
Publications.attachSchema(new SimpleSchema({
name: {
type: String,
label: TAPi18n.__("name"),
max: 200,
autoform: {
afFieldInput: {
placeholder: TAPi18n.__("name")
}
}
},
description: {
type: String,
autoform: {
afFieldInput: {
placeholder: TAPi18n.__("description")
}
}
},
price: {
type: Number,
min: 0,
autoform: {
afFieldInput: {
placeholder: TAPi18n.__("price")
}
}
}
}));
});
That way i can use i18n in the placeholder without making tons of helpers.
Sorry if I made someone waste time on this.

Resources