Angular 8 - Form Group Values are always empty - angular8

Am new to angular and am starting off with a simple login form. However the form values are always showing up as empty - even if values have been entered before form submission.
I have created a login component which is imported in app module. The page comes up, but the submission does not carry any values. I have removed all Angular validation checks for debugging but the values received in my onsubmit function are still empty.
I have pasted the component code.Below.
login.component.html
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
<div class="form-group" >
<div class="form-group">
<input type="email" class="form-control" formcontrolname="username"
placeholder="Email Id" required autofocus/><br/>
</div>
<div class="form-group">
<input type="password" class="form-control" formcontrolname="password" placeholder="Password" required/><br/>
</div>
<div class="form-group">
<button>Login</button>
<a href="/public/register" class="btn" >Register</a>
</div>
app.component
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '#angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';
#NgModule({
imports: [ BrowserModule, AppRoutingModule, FormsModule,
ReactiveFormsModule ],
declarations: [ AppComponent, HelloComponent, LoginComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
login.component.ts
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
loginForm: FormGroup;
submitted = false;
returnUrl = '';
constructor(
private formBuilder: FormBuilder,
private router: Router
) {
}
ngOnInit() {
this.loginForm = this.formBuilder.group ({
username: ['', Validators.required],
password: ['', Validators.required]
});
}
get user() { return this.loginForm.controls; }
onSubmit() {
// stop if form is invalid
/* if (this.loginForm.invalid) {
console.log("Returning");
return;
} */
this.submitted = true;
console.log('login values:');
console.log(this.user.username.value, this.user.password.value);
this.returnUrl='/home';
// this.router.navigate([this.returnUrl]);
}
}

You can try this:
get user() { return this.loginForm.value; }

Knew it would be something super silly and trivial - turned out to be a case sensitivity problem issue on my end. Changed 'formcontrolname' to 'formControlName' and it started working! Duh!

Related

Angular Unit Test when we called Modal with #viewChild

I am very new in karma jasmine test case. please help me out.
Typescript
import { Component, OnInit, ViewChild } from '#angular/core';
#Component({
selector: 'app-label',
templateUrl: './app-label.component.html',
})
export class LabelRenderer implements OnInit {
moreInfoItems;
#ViewChild('moreInfoModal') moreInfoModal;
constructor() { }
ngOnInit(): void { }
showModal(labelMenu) {
this.moreInfoItems = labelMenu?.content;
this.moreInfoModal.open();
}
}
HTML
<div
#moreInfoModal
[closeButton]="true"
>
<h2>More Information</h2>
<div class="modal-content">
<ul
data-size="large"
>
<li *ngFor="let item of moreInfoItems">
<div class="_text-bold _text-underline">{{item.heading}}</div>{{item.description}}
</li>
</ul>
</div>
</div>
I want to write unit test case for same but facing issue. Below code I tried for same.
it('should validate showModal method', () => {
component.moreInfoItems = {
content: [
{
"heading": "",
"description": ""
}
]
};
spyOn(component.moreInfoModal, 'open').and.returnValue({});
component.moreInfoModal.open();
expect(component.moreInfoModal.open).toHaveBeenCalledTimes(1);
spyOn(component, 'showModal').and.callThrough();
component.showModal(component.moreInfoItems);
expect(component.moreInfoItems).toHaveBeenCalled();
});
I saw some solution on stack overflow but most of the solutions I see with modal component. so request to help me out.
Thanks in advance.

How to use composition API with custom field in Vee Validate 4 correctly

