Angular testing: Expected spy AuthenticationService.checkUsername to have been called - javascript

I'm learning Angular testing and so far it has been pretty frustrating. I wanted to test my signup component, but it gave me the following error.
Expected spy AuthenticationService.checkUsername to have been
called.
This is the component I am trying to test. I am currently testing whether the method checkUniqueUsername() works as planned. When I call the message I expect the property uniqueUsernameMessage to contain the string 'already exists' when the authService.checkUsername() method returns {obj: true}
Unfortunately it returns the above error and for the value of the property uniqueUsernameMessage it returns undefined instead of the expected .contains(already exists).
Can someone help me figure out where I went wrong?
Signup.component.ts
#Component({
selector: 'app-sign-up',
templateUrl: 'sign-up.component.html',
styleUrls: ['sign-up.component.css']
})
export class SignUpComponent implements OnInit {
mySignupForm: FormGroup;
countries = countries;
uniqueUsernameMessage;
uniqueEmailMessage;
formSubmitted = false;
#Output() closeSubmenu = new EventEmitter();
constructor(
private authService: AuthenticationService,
private route: Router,
private navigationService: NavigationService){}
ngOnInit() {
this.mySignupForm = new FormGroup({
firstName: new FormControl(null, Validators.required),
lastName: new FormControl(null, Validators.required),
username: new FormControl(null, [Validators.required, Validators.minLength(5), Validators.maxLength(15)]),
birthDate: new FormControl(null, Validators.required),
password1: new FormControl(null, [Validators.required, Validators.minLength(6), Validators.maxLength(15)]),
password2: new FormControl(null, Validators.required),
email: new FormControl(null, [Validators.required, Validators.email]),
country: new FormControl(null, Validators.required),
house: new FormControl(null, Validators.required)
})
}
checkUniqueUsername() {
if ((this.mySignupForm.value.username >= 5 && this.mySignupForm.value.username <= 15) || null ){
this.authService.checkUsername(this.username.value)
.pipe(debounceTime(500))
.subscribe((result: any) => {
if (result.obj) {
console.log('result', result);
this.uniqueUsernameMessage = "This username already exists. Please pick another one."
} else {
this.uniqueUsernameMessage = null;
}
})
}
}
}
signup.component.spec.ts
describe('signup', () => {
let component: SignUpComponent;
let fixture: ComponentFixture<SignUpComponent>;
let authService;
beforeEach(async(() => {
const authServiceSpy = jasmine.createSpyObj('AuthenticationService', ['checkUsername', 'checkEmailUniqueness', 'signup']);
TestBed.configureTestingModule({
declarations: [SignUpComponent],
providers: [
{
provide: AuthenticationService,
useValue: authServiceSpy
},
NavigationService
],
imports: [
RouterTestingModule],
schemas: [NO_ERRORS_SCHEMA]
})
.compileComponents();
authService = TestBed.get(AuthenticationService);
}));
beforeEach(() => {
fixture = TestBed.createComponent(SignUpComponent);
component = fixture.componentInstance;
// fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should warn user if username already exists', fakeAsync(() => {
const fakeUsername = "testUsername"
authService.checkUsername.and.returnValue(of({obj: true}));
fixture.detectChanges();
component.mySignupForm.value.username = fakeUsername;
fixture.detectChanges();
component.checkUniqueUsername();
tick(500);
expect(authService.checkUsername).toHaveBeenCalled();
expect(component.uniqueUsernameMessage).toContain('already exists');
})
)
});

This line:
component.mySignupForm.value.username = fakeUsername;
does not change the value of the FormControl. The username value is still null. Thus, when you check its validity, the condition inside if is false.
Reactive form is immutable. If you want to set new value for any FormControl, it should be done using .setValue().
Like this:
component.mySignupForm.get('username').setValue(fakeUsername);
Hope this helps :)

Related

How to unit test with Jasmine when FormGroup is set to disabled?