After reading documentation of Vee Validate 4 about using composition api with custom inputs, do I understand correctly that hook useField a have to call only inside input component(in my example is VInput.vue)? Is any way that i can use hook functionality in parent component? The VInput is used for another functionality that don't need validation so it will be extra functionality add useForm for global component in out project
For example I have List.vue
<template>
<form class="shadow-lg p-3 mb-5 bg-white rounded" #submit.prevent="submitPostForm">
<VFormGroup label="Title" :error="titleError">
<VInput v-model="postTitle" type="text" name="title" />
</VFormGroup>
<VFormGroup label="Body" :error="bodyError">
<VInput v-model="postBody" type="text" name="body" />
</VFormGroup>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</template>
<script>
import { ref, onMounted } from 'vue';
import { Form, useForm, useField } from 'vee-validate';
import * as Yup from 'yup';
export default {
components: { Form },
setup() {
const schema = Yup.object().shape({
title: Yup.string().required(),
body: Yup.string().min(6).required(),
});
//hooks
const { handleSubmit } = useForm({ validationSchema: schema });
// No need to define rules for fields because of schema
const { value: postTitle, errorMessage: titleError } = useField('title');
const { value: postBody, errorMessage: bodyError } = useField('body');
//methods
const submitPostForm = handleSubmit(() => {
addPost({ title: postTitle, body: postBody });
});
return { schema, postTitle, postBody, titleError, bodyError, submitPostForm };
},
};
</script>
The problem that input error I have to show only in VFormGroup so how I can manage this form?
I am not sure if understood your question correctly.
But in your case you only have 1 issue, you destructure errorMessage and value twice in one file, which isn't working.
you could change your useField's like this:
const { errorMessage: titleError, value: titleValue } = useField('title', Yup.string().required());
const { errorMessage: bodyError, value: bodyValue } = useField('body', Yup.string().required().min(8));
In your template you then want to use titleError and bodyError in your VFormGroup.
In your VInput you want to use titleValue and bodyValue for v-model
because you initialise your title and body in your setup() I guess that those do not have any predefiend values. If that would be the case you might want to take a look at the Options for useField() where you can have as a thridParam as an Object with e.g. initialValue as key which then would be your post.value.title. But for your use case I wouldn't recommend this.
to answer the Code question from the comments:
<template>
<form class="shadow-lg p-3 mb-5 bg-white rounded" #submit="handleSubmit">
<VFormGroup label="Title" :error="titleError">
<VInput v-model="titleValue" type="text" name="title" />
</VFormGroup>
<VFormGroup label="Body" :error="bodyError">
<VInput v-model="bodyValue" type="text" name="body" />
</VFormGroup>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</template>
<script>
import { ref } from 'vue';
import { Form, useForm, useField } from 'vee-validate';
import * as Yup from 'yup';
export default {
components: { Form },
setup() {
const title = ref('');
const body = ref('');
//hooks
const { handleSubmit } = useForm();
// No need to define rules for fields because of schema
const { errorMessage: titleError, value: titleValue } = useField('title', Yup.string().required());
const { errorMessage: bodyError, value: bodyValue } = useField('body', Yup.string().required().min(8));
return { titleError, bodyError, titleValue, bodyValue, handleSubmit };
},
};
</script>

ReactiveForms with custom input

I'm getting the following error when trying to create a form using a custom component.
Error: No value accessor for form control with name:
'nomeEmpresaAerea'
Error: No value accessor for form control with name: 'nomeEmpresaAerea'
Stack trace:
_throwError#webpack-internal:///./node_modules/#angular/forms/esm5/forms.js:2510:11
setUpControl#webpack-internal:///./node_modules/#angular/forms/esm5/forms.js:2380:9
FormGroupDirective.prototype.addControl#webpack-internal:///./node_modules/#angular/forms/esm5/forms.js:6736:9
FormControlName.prototype._setUpControl#webpack-internal:///./node_modules/#angular/forms/esm5/forms.js:7386:45
FormControlName.prototype.ngOnChanges#webpack-internal:///./node_modules/#angular/forms/esm5/forms.js:7299:13
checkAndUpdateDirectiveInline#webpack-internal:///./node_modules/#angular/core/esm5/core.js:12581:9
checkAndUpdateNodeInline#webpack-internal:///./node_modules/#angular/core/esm5/core.js:14109:20
checkAndUpdateNode#webpack-internal:///./node_modules/#angular/core/esm5/core.js:14052:16
debugCheckAndUpdateNode#webpack-internal:///./node_modules/#angular/core/esm5/core.js:14945:55
debugCheckDirectivesFn#webpack-internal:///./node_modules/#angular/core/esm5/core.js:14886:13
View_EmpresaCadastroComponent_0/<#ng:///EmpresaModule/EmpresaCadastroComponent.ngfactory.js:80:5
debugUpdateDirectives#webpack-internal:///./node_modules/#angular/core/esm5/core.js:14871:12
checkAndUpdateView#webpack-internal:///./node_modules/#angular/core/esm5/core.js:14018:5
callViewAction#webpack-internal:///./node_modules/#angular/core/esm5/core.js:14369:21
this is my reactive form:
import { Component, OnInit, Input } from '#angular/core';
import { Router } from '#angular/router';
import { HttpClient } from '#angular/common/http';
import { FormArray, FormBuilder, FormGroup, Validators } from '#angular/forms';
#Component({
selector: 'app-empresa-cadastro',
templateUrl: './empresa-cadastro.component.html',
styleUrls: ['./empresa-cadastro.component.css']
})
export class EmpresaCadastroComponent implements OnInit {
#Input() empresa = {};
empresaForm: FormGroup;
constructor(private route: Router, private http: HttpClient, private fb: FormBuilder) {
this.createForm();
}
createForm() {
this.empresaForm = this.fb.group({
nomeEmpresaAerea: '',
status: 'S',
});
}
this is my html:
<form [formGroup]="empresaForm" (ngSubmit)="onSubmit()">
<div style="margin-bottom: 1em">
<button type="submit" [disabled]="empresaForm.pristine" class="btn btn-success">Cadastrar</button>
<button type="button" (click)="limpar()" [disabled]="empresaForm.pristine" class="btn btn-danger">Limpar</button>
</div>
<div class="form-group">
<label class="center-block">Nome:
<input class="form-control" >
<app-pf-input-text formControlName="nomeEmpresaAerea" ></app-pf-input-text>
</label>
</div>
</form>
pf-input-text.componente.ts:
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'app-pf-input-text',
templateUrl: './pf-input-text.component.html',
styleUrls: ['./pf-input-text.component.scss']
})
export class PfInputTextComponent implements OnInit {
#Input() id: string;
#Input() name: string;
#Input() value: string;
#Input() placeholder: string;
//falta trim
#Input() maxlength: string;
#Input() minlength: string;
#Input() disabled: boolean;
#Input() required: boolean;
constructor() { }
ngOnInit() {
}
}
pf-input-text.component.html
<div class="input-group">
<input
type="text"
id="{{id}}"
name="{{name}}"
placeholder="{{placeholder}}"
attr.maxlength="{{maxlength}}"
class="form-control"
disabled="{{disabled}}"
required="{{required}}">
</div>
try this in reactive form component
this.empresaForm = new FormGroup({
'nomeEmpresaAerea': new FormControl(null, Validators.required)});

input not beign validated by form