I havea a angular 8 application and I am using Jasmine Karma for unit testing. And I want to test a Formbuilder that is disabled in the NgOninit function:
I have it like this: component.ts
constructor(
private dossierService: DossierService,
private route: ActivatedRoute,
private sanitizer: DomSanitizer,
private dossierFileService: DossierFileService,
private errorProcessor: ErrorProcessor,
private dialog: MatDialog
) {
this.dossierItems = this.route.snapshot.data.dossierItems;
this.editDossierForm = this.formBuilder.group({});
this.editDossierForm.disable();
this.dossier = this.route.snapshot.data.dossier;
this.dossierItems = route.snapshot.data.dossierItems;
this.profileImagefile = this.route.snapshot.data.profileImage;
this.editDossierForm = this.formBuilder.group({
firstName: this.formBuilder.control(this.dossier.firstName, [Validators.required, Validators.maxLength(255)]),
lastName: this.formBuilder.control(this.dossier.lastName, [Validators.required, Validators.maxLength(255)]),
mobile: this.formBuilder.control(this.dossier.mobile, [Validators.maxLength(255)]),
company: this.formBuilder.control(this.dossier.company, [Validators.maxLength(255)]),
buddy: this.formBuilder.control(this.dossier.buddy, [Validators.maxLength(255)]),
supervisor: this.formBuilder.control(this.dossier.supervisor, [Validators.maxLength(255)]),
dateOfBirth: this.formBuilder.control(this.dossier.dateOfBirth)
});
}
ngOnInit(): void {
this.editDossierForm.disable();
}
}
and this is the spec file of it:
describe('DossierPersonalDataComponent', () => {
let component: DossierPersonalDataComponent;
let fixture: ComponentFixture<DossierPersonalDataComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, DossierModule, BrowserModule],
declarations: [DossierPersonalDataComponent],
providers: [
DossierFileService,
ErrorProcessor,
{
provide: ActivatedRoute,
useValue: {
snapshot: {
data: {
dossier: {
firstName: 'hello',
lastName: 'world',
mobile: '111-111-1111',
company: 'carapax',
buddy: 'bud',
supervisor: 'super',
dateOfBirth: '1900-01-01',
},
dossierItems: [], // mock
profileImage: '',
}
}
}
},
{
// DossierFileService, These have to be outside of the braces
// ErrorProcessor,
provide: DomSanitizer,
useValue: {
sanitize: () => 'safeString',
bypassSecurityTrustHtml: () => 'safeString'
}
}
]
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(DossierPersonalDataComponent);
component = fixture.componentInstance;
});
}));
it('should create', () => {
expect(component).toBeTruthy();
});
});
but in the instanbul coverage rapport this stays red:
}
ngOnInit(): void {
this.editDossierForm.disable();
}
So how to coverage this part?
Thank you
oke,
if I do it like this:
it('should create', () => {
fixture.detectChanges();
expect(component).toBeTruthy();
});
I get this error:
DossierPersonalDataComponent > should create
TypeError: Failed to execute 'createObjectURL' on 'URL': No function was found that matched the signature provided.
and in my tesspec, I see this:
get profileImageUrl() {
return this.profileImagefile === null
? '/assets/placeholder.jpg'
: this.sanitizer.bypassSecurityTrustUrl(window.URL.createObjectURL(this.profileImagefile));
}
and this line:
? '/assets/placeholder.jpg'
is in yellow. Branch not covered.
You need to execute a fixture.detectChanges(). Calling this the first time, it will trigger ngOnInit. After that it will just trigger change detection.
So depending on your tests, you can either include a fixture.detectChanges() inside the beforeEach block.
I would only recommend this approach if you don't want to spy or setup different mocks for things happening inside ngOnInit for each test case.
I usually add this inside every test case after I declared my spys. So your test would look something along those lines:
it('should create', () => {
// spy on anything you would like. e.g the disable call of your form
// then trigger onInit
fixture.detectChanges();
// then run your expectations
expect(component).toBeTruthy();
});

How to set default values in angular reactive form using previously saved object?