I created an input component to reuse it between a few forms. In one of then, it's working perfectly, but in the other, it's not.
It doesn't throw any erros. I even receive the input value after submit.
code.component.html
<div [ngClass]="aplicaCssErro(ag)">
<label for="code">Code</label>
<input id="code" name="code" type="text" class="form-control" [(ngModel)]="value" required #ag="ngModel"
maxlength="4" minlength="4" (blur)="formatCode(ag)">
<div *ngIf="verificaValidTouched(ag)" class="msgErroText">
<gce-campo-control-erro [mostrarErro]="ag?.errors?.required" msgErro="the code is required">
</gce-campo-control-erro>
</div>
code.component.ts
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'gce-input-code',
templateUrl: './input-code.component.html',
styleUrls: ['./input-code.component.scss']
})
export class InputCodeComponent implements OnInit {
#Input() value: string = "";
constructor() { }
ngOnInit() {
}
//some functions
}
form.component.html
The problem is that the form is not validating it, just the first input.
I think the form is not recognizing it as one of it's inputs.
<form (ngSubmit)="onSubmitForm2(f)" #f="ngForm">
<div class="row">
<div class="col-sm-6" [ngClass]="aplicaCssErro(apelido)">
<label for="apelido">Apelido da conta</label>
<input id="apelido" name="apelido" type="text" class="form-control" alt="Apelido" [(ngModel)]="conta.apelido" required #apelido="ngModel">
<div *ngIf="verificaValidTouched(apelido)" class="msgErroText">
<gce-campo-control-erro [mostrarErro]="apelido?.errors?.required" msgErro="O Apelido é obrigatório.">
</gce-campo-control-erro>
</div>
</div>
</div>
<div class="row">
<div class="form-group">
<div class="col-sm-2">
<gce-input-code name="code" [(ngModel)]="user.code" #code="ngModel" ngDefaultControl></gce-input-code>
</div>
</div>
</div>
<div class="row">
<button class="btn btn-default" name="btn2" type="submit" alt="Continuar" [disabled]="!f.valid">Continue</button>
</div>
Any help?
If I understand your question correctly. You are trying to make it so the form(ngForm) can validate the custom component that wraps around the input(gce-input-code).
A normal form does not have any way to know what is going in/out of the component as it is Angular component. You would have to enhance your code.component.ts to include all the connectors (ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS) into it.
Checkout this blog
https://blog.thoughtram.io/angular/2016/07/27/custom-form-controls-in-angular-2.html#custom-form-control-considerations
and its plnkr(exerpt code below)
https://plnkr.co/edit/6xVdppNQoLcsXGMf7tph?p=info
import { Component, OnInit, forwardRef, Input, OnChanges } from '#angular/core';
import { FormControl, ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS }
from '#angular/forms';
export function createCounterRangeValidator(maxValue, minValue) {
return (c: FormControl) => {
let err = {
rangeError: {
given: c.value,
max: maxValue || 10,
min: minValue || 0
}
};
return (c.value > +maxValue || c.value < +minValue) ? err: null;
}
}
#Component({
selector: 'counter-input',
template: `
<button (click)="increase()">+</button> {{counterValue}} <button (click)="decrease()">-</button>
`,
providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CounterInputComponent), multi: true },
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => CounterInputComponent), multi: true }
]
})
export class CounterInputComponent implements ControlValueAccessor, OnChanges {
propagateChange:any = () => {};
validateFn:any = () => {};
#Input('counterValue') _counterValue = 0;
#Input() counterRangeMax;
#Input() counterRangeMin;
get counterValue() {
return this._counterValue;
}
set counterValue(val) {
this._counterValue = val;
this.propagateChange(val);
}
ngOnChanges(inputs) {
if (inputs.counterRangeMax || inputs.counterRangeMin) {
this.validateFn = createCounterRangeValidator(this.counterRangeMax, this.counterRangeMin);
this.propagateChange(this.counterValue);
}
}
writeValue(value) {
if (value) {
this.counterValue = value;
}
}
registerOnChange(fn) {
this.propagateChange = fn;
}
registerOnTouched() {}
increase() {
this.counterValue++;
}
decrease() {
this.counterValue--;
}
validate(c: FormControl) {
return this.validateFn(c);
}
}

File upload from <input type="file">