My reactive form is almost done. At least it's basic logic. I have only one problem. According to the task I need all inputs to be set using previous data that user entered, if that data exists of course. The data should be kept and stored and rendered if components are switched. I mannaged to save all data into an object in a component, but after page refresh everything is gone.
How can I tackle with this problem?
My code is as follows:
import { Component, OnInit, ViewChild, ElementRef } from '#angular/core';
import { FormGroup, FormControl, FormArray, Validators } from '#angular/forms';
#Component({
selector: 'app-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.scss']
})
export class FormComponent implements OnInit {
signUpForm: FormGroup;
countries = [
new FormControl('USA'),
new FormControl('India')
];
savedForm: {
city: string,
code: number,
country: string,
email: string,
firstName: string,
id: string,
lastName: string,
phone: number,
state: string;
}
statesUSA = [new FormControl('New York'), new FormControl('California')];
statesIndia = [new FormControl('Andhra Pradesh'), new FormControl('Goa')]
citiesNY = [new FormControl('Albany'), new FormControl('New York City')];
citiesCali = [new FormControl('Sacramento'), new FormControl('Los Angeles'), new FormControl('San Francisco')];
citiesAndhra = [new FormControl('Visakhapatnam'), new FormControl('Amaravati')];
citiesGoa = [new FormControl('Panaji'), new FormControl('Vasco da Gama')];
#ViewChild('phoneInput', {static: false}) phoneInput: ElementRef;
public mask:any = {
mask: '+{38}(0__)000-00-00',
lazy: false
}
constructor() { }
ngOnInit() {
this.signUpForm = new FormGroup({
'firstName': new FormControl(null, [Validators.required, Validators.pattern(/^[а-яА-ЯёЁіІїЇ]{2,32}$/iu)]),
'email': new FormControl(null, [Validators.required, Validators.email, Validators.pattern(/^\S{2,255}#\S+\.\S+$/iu)]),
'country': new FormControl(null, Validators.required),
'phone': new FormControl(null),
'lastName': new FormControl(null, [Validators.required, Validators.pattern(/^[а-яА-ЯёЁіІїЇ]{2,32}$/iu)]),
'id': new FormControl(null, [Validators.required, Validators.pattern(/\b[A-Za-z_]{5,30}\b/)]),
'state': new FormControl(null, Validators.required),
'city': new FormControl(null, Validators.required),
'code': new FormControl(null, [Validators.pattern(/\b[A-Za-z_0-9]{1,10}\b/)])
});
this.signUpForm.setValue(this.savedForm);
}
onBlur(blur: boolean) {
}
onSubmit() {
if(this.signUpForm.status === 'VALID') {
this.
savedForm = this.signUpForm.value;
console.log(this.savedForm);
}
}
onReset() {
}
onChange() {
(<FormGroup>this.signUpForm.get('state').value) = null;
(<FormGroup>this.signUpForm.get('city').value) = null;
}
onOpen(controlName: string) {
}
}
You should use Service, components shouldn't fetch or save data directly they should focus on presenting data and delegate data access to a service.
Step 1
Create a service :
src/services/storeService.ts
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root',
})
export class StoreService {
public savedForm
}
When you provide the service at the root level, Angular creates a single, shared instance of StoreService and injects into any class that asks for it. Registering the provider in the #Injectable metadata also allows Angular to optimize an app by removing the service if it turns out not to be used after all.
Step 2
In your component inject the newly created service in the constructor:
#Component({
selector: 'app-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.scss']
})
export class FormComponent {
signUpForm: FormGroup;
constructor(private storeService: StoreService) { }
ngOnInit() {
this.signUpForm = new FormGroup({
'firstName': new FormControl(null, [Validators.required, Validators.pattern(/^[а-яА-ЯёЁіІїЇ]{2,32}$/iu)]),
'email': new FormControl(null, [Validators.required, Validators.email, Validators.pattern(/^\S{2,255}#\S+\.\S+$/iu)]),
'country': new FormControl(null, Validators.required),
'phone': new FormControl(null),
'lastName': new FormControl(null, [Validators.required, Validators.pattern(/^[а-яА-ЯёЁіІїЇ]{2,32}$/iu)]),
'id': new FormControl(null, [Validators.required, Validators.pattern(/\b[A-Za-z_]{5,30}\b/)]),
'state': new FormControl(null, Validators.required),
'city': new FormControl(null, Validators.required),
'code': new FormControl(null, [Validators.pattern(/\b[A-Za-z_0-9]{1,10}\b/)])
});
if (this.storeService.savedForm) this.signUpForm.setValue(this.storeService.savedForm)
}
onSubmit() {
if (this.signUpForm.status === 'VALID') {
this.storeService.savedForm = this.signUpForm
}
}
}

Cannot read property "*******"' of undefined

I am trying To create custom Validation to match password and ConfirmPassword.
But I am getting the error.
I am new to angular. Please ignore my ignorance.
Is it a good idea to create custom validation to match password and Confirm password? or is there a better way out?
Here is the code.
Please help
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormControl, Validators, AbstractControl, ValidatorFn, ValidationErrors, FormBuilder } from '#angular/forms';
#Component({
selector: 'app-sample-form',
templateUrl: './sample-form.component.html',
styleUrls: ['./sample-form.component.scss']
})
export class SampleFormComponent implements OnInit {
formattedMessage: string;
passMatch: Boolean;
formValid: boolean;
constructor() { }
ngOnInit() {
this.onChanges();
}
sample: FormGroup = new FormGroup({
inpt: new FormControl(null, Validators.required),
pass: new FormControl("", Validators.required),
cpass: new FormControl("", [Validators.required, this.passwordMatch]),
iagree: new FormControl(null, Validators.nullValidator)
});
onChanges(): void {
this.sample.valueChanges.subscribe(val => {
console.log("======================================");
console.log("PASS : " + this.sample.controls.pass.value);
console.log("CPASS : " + this.sample.controls.cpass.value);
if (this.sample.controls.pass.value == this.sample.controls.cpass.value) {
this.passMatch = true;
console.log("MATCHED");
} else {
this.passMatch = false;
console.log("MIS-MATCHED");
}
this.formValid = this.sample.valid;
console.log("PASS MATCH : " + String(this.passMatch));
console.log("FORM VALID : " + String(this.formValid));
});
}
passwordMatch(group: FormGroup): ValidationErrors | null {
let pass = group.controls.pass.value;
let confirmPass = group.controls.cpass.value;
return pass === confirmPass ? null : { notSame: true }
}
sumbit() {
}
}
I am Getting this error :
ERROR Error: Uncaught (in promise): TypeError: Cannot read property 'pass' of undefined
TypeError: Cannot read property 'pass' of undefined
at passwordMatch (sample-form.component.ts:51)
at forms.js:1480
at Array.map (<anonymous>)
at _executeValidators (forms.js:1476)
at FormControl.validator (forms.js:1418)
at FormControl._runValidator (forms.js:4089)
at FormControl.updateValueAndValidity (forms.js:4050)
at new FormControl (forms.js:4656)
at new SampleFormComponent (sample-form.component.ts:25)
at createClass (core.js:31985)
at resolvePromise (zone-evergreen.js:797)
at resolvePromise (zone-evergreen.js:754)
at zone-evergreen.js:858
at ZoneDelegate.invokeTask (zone-evergreen.js:391)
at Object.onInvokeTask (core.js:39680)
at ZoneDelegate.invokeTask (zone-evergreen.js:390)
at Zone.runTask (zone-evergreen.js:168)
at drainMicroTaskQueue (zone-evergreen.js:559)
-Ashish
You are assigning your validator to the formControl cpass, so the formControl you will get through the arguments is only cpass. That validator needs the parent level to be able to access both controls (pass and cpass). Try this instead:
sample: FormGroup = new FormGroup({
inpt: new FormControl(null, Validators.required),
pass: new FormControl("", Validators.required),
cpass: new FormControl("", [Validators.required]),
iagree: new FormControl(null, Validators.nullValidator)
}, {validators: [this.passwordMatch]});
That should do the trick. The error will be in sample formgroup, not in the formControl, which in my opinion makes sense because it affects to more than one form controls.
You need to bind the passwordMatch function because angular will call it in different context
try
cpass: new FormControl("", [Validators.required, this.passwordMatch.bind(this)]),
instead of
cpass: new FormControl("", [Validators.required, this.passwordMatch]),
Updated answer:
You need to do both bind the passwordMatch method and add pass and cpass in the separate form group with generic validator
something like this:
sample = new FormGroup({
inpt: new FormControl(null, Validators.required),
iagree: new FormControl(null, Validators.nullValidator),
confirmPasswordForm: new FormGroup({
pass: new FormControl('', Validators.required),
cpass: new FormControl('', Validators.required),
}, {
validator: this.passwordMatch.bind(this),
})
});
But you need create nested form in you html
This actually solved the problem.
to use the keyword "root"
passwordMatch(control: AbstractControl): ValidationErrors | null {
const pass = control.root.get('pass');
const cpass = control.root.get('cpass');
if (pass != null && cpass != null) {
console.log("Pass : ", pass.value);
console.log("cpass : ", cpass.value);
if (pass.value != cpass.value) {
console.log("MISMATCH");
return { PasswordMismatch: 'Password Mismatch' }
} else {
console.log("MATCH");
return null;
}
} else {
console.log("MISMATCH2");
return { PasswordMismatch: 'Password Mismatch' };
}
}

MomentJS changing value when assigned to new variable

When I try assigning a moment to a new variable it changes the value without me modifying it.
I have tried everything from forcing the use of UTC and settings timezones. It just keeps changing the value.
#Component({
selector: 'app-appointment-create',
templateUrl: './appointment-create.component.html',
styleUrls: ['./appointment-create.component.css']
})
export class AppointmentCreateComponent implements OnInit {
appointment: CreateAppointmentDto;
form: FormGroup;
private formSubmitted: boolean;
tasks: Task[];
availability: Availability[];
task: number;
availablemoments: Moment[];
constructor(
private titleService: Title,
private router: Router,
private appointmentService: AppointmentService,
private taskService: TasksService,
private webLogger: WebloggerService,
private availabilityService: AvailabilityService,
) {
this.appointment = new CreateAppointmentDto();
}
dateFilter = (d: Moment): boolean => {
return this.availability.filter(s => s.timestampstart.isSame(d, 'day')).length >= 1;
}
ngOnInit() {
this.titleService.setTitle('New Appointment');
this.taskService.getActiveTasks().subscribe(value => {this.tasks = value; });
this.form = new FormGroup({
timestampstart: new FormControl(this.appointment.timestampstart, Validators.required),
daystart: new FormControl(this.appointment.timestampstart, Validators.required),
location: new FormControl(this.appointment.location, Validators.required),
description: new FormControl(this.appointment.description, Validators.required),
paid: new FormControl(false, Validators.required),
serviceType: new FormControl(this.appointment.serviceTypeId, Validators.required),
client: new FormControl(this.appointment.clientId, Validators.required),
assignedUser: new FormControl(this.appointment.assignedUserId, Validators.required),
});
}
onSubmit(event) {
event.preventDefault();
this.formSubmitted = true;
if (this.form.valid) {
this.form.disable();
this.appointment.timestampstart = this.form.get('timestampstart').value;
this.appointment.location = this.form.get('location').value;
this.appointment.description = this.form.get('description').value;
this.appointment.paid = this.form.get('paid').value;
this.appointment.serviceTypeId = this.form.get('serviceType').value;
this.appointment.clientId = this.form.get('client').value;
this.appointment.assignedUserId = this.form.get('assignedUser').value;
this.appointmentService.createNewAppointment(this.appointment)
.subscribe(value => { this.router.navigate([`/dashboard/appointment/${value.id}/edit`]); });
} else {
this.webLogger.error('The form is invalid, please check the values');
}
}
selectTask($event: Event) {
this.task = Number(this.form.get('serviceType').value);
this.availabilityService.getAvailabilityForTask(this.task).subscribe(value => {
this.availability = value;
});
}
setTime($event: Event) {
this.availablemoments = [];
const dayAvailability: Availability[] = this.availability.filter(
s => s.timestampstart.isSame(moment(this.form.get('daystart').value), 'day'));
const currentDate = dayAvailability.reduce((prev, curr) => prev.timestampstart < curr.timestampstart ? prev : curr).timestampstart;
dayAvailability.forEach(value => {
while (value.timestampend.isAfter(currentDate)) {
if (!this.availablemoments.includes(moment(currentDate))) {
this.availablemoments.push(moment(currentDate));
}
currentDate.add(30, 'minutes');
}
});
}
}
this.availability is a list of Availability objects which include start and end moments
I expect the second console log to return the same as the first console log.
UPDATE:
The Availability class looks like this:
export class Availability {
id: number;
timestampstart: Moment;
timestampend: Moment;
location: string;
description: string;
paid: boolean;
payment: Invoice;
serviceType: Task;
client: Client;
assignedUser: User;
static serialize(data: any): Availability {
const user: Availability = Object.assign(new this(), data);
if (data.hasOwnProperty('timestampstart')) {
user.timestampstart = moment(data.timestampstart);
}
if (data.hasOwnProperty('timestampend')) {
user.timestampend = moment(data.timestampend);
}
if (data.hasOwnProperty('serviceType')) {
user.serviceType = Task.serialize(data.serviceType);
}
if (data.hasOwnProperty('client')) {
user.client = Client.serialize(data.client);
}
if (data.hasOwnProperty('assignedUser')) {
user.assignedUser = User.serialize(data.assignedUser);
}
return user;
}
}
I figured it out, since I was using Angular, I assigned the formControlName to the wrong HTML tag (the option tag instead of the select) causing the get of the value to return a value that I wasn't expecting. I've updated the main post to show the entire code.