Using angular 2 beta, I cannot seem to get an <input type="file"> to work.
Using diagnostic, I can see two-way binding for other types such as text.
<form>
{{diagnostic}}
<div class="form-group">
<label for="fileupload">Upload</label>
<input type="file" class="form-control" [(ngModel)]="model.fileupload">
</div>
</form>
In my TypeScript file, I have the following diagnostic line:
get diagnostic() { return JSON.stringify(this.model); }
Could it be that it is the issue of not being JSON? The value is null.
I cannot really verify the value of the input. Уven though the text next to "Choose file ..." updates, I cannot see differences in the DOM for some reason.
I think that it's not supported. If you have a look at this DefaultValueAccessor directive (see https://github.com/angular/angular/blob/master/modules/angular2/src/common/forms/directives/default_value_accessor.ts#L23). You will see that the value used to update the bound element is $event.target.value.
This doesn't apply in the case of inputs with type file since the file object can be reached $event.srcElement.files instead.
For more details, you can have a look at this plunkr: https://plnkr.co/edit/ozZqbxIorjQW15BrDFrg?p=info:
#Component({
selector: 'my-app',
template: `
<div>
<input type="file" (change)="onChange($event)"/>
</div>
`,
providers: [ UploadService ]
})
export class AppComponent {
onChange(event) {
var files = event.srcElement.files;
console.log(files);
}
}
#Component({
selector: 'my-app',
template: `
<div>
<input name="file" type="file" (change)="onChange($event)"/>
</div>
`,
providers: [ UploadService ]
})
export class AppComponent {
file: File;
onChange(event: EventTarget) {
let eventObj: MSInputMethodContext = <MSInputMethodContext> event;
let target: HTMLInputElement = <HTMLInputElement> eventObj.target;
let files: FileList = target.files;
this.file = files[0];
console.log(this.file);
}
doAnythingWithFile() {
}
}
There is a slightly better way to access attached files. You could use template reference variable to get an instance of the input element.
Here is an example based on the first answer:
#Component({
selector: 'my-app',
template: `
<div>
<input type="file" #file (change)="onChange(file.files)"/>
</div>
`,
providers: [ UploadService ]
})
export class AppComponent {
onChange(files) {
console.log(files);
}
}
Here is an example app to demonstrate this in action.
Template reference variables might be useful, e.g. you could access them via #ViewChild directly in the controller.
Another way using template reference variable and ViewChild, as proposed by Frelseren:
import { ViewChild } from '#angular/core';
#Component({
selector: 'my-app',
template: `
<div>
<input type="file" #fileInput/>
</div>
`
})
export class AppComponent {
#ViewChild("fileInput") fileInputVariable: any;
randomMethod() {
const files = this.fileInputVariable.nativeElement.files;
console.log(files);
}
}
Also see https://stackoverflow.com/a/40165524/4361955
Try this small lib, works with Angular 5.0.0
https://www.npmjs.com/package/ng2-file-upload
Quickstart example with ng2-file-upload 1.3.0:
User clicks custom button, which triggers upload dialog from hidden input type="file" , uploading started automatically after selecting single file.
app.module.ts:
import {FileUploadModule} from "ng2-file-upload";
your.component.html:
...
<button mat-button onclick="document.getElementById('myFileInputField').click()" >
Select and upload file
</button>
<input type="file" id="myFileInputField" ng2FileSelect [uploader]="uploader" style="display:none">
...
your.component.ts:
import {FileUploader} from 'ng2-file-upload';
...
uploader: FileUploader;
...
constructor() {
this.uploader = new FileUploader({url: "/your-api/some-endpoint"});
this.uploader.onErrorItem = item => {
console.error("Failed to upload");
this.clearUploadField();
};
this.uploader.onCompleteItem = (item, response) => {
console.info("Successfully uploaded");
this.clearUploadField();
// (Optional) Parsing of response
let responseObject = JSON.parse(response) as MyCustomClass;
};
// Asks uploader to start upload file automatically after selecting file
this.uploader.onAfterAddingFile = fileItem => this.uploader.uploadAll();
}
private clearUploadField(): void {
(<HTMLInputElement>window.document.getElementById('myFileInputField'))
.value = "";
}
Alternative lib, works in Angular 4.2.4, but requires some workarounds to adopt to Angular 5.0.0
https://www.npmjs.com/package/angular2-http-file-upload
If you have a complex form with multiple files and other inputs here is a solution that plays nice with ngModel.
It consists of a file input component that wraps a simple file input and implements the ControlValueAccessor interface to make it consumable by ngModel. The component exposes the FileList object to ngModel.
This solution is based on this article.
The component is used like this:
<file-input name="file" inputId="file" [(ngModel)]="user.photo"></file-input>
<label for="file"> Select file </label>
Here's the component code:
import { Component, Input, forwardRef } from '#angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '#angular/forms';
const noop = () => {
};
export const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => FileInputComponent),
multi: true
};
#Component({
selector: 'file-input',
templateUrl: './file-input.component.html',
providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR]
})
export class FileInputComponent {
#Input()
public name:string;
#Input()
public inputId:string;
private innerValue:any;
constructor() { }
get value(): FileList {
return this.innerValue;
};
private onTouchedCallback: () => void = noop;
private onChangeCallback: (_: FileList) => void = noop;
set value(v: FileList) {
if (v !== this.innerValue) {
this.innerValue = v;
this.onChangeCallback(v);
}
}
onBlur() {
this.onTouchedCallback();
}
writeValue(value: FileList) {
if (value !== this.innerValue) {
this.innerValue = value;
}
}
registerOnChange(fn: any) {
this.onChangeCallback = fn;
}
registerOnTouched(fn: any) {
this.onTouchedCallback = fn;
}
changeFile(event) {
this.value = event.target.files;
}
}
And here's the component template:
<input type="file" name="{{ name }}" id="{{ inputId }}" multiple="multiple" (change)="changeFile($event)"/>
just try (onclick)="this.value = null"
in your html page add onclick method to remove previous value so user can select same file again.