Unable to patch data to FormArray

Unable to patch values to FormArray resultList.
Anybody can please explain me, what i'm missing?
TS File:
import { Component, OnInit } from '#angular/core';
import { Student } from '../student';
import { FormGroup, FormControl, Validators, FormArray } from '#angular/forms';
#Component({
selector: 'app-container',
templateUrl: './container.component.html',
styleUrls: ['./container.component.css']
})
export class ContainerComponent implements OnInit {
studList: Student[] = [];
myform: FormGroup = new FormGroup({
firstName: new FormControl('', [Validators.required, Validators.minLength(4)]),
lastName: new FormControl(),
gender: new FormControl('male'),
dob: new FormControl(),
qualification: new FormControl(),
resultList: new FormArray([])
});
onSave() {
let stud: Student = new Student();
stud.firstName = this.myform.get('firstName').value;
stud.lastName = this.myform.get('lastName').value;
stud.gender = this.myform.get('gender').value;
stud.dob = this.myform.get('dob').value;
stud.qualification = this.myform.get('qualification').value;
this.studList.push(stud);
this.myform.controls.resultList.patchValue(this.studList);
console.log(JSON.stringify(this.studList));
}
ngOnInit() {
}
}
Model:
export class Student {
public firstName: String;
public lastName: string;
public gender: string;
public dob: string;
public qualification: string;
}
HTML:
<div class="container">
<h3>Striped Rows</h3>
<table class="table table-striped" formArrayName="resultList">
<thead>
<tr>
<th>Firstname</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of myform.controls.resultList.controls; let i = index" [formGroupName]="i">
<td><p formControlName="firstName"></p></td>
</tr>
</tbody>
</table>
</div>
this.studList JSON:
[
{
"firstName":"santosh",
"lastName":"jadi",
"gender":"male",
"dob":"2018-03-31T18:30:00.000Z",
"qualification":"BE"
},
{
"firstName":"santosh",
"lastName":"jadi",
"gender":"male",
"dob":"2018-03-31T18:30:00.000Z",
"qualification":"BE"
}
]
By your question you want to add new Student to resultList.
First of all, you need to know FormArray is an array of AbstractControl.
You can add to array only type of AbstractControl not other.
To simplify task prefer to use FormBuilder:
constructor(private fb: FormBuilder) {}
createForm() {
this.myform = this.fb.group({
firstName: ['', [Validators.required, Validators.minLength(4)]],
lastName: [],
gender: ['male'],
dob: [],
qualification: [],
resultList: new FormArray([])
});
}
As you can see before filling resultList FormArray, it's mapped to FormGroup:
onSave() {
let stud: Student = new Student();
stud.firstName = 'Hello';
stud.lastName = 'World';
stud.qualification = 'SD';
this.studList.push(stud);
let studFg = this.fb.group({
firstName: [stud.firstName, [Validators.required, Validators.minLength(4)]],
lastName: [stud.lastName],
gender: [stud.gender],
dob: [stud.dob],
qualification: [stud.qualification],
})
let formArray = this.myform.controls['resultList'] as FormArray;
formArray.push(studFg);
console.log(formArray.value)
}
FormBuilder - Creates an AbstractControl from a user-specified
configuration.
It is essentially syntactic sugar that shortens the new FormGroup(),
new FormControl(), and new FormArray() boilerplate that can build up
in larger forms.
Also, in html formControlName bound to <p> element, It's not an input and you can't bind to not form elements like div/p/span...:
<tbody>
<tr *ngFor="let item of myform.controls.resultList.controls; let i = index" [formGroupName]="i">
<td><p formControlName="firstName"></p></td> <==== Wrong element
</tr>
</tbody>
So, I think you just want to show added students in table. Then iterate over studList and show it's value in table:
<tbody>
<tr *ngFor="let item of studList; let i = index" [formGroupName]=i>
<td>
<p> {{item.firstName}} </p>
</td>
</tr>
</tbody>
Patching value
Take care when patching array. Because patchValue of FormArray patches values by the index:
patchValue(value: any[], options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
value.forEach((newValue: any, index: number) => {
if (this.at(index)) {
this.at(index).patchValue(newValue, {onlySelf: true, emitEvent: options.emitEvent});
}
});
this.updateValueAndValidity(options);
}
So, code below it patches the element at index=0: First indexed value of this.myform.controls['resultList'] as FormArray will be replaced with:
let stud1 = new Student();
stud1.firstName = 'FirstName';
stud1.lastName = 'LastName';
stud1.qualification = 'FFF';
formArray.patchValue([stud1]);
Your case doesn't work because patchValue requires some controls in array. In your case there is no controls in array. Look source code.
StackBlitz Demo
First try with this steps and make sure are you on correct way
Because in your scenario you are patching the object to formArray ,so you have to parse that object first & check once have you imported ReactiveFormsModule in your app.module.ts.
you have to co like this , code is taken from angular.io, you need to do setcontrol that will do or go though link there is code for the same it makes uses of Address array
this.setAddresses(this.hero.addresses);
setAddresses(addresses: Address[]) {
const addressFGs = addresses.map(address => this.fb.group(address));
const addressFormArray = this.fb.array(addressFGs);
this.heroForm.setControl('secretLairs', addressFormArray);
}
I'd prefer using FormBuilder to create form.
export class ComponentName implements OnInit {
form: FormGroup;
constructor(private fb: FormBuilder){}
ngOnInit() {
this.buildForm();
}
buildForm() {
this.form = this.fb.group({
firstName: '',
lastName: '',
...
resultList: this.fb.array([])
});
}
}
I believe the studlist will be obtained through API call as an observable rather than an static array. Let's assume, we data as follow.
resultModel =
{
firstName: "John",
lastName: "Doe",
....
resultList: [
{
prop1: value1,
prop2: value2,
prop3: value3
},
{
prop1: value1,
prop2: value2,
prop3: value3
}
...
]
}
Once the data is available, we can patch the values as follow:
patchForm(): void {
this.form.patchValue({
firstName: this.model.firstName,
lastName: this.model.lastName,
...
});
// Provided the FormControlName and Object Property are same
// All the FormControls can be patched using JS spread operator as
this.form.patchValue({
...this.model
});
// The FormArray can be patched right here, I prefer to do in a separate method
this.patchResultList();
}
// this method patches FormArray
patchResultList() {
let control = this.form.get('resultList') as FormArray;
// Following is also correct
// let control = <FormArray>this.form.controls['resultList'];
this.resultModel.resultList.forEach(x=>{
control.push(this.fb.group({
prop1: x.prop1,
prop2: x.prop2,
prop3: x.prop3,
}));
});
}
Array does not contain patchValue method. You have to iterate over controls and patchValue
each of them separately.
I am using the formgroup in formarray as:
this.formGroup = new FormGroup({
clientCode: new FormControl('', []),
clientName: new FormControl('', [Validators.required, Validators.pattern(/^[a-zA-Z0-9 _-]{0,50}$/)]),
type: new FormControl('', [Validators.required]),
description: new FormControl('', []),
industry: new FormControl('', []),
website: new FormControl('', [Validators.required, Validators.pattern(this.settings.regex.website)]),
businessEmail: new FormControl('', [Validators.pattern(this.settings.regex.email)]),
clients: this._formBuilder.array([this._formBuilder.group({
contactPerson: new FormControl('', [Validators.required]),
contactTitle: new FormControl('', [Validators.required]),
phoneNumber: new FormControl('', [Validators.required, Validators.pattern(this.settings.regex.phone)]),
emailId: new FormControl('', [Validators.required, Validators.pattern(this.settings.regex.email)]),
timeZone: new FormControl('', [Validators.required, Validators.pattern(this.settings.zipCode), Validators.minLength(5), Validators.maxLength(12)])
})])
})
For patch value I am using below method as:
let control = _this.formGroup.get('clients') as FormArray
clients.forEach(ele => {
control.push(_this._formBuilder.group({
contactPerson: new FormControl(ele.client_name, [Validators.required]),
contactTitle: new FormControl(ele.contact_title, [Validators.required]),
phoneNumber: new FormControl(ele.phone_number, [Validators.required, Validators.pattern(_this.settings.regex.phone)]),
emailId: new FormControl(ele.email_id, [Validators.required, Validators.pattern(_this.settings.regex.email)]),
timeZone: new FormControl(ele.timezone, [Validators.required, Validators.pattern(_this.settings.zipCode), Validators.minLength(5), Validators.maxLength(12)])
}))
});
Using this method we can validate the nested field as well.
Hope this may help.

Categories