How to save Dynamic Form Answer in Angular? - javascript

I'm trying to save some data that I display dynamic in the browser.This is my HTML where I iterate trough a list of exam questions dragged from the database and display them dynamic in browser.
<div mat-dialog-content [formGroup]="examform">
<div *ngFor="let exam_question of exam_questions;let i = index">
<mat-label >{{exam_question.questionTitle}}</mat-label><br>
<mat-radio-group aria-label="Image Position">
<mat-radio-button id="{{'examItem1'+i}}" value="1" >{{exam_question.questionItem1}}</mat-radio-button>
<mat-radio-button id="{{'examItem2'+i}}" value="2">{{exam_question.questionItem2}}</mat-radio-button>
<mat-radio-button id="{{'examItem3'+i}}" value="3">{{exam_question.questionItem3}}</mat-radio-button>
<mat-radio-button id="{{'examItem4'+i}}" value="4">{{exam_question.questionItem4}}</mat-radio-button>
</mat-radio-group>
</div>
<div>
<button class="button-course" mat-raised-button color="primary" (click)="submitAnswer()">Submit</button>
How can I use Form from Angular Typescript to save what the user responds to this form? For example what was the answer for the first question, for the second question and so on. I tried to put on every Form Controls and id, but I don t know how to go further.This is the form from Typescript.
import { registerLocaleData } from '#angular/common';
import { Component, OnInit } from '#angular/core';
import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '#angular/forms';
import { ActivatedRoute, Router } from '#angular/router';
import { Questions } from '../models/questions';
import { SharedService } from '../service/shared.service';
#Component({
selector: 'app-quiz-exam-page',
templateUrl: './quiz-exam-page.component.html',
styleUrls: ['./quiz-exam-page.component.css']
})
export class QuizExamPageComponent implements OnInit {
examID:any;
examform!: FormGroup;
examData: any;
userDetails:any;
examFormArray=new FormArray([new FormControl('',Validators.required)]);
constructor(private formBuilder:FormBuilder,private router:Router,private sharedService:SharedService,private route:ActivatedRoute) { }
exam_questions:Questions[]=[];
ngOnInit(): void {
this.route.queryParams
.subscribe(params => {
this.examID = params['examID'];
}
);
this.examform=this.formBuilder.group({
examTitle:this.formBuilder.array([],Validators.required),
examItem1:this.formBuilder.array([],Validators.required),
examItem2:this.formBuilder.array([],Validators.required),
examItem3:this.formBuilder.array([],Validators.required),
examItem4:this.formBuilder.array([],Validators.required)
});
if(localStorage.getItem('token')==null)
this.router.navigate(['login'])
this.sharedService.getUserProfile().subscribe(res=>{
this.userDetails=res;
this.sharedService.getExamQuestionsByID(this.examID).subscribe(data=>{
this.exam_questions=data;
},err=>{
console.log(err)
});
},(err:any)=>{
console.log(err);
},
);
}
submitAnswer(submitData:any)
{
this.examData = submitData;
console.log(this.examData)
}
}
This is the generated form. And as you can see, I need the answer for every question and after that I click submit, it will save the answer for every question, but I need to know to what question it was the answer, that is why I put the Id in the HTML.
This is my question array. It is an interface:
export interface Questions {
questionID:number;
questionCourseID:number;
questionTitle:string;
questionTopic:string;
questionPoints:string;
questionDifficulty:string;
questionItem1:string;
questionItem2:string;
questionItem3:string;
questionItem4:string;
qustionAnswers:string;
}

To your HTML-File I would add an NgSubmit and also add the type submit to your button. In the ngSubmit you give the value of your form (examForm) to the function (saveForm) in the Typescript-File. Instead I would delete the (click) event on your button.
HTML-File
<div mat-dialog-content [formGroup]="examform" (ngSubmit)="saveForm(examform.value)">
<div *ngFor="let exam_question of exam_questions;let i = index">
<mat-label >{{exam_question.questionTitle}}</mat-label><br>
<mat-radio-group aria-label="Image Position">
<mat-radio-button id="{{'examItem1'+i}}" value="1" >{{exam_question.questionItem1}}</mat-radio-button>
<mat-radio-button id="{{'examItem2'+i}}" value="2">{{exam_question.questionItem2}}</mat-radio-button>
<mat-radio-button id="{{'examItem3'+i}}" value="3">{{exam_question.questionItem3}}</mat-radio-button>
<mat-radio-button id="{{'examItem4'+i}}" value="4">{{exam_question.questionItem4}}</mat-radio-button>
</mat-radio-group>
</div>
<div>
<button type="submit" class="button-course" mat-raised-button color="primary">Submit</button>
If you want, that your button only is pressable if the inputs are valid (for example every required fields are filled out). You can add following code to your button:
<button [disabled]="!examForm?.valid" type="submit" class="button-course" mat-raised-button color="primary">Submit</button>
In your Typescript-File, you should write a function with the same name as you specified in (ngSubmit)="functionName". In my example, the function Name is saveData(). Then you write the value from the Form to a variable with the datatype any.
So your TS-File would look something like:
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup, Validators, FormControl } from '#angular/forms';
#Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./test.component.css']
})
export class TestComponent implements OnInit {
examForm!: FormGroup;
examData: any;
constructor(
private formBuilder: FormBuilder,
) { }
ngOnInit(): void {
this.examForm=this.formBuilder.group({
examTitle:this.formBuilder.array([],Validators.required),
examItem1:this.formBuilder.array([],Validators.required),
examItem2:this.formBuilder.array([],Validators.required),
examItem3:this.formBuilder.array([],Validators.required),
examItem4:this.formBuilder.array([],Validators.required)
});
}
showPreview(submitData: any) {
this.examData = submitData;
console.log(this.examData)
}
}

Related

Angular Material dialog: is there a way not to use the data property as "data.*" in the template?

I have a template like the one below:
card.component.html
<mat-card class="mat-elevation-z4">
<img mat-card-image src="{{ item.media_url }}" />
<mat-card-content class="custom">
<p>{{ item.caption }}</p>
</mat-card-content>
</mat-card>
It is a Material Card used inside a parent View that creates a grid with cards, each having a specific item.media_url and item.caption
events.component.ts
<div class="content">
<div fxLayout="row wrap" fxLayoutGap="16px grid">
<div
fxFlex="25%"
fxFlex.md="33%"
fxFlex.sm="50%"
fxFlex.xs="100%"
*ngFor="let item of events"
>
<app-card [item]="item" #myCard></app-card>
<button mat-button (click)="openDialog(myCard)">SCOPRI DI PIU'</button>
</div>
</div>
</div>
As you can see, inside the ngFor loop, below each card, there's a button that is supposed to open a dialog showing only one card at a time (the one above the clicked button in the grid).
I would like to put very the same card with its item.media_url and item.caption in this dialog, so I thought to use the data property of MatDialog to do this.
card.component.ts
import { Component, OnInit, Input, Inject, Optional } from '#angular/core';
import { MAT_DIALOG_DATA } from '#angular/material/dialog';
#Component({
selector: 'app-card',
templateUrl: './card.component.html',
styleUrls: ['./card.component.css']
})
export class CardComponent implements OnInit {
#Input() item : any;
constructor(#Optional() #Inject(MAT_DIALOG_DATA) public data: any) { }
ngOnInit(): void {
}
}
events.component.ts
import { Component, OnInit } from '#angular/core';
import { EventsService } from '../../shared/services/events.service';
import { MatDialog } from '#angular/material/dialog';
import { CardComponent } from 'src/app/shared/card/card.component';
#Component({
selector: 'app-events',
templateUrl: './events.component.html',
styleUrls: ['./events.component.css'],
})
export class EventsComponent implements OnInit {
events: any[];
constructor(private eventsService: EventsService, public dialog: MatDialog) {}
ngOnInit(): void {
this.getEvents();
}
getEvents(): void {
this.eventsService.getEvents().subscribe((response: any) => {
this.events = response.data;
console.log(this.events);
});
}
openDialog(card: any) {
this.dialog.open(CardComponent, {
data: {
item: card,
},
});
}
}
However, when I do this, I need to pass data to this.dialog.open(), not item, nor card.
Right now I am getting the error ERROR TypeError: ctx.item is undefined (which I perfectly understand why I am getting).
Is there a way I can somehow "alias" data as item, or maybe a better way to do this?
To make it work we did the following:
openDialog(card: CardComponent) { <-- strongly type
console.log(card.item); <-- access item
this.dialog.open(CardComponent, {
data: {
item: card.item <-- pass it as param
}
});
}
next in card.component.ts set the 'item` property.
constructor(#Optional() #Inject(MAT_DIALOG_DATA) public data: any) {
if (data) { <-- only if data is injected
this.item = data.item; <-- set item
}
}
Working Stackblitz
Robert's coment under your question answers the template / component part.
As for the data alias question, the answer is no. data is a property of the MatDialogConfig class, as shown here and to the best of my knowledge, you can't change it.

How to pop up a modal from click in dropdown list option

Good day developers , im working in this app with angular , and now im trying to once one of the options
get clicked , to show a modal tag .
Basically what i did was create a paralell template equal to the item selected on the dropdown , and over this template using the a tag i set all the logic to show the modal, but guess isn't user friendly cause of couple extra clicks.Trying to set the a tag inside the options also wasn't viable cause the my dropdown didn't work.Here a mock about what i did:
HTML tag
<select [hidden]="!state" name="optionsInc" required [(ngModel)]="optionsInc" (change)="subItemSelected($event)">
<option value="select" [ngValue]="null" [disabled]="true">Select Income</option>
<option *ngFor="let item of allKeysIncomings" label="{{item}}" value="{{item}}"></option>
</select>====>DROPDOWN LIST LOGIC
<p [hidden]="!state"> <a *ngIf="incomeSelected"
href="#"
class="btn btn-primary btn-block"
data-toggle="modal"
data-target="#editItem"
>{{incomeSelected}}</a>
</p>====>PARALELL REFERENCE TO POP THE MODAL UP
<div class="modal fade" id='editItem'>======>MODAL
SOME TAGS AND CODE
</div>
then on my component i did this :
imports...
#Component({
selector: 'app-user-sheet-balance',
templateUrl: './user-sheet-balance.component.html',
styleUrls: ['./user-sheet-balance.component.css'],
})
export class UserSheetBalanceComponent implements OnInit {
allKeysIncomings: any;==>ITERABLE
incomeSelected: string;
constructor(some code) {}
ngOnInit(): void {some code}
async subItemSelected(event) {
SOME CODE
return (
await (this.incomeSelected = event.target.value),
);
}
All this process does the task on activate the modal once i click the tag a, but instead of creating that paralell reference to the dropdown, im wondering if is possible to do it straight from the dropdown in fact.
I have been watching some similar issues on the comunity like
:Open a Modal Using an Option from a Dropdown - Angular 2 + ngx
but doesn't work on my code specifications.
Any updated idea about this ?.Thanks in advance!!!!!!
if you have Component with dialog layout in ModalComponent it should work as follow
import { Injectable } from '#angular/core';
import { MatDialog, MatDialogRef } from '#angular/material/dialog';
import { ModalComponent } from './modal/modal.component';
#Injectable({
providedIn: 'root'
})
export class TestDialogService {
dialogRef: MatDialogRef<ModalComponent, any>;
constructor(public dialog: MatDialog) { }
open() {
if(this.dialogRef) {
this.dialogRef.close();
}
this.dialogRef = this.dialog.open(ModalComponent, {
panelClass: 'app-dialog'
});
}
close() {
if(this.dialogRef) {
this.dialogRef.close();
}
}
}
// html
<mat-form-field>
<mat-label>Favorite car</mat-label>
<select name="optionsInc"
matNativeControl
[(ngModel)]="optionsInc"
(ngModelChange)="onModelChange()">
<option value="select" [value]="null" [disabled]="true">Select Income</option>
<option *ngFor="let item of allKeysIncomings" [label]="item.viewValue"
[value]="item.value"></option>
</select>
</mat-form-field>
// ts
#Component({
selector: 'app-root',
templateUrl: "./app.component.html",
styleUrls: ["./app.component.scss"]
})
export class AppComponent {
state = false;
optionsInc = null;
allKeysIncomings = [
{value: 'volvo', viewValue: 'Volvo'},
{value: 'saab', viewValue: 'Saab'},
{value: 'mercedes', viewValue: 'Mercedes'}
];
constructor(
public testDialogService: TestDialogService) {
}
onModelChange() {
this.testDialogService.open();
}
}
example

Prime-NG Confirm Dialog does not work in a service

I just noticed a strange behavior of PrimeNG's Confirm Dialog. In test.component.html, there is an Input Field. It checks if the typed in value is greater than 150. If it is greater, then a Confirm Button shows up below the Input Field ("Please confirm"). Clicking it shows a Dialog with Yes and No.
The Confirm Button shall vanish after choosing either Yes or No.
Now here's the problem:
Vanishing only works if the confirm method is called directly in test.component.ts. I'd like to extract it into a service (customConfirmation.service.ts), but the vanishing does not work there. Do you know why? I have got absolutely no idea. ("this.messagesWeightTest" and the button vanishing do not work.)
test.component.html
<div class="p-col-12 p-md-6 p-lg-5">
Weight:
<div class="ui-inputgroup">
<input pInputText type="number" id="weight" name="weight" [(ngModel)]="newTest.testWeight"
placeholder="---">
<span class="ui-inputgroup-addon">kg</span>
</div>
<div *ngIf="validateIfWeightOutsideRange()">
<div>
<p-confirmDialog key="confirmWeightTest"></p-confirmDialog>
<button type="button" (click)="confirmWeightTest()" pButton icon="pi pi-check"
label="Please confirm!">
</button>
<p-messages [value]="messagesWeightTest"></p-messages>
</div>
</div>
</div>
It seems that the whole "accept" and "reject" do not work:
customConfirmation.service.ts
import {Injectable} from '#angular/core';
import {SessionService} from "./session.service";
import {ConfirmationService, Message} from "primeng/api";
#Injectable()
export class CustomConfirmationService {
messagesWeightTest: Message[] = [];
weightConfirmed: boolean = false;
constructor(private confirmationService: ConfirmationService) {}
confirmWeightTest() {
this.confirmationService.confirm({
message: 'Are you sure?',
header: 'Confirmation',
icon: 'pi pi-exclamation-triangle',
key: 'confirmWeightTest',
accept: () => {
this.messagesWeightTest = [{
severity: 'info', summary: 'Confirmed', detail: 'The input is correct.'}];
this.weightConfirmed = true;
},
reject: () => {
this.sessionService.newTest.testWeight = null;
this.weightConfirmed = true;
}
});
}
}
test.component.ts just calls the confirmation method from the service:
test.component.ts
import {Component, Injectable, Input, OnInit} from '#angular/core';
import {ConfirmationService, Message, SelectItem} from "primeng/api";
import {trigger, state, style, transition, animate} from '#angular/animations';
import {FormBuilder, FormControl, FormGroup, Validators} from "#angular/forms";
import {CustomConfirmationService} from "../services/customConfirmation.service";
import {ValidationService} from "../services/validation.service";
#Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./test.component.css']
})
#Injectable()
export class TestComponent implements OnInit {
constructor(private fb: FormBuilder,
private customConfirmationService: CustomConfirmationService,
private confirmationService: ConfirmationService,
private validationService: ValidationService) {
}
ngOnInit() {}
// Confirmations for ConfirmDialogs
confirmWeightTest() {
this.customConfirmationService.confirmWeightTest();
}
// Validations for ConfirmDialogs --> work!
validateIfWeightOutsideRange() {
return !!this.validationService.validateIfWeightOutsideRange();
}
Again, if I copy and paste confirmWeightTest() from customConfirmation.service.ts into test.component.ts, everything works fine. I also tested this with another project.
I would be glad if you could tell me whats going on here.
Please also refer to Prime-NG Confirm Dialog: Hide the Button after Confirmation
I implemented everything as recommended in the answer to this question. Thanks!
I created a demo and everything work as well
You need to declare CustomConfirmationService in providers and update
<p-messages [(value)]="messagesWeightTest"></p-messages>
to
<p-messages [(value)]="customConfirmationService.messagesWeightTest"></p-messages>

Angular 5.2 - bypass form validation for [routerLink] while keeping focus

I have completely reworded this question and included a complete code sample.
I have an intermittent issue where clicking the button sometimes shows the validation error message, instead of executing the router.nagivate command. Then, I have to click it a second to work. As I said, this is intermittent. The solution needs to include the focus behavior of the sample below, or an alternative way to focus on input html tags. Sometimes, I only have to click once. Why? And, how can I control this behavior so that it is not random?
I am posting two test components to demonstrate the issue. Any help would be greatly appreciated.
test.component.html
<form novalidate #f="ngForm">
<h2>Scan Part</h2>
<input id="partNum" type="number" class="form-control" required [correctPart]="orderShipDetail?.UPC" name="partNum" [(ngModel)]="model.partNum" #partNum="ngModel" #partNumRef />
<div *ngIf="partNum.invalid && (partNum.dirty || partNum.touched)" class="text-danger">
<p *ngIf="partNum.errors.required">PartNum is required.</p>
<p *ngIf="partNum.errors.correctPart">Please scan the correct part. </p>
</div>
<button type="button" (click)="onClickCartonPartButton()">Carton Parts</button>
</form>
test.component.ts
import { Component, OnInit, AfterViewChecked, ViewChild, ElementRef } from '#angular/core';
import { Router } from '#angular/router';
class TestForm {
constructor(
public partNum: string = '') {
}
}
#Component({
selector: 'test',
templateUrl: './test.component.html',
styleUrls: ['./test.component.scss']
})
export class TestComponent implements OnInit, AfterViewChecked {
#ViewChild('partNumRef') partNumRef: ElementRef;
model: TestForm = new TestForm();
public focusedElement: ElementRef;
constructor(
private router: Router,
private route: ActivatedRoute
) { }
ngAfterViewChecked() {
this.focusedElement.nativeElement.focus();
}
ngOnInit() {
this.focusedElement = this.partNumRef;
}
onClickCartonPartButton() {
try {
this.router.navigate(['/test2', 1006, 1248273, 1234]);
} catch (ex) {
console.log(ex);
}
}
}
test2.component.html
<a [routerLink]="['/test', 1006, 1248273, 1234, 5 ]">click this</a>
test2.component.ts
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'test2',
templateUrl: './test2.component.html',
styleUrls: ['./test2.component.scss']
})
export class Test2Component implements OnInit {
constructor() { }
ngOnInit() {
}
}
Add these routes to the app.module.ts
{ path: 'test/:empId/:orderNumber/:licensePlate/:orderLine', component: TestComponent },
{ path: 'test2/:empId/:orderNumber/:licensePlate', component: Test2Component },
Set type="button" on the two hyperlinks to avoid submit
A button element with no type attribute specified represents the same
thing as a button element with its type attribute set to "submit".
Or put the two hyperlinks outside of the form
Remove partNum.touched from *ngIf in the validation message div, like this...
<div *ngIf="partNum.invalid && partNum.dirty" class="text-danger">
<p *ngIf="partNum.errors.required">PartNum is required.</p>
<p *ngIf="partNum.errors.correctPart">Please scan the correct part.</p>
</div>

How can I pass the FormGroup of a parent component to its child component using the current Form API

I would like to pass the parent component's FormGroup to its child for the purpose of displaying an error-message using the child.
Given the following parent:
parent.component.ts
import { Component, OnInit } from '#angular/core'
import {
REACTIVE_FORM_DIRECTIVES, AbstractControl, FormBuilder, FormControl, FormGroup, Validators
} from '#angular/forms'
#Component({
moduleId: module.id,
selector: 'parent-cmp',
templateUrl: 'language.component.html',
styleUrls: ['language.component.css'],
directives: [ErrorMessagesComponent]
})
export class ParentCmp implements OnInit {
form: FormGroup;
first: AbstractControl;
second: AbstractControl;
constructor(private _fb: FormBuilder) {
this.first = new FormControl('');
this.second = new FormControl('')
}
ngOnInit() {
this.form = this._fb.group({
'first': this.first,
'second': this.second
});
}
}
I would now like to pass the form:FormGroup variable above to the child component below:
error-message.component.ts
import { Component, OnInit, Input } from '#angular/core'
import { NgIf } from '#angular/common'
import {REACTIVE_FORM_DIRECTIVES, FormGroup } from '#angular/forms'
#Component({
moduleId: module.id,
selector: 'epimss-error-messages',
template: `<span class="error" *ngIf="errorMessage !== null">{{errorMessage}}</span>`,
styles: [],
directives: [REACTIVE_FORM_DIRECTIVES, NgIf]
})
export class ErrorMessagesComponent implements OnInit {
#Input() ctrlName: string
constructor(private _form: FormGroup) { }
ngOnInit() { }
get errorMessage() {
// Find the control in the Host (Parent) form
let ctrl = this._form.find(this.ctrlName);
console.log('ctrl| ', ctrl);
// for (let propertyName of ctrl.errors) {
// // If control has a error
// if (ctrl.errors.hasOwnProperty(propertyName) && ctrl.touched) {
// // Return the appropriate error message from the Validation Service
// return CustomValidators.getValidatorErrorMessage(propertyName);
// }
// }
return null;
}
The constructor formGroup represents the FormGroup of the parent - in its present form it does not work.
I am trying to follow this obsolete example at http://iterity.io/2016/05/01/angular/angular-2-forms-and-advanced-custom-validation/
In the parent component do this:
<div [formGroup]="form">
<div>Your parent controls here</div>
<your-child-component [formGroup]="form"></your-child-component>
</div>
And then in your child component you can get hold of that reference like so:
export class YourChildComponent implements OnInit {
public form: FormGroup;
// Let Angular inject the control container
constructor(private controlContainer: ControlContainer) { }
ngOnInit() {
// Set our form property to the parent control
// (i.e. FormGroup) that was passed to us, so that our
// view can data bind to it
this.form = <FormGroup>this.controlContainer.control;
}
}
You can even ensure either formGroupName or [formGroup] is specified on your component by changing its selector like so:
selector: '[formGroup] epimss-error-messages,[formGroupName] epimss-error-messages'
This answer should be sufficient for your needs, but if you want to know more I've written a blog entry here:
https://mrpmorris.blogspot.co.uk/2017/08/angular-composite-controls-formgroup-formgroupname-reactiveforms.html
For Angular 11 I tried all the above answers, and in different combinations, but nothing quite worked for me. So I ended up with the following solution which worked for me just as I wanted.
TypeScript
#Component({
selector: 'fancy-input',
templateUrl: './fancy-input.component.html',
styleUrls: ['./fancy-input.component.scss']
})
export class FancyInputComponent implements OnInit {
valueFormGroup?: FormGroup;
valueFormControl?: FormControl;
constructor(
private formGroupDirective: FormGroupDirective,
private formControlNameDirective: FormControlName
) {}
ngOnInit() {
this.valueFormGroup = this.formGroupDirective.form;
this.valueFormControl = this.formGroupDirective.getControl(this.formControlNameDirective);
}
get controlName() {
return this.formControlNameDirective.name;
}
get enabled() {
return this.valueFormControl?.enabled
}
}
HTML
<div *ngIf="valueFormGroup && valueFormControl">
<!-- Edit -->
<div *ngIf="enabled; else notEnabled" [formGroup]="valueFormGroup">
<input class="input" type="text" [formControlName]="controlName">
</div>
<!-- View only -->
<ng-template #notEnabled>
<div>
{{valueFormControl?.value}}
</div>
</ng-template>
</div>
Usage
Note that I had to add ngDefaultControl otherwise it would give no default value accessor error in console (if somebody knows how to get rid of it without error - will be much appreciated).
<form [formGroup]="yourFormGroup" (ngSubmit)="save()">
<fancy-input formControlName="yourFormControlName" ngDefaultControl></fancy-input>
</form>
this is an example of child component used inside parent formGroup :
child component ts:
import { Component, OnInit, Input } from '#angular/core';
import { FormGroup, ControlContainer, FormControl } from '#angular/forms';
#Component({
selector: 'app-date-picker',
template: `
<mat-form-field [formGroup]="form" style="width:100%;">
<input matInput [matDatepicker]="picker" [placeholder]="placeHolder" [formControl]="control" readonly>
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
</mat-form-field>
<mat-icon (click)="clearDate()">replay</mat-icon>`,
styleUrls: ['./date-picker.component.scss']
})
export class DatePickerComponent implements OnInit {
public form: FormGroup;
public control : FormControl;
#Input() controlName : string;
#Input() placeHolder : string;
constructor(private controlContainer: ControlContainer) {
}
clearDate(){
this.control.reset();
}
ngOnInit() {
this.form = <FormGroup>this.controlContainer.control;
this.control = <FormControl>this.form.get(this.controlName);
}
}
css date picker :
mat-icon{
position: absolute;
left: 83%;
top: 31%;
transform: scale(0.9);
cursor: pointer;
}
and used like this :
<app-date-picker class="col-md-4" [formGroup]="feuilleForm" controlName="dateCreation" placeHolder="Date de création"></app-date-picker>
Parent Component :
#Component({
selector: 'app-arent',
templateUrl: `<form [formGroup]="parentFormGroup" #formDir="ngForm">
<app-child [formGroup]="parentFormGroup"></app-child>
</form> `
})
export class ParentComponent implements {
parentFormGroup :formGroup
ngOnChanges() {
console.log(this.parentFormGroup.value['name'])
}
}
Child Component :
#Component({
selector: 'app-Child',
templateUrl: `<form [formGroup]="childFormGroup" #formDir="ngForm">
<input id="nameTxt" formControlName="name">
</form> `
})
export class ChildComponent implements OnInit {
#Input() formGroup: FormGroup
childFormGroup :FormGroup
ngOnInit() {
// Build your child from
this.childFormGroup.addControl('name', new FormControl(''))
/* Bind your child form control to parent form group
changes in 'nameTxt' directly reflect to your parent
component formGroup
*/
this.formGroup.addControl("name", this.childFormGroup.controls.name);
}
}
The ngOnInit was important - this did not work in the constructor.
And I prefer looking for the FormControlDirective - its the first one found in the child component's ancestor hierarchy
constructor(private formGroupDirective: FormGroupDirective) {}
ngOnInit() {
this.formGroupDirective.control.addControl('password', this.newPasswordControl);
this.formGroupDirective.control.addControl('confirmPassword', this.confirmPasswordControl);
this.formGroup = this.formGroupDirective.control;
}
I would do this in this way, i have passed child form data as group to parent so you can have separated form data in submit call.
Parent:
<form [formGroup]="registerStudentForm" (ngSubmit)="onSubmit()">
<app-basic-info [breakpoint]="breakpoint" [formGroup]="registerStudentForm"></app-basic-info>
<button mat-button>Submit</button>
</form>
Child:
<mat-card [formGroup]="basicInfo">
<mat-card-title>Basic Information</mat-card-title>
<mat-card-content>
<mat-grid-list
[gutterSize]="'20px'"
[cols]="breakpoint"
rowHeight="60px"
>
<mat-grid-tile>
<mat-form-field appearance="legacy" class="full-width-field">
<mat-label>Full name</mat-label>
<input matInput formControlName="full_name" />
</mat-form-field>
</mat-grid-tile>
</mat-grid-list>
</mat-card-content>
</mat-card>
Parent.ts:
export class RegisterComponent implements OnInit {
constructor() { }
registerForm = new FormGroup({});
onSubmit() {
console.warn(this.registerForm.value);
}
}
Child.ts
export class BasicInfoComponent implements OnInit {
#Input() breakpoint;
#Input() formGroup: FormGroup;
basicInfo: FormGroup;
constructor() { }
ngOnInit(): void {
this.basicInfo = new FormGroup({
full_name: new FormControl('Riki maru'),
dob: new FormControl(''),
});
this.formGroup.addControl('basicInfo', this.basicInfo);
}
}
Here in your child form components #Input() formGroup: FormGroup; part would be reference of parent component
I would pass the form as an input to the child component;
#Component(
{
moduleId: module.id,
selector: 'epimss-error-messages',
template: `
<span class="error" *ngIf="errorMessage !== null">{{errorMessage}}</span>`,
styles: [],
directives: [REACTIVE_FORM_DIRECTIVES, NgIf]
})
export class ErrorMessagesComponent implements OnInit {
#Input()
ctrlName: string
#Input('form') _form;
ngOnInit() {
this.errorMessage();
}
errorMessage() {
// Find the control in the Host (Parent) form
let ctrl = this._form.find(this.ctrlName);
console.log('ctrl| ', ctrl)
// for (let propertyName of ctrl.errors) {
// // If control has a error
// if (ctrl.errors.hasOwnProperty(propertyName) && ctrl.touched) {
// // Return the appropriate error message from the Validation Service
// return CustomValidators.getValidatorErrorMessage(propertyName);
// }
// }
return null;
}
And of course you'll need o pass the form from the parent component to the child, which you can do it in different ways , but the simplest is :
Somewhere in your parent ;
<epimss-error-messages [form]='form'></epimss-error-messages>
If you want to access the parent from the child component, you can access parent property of the FormControl instance, https://angular.io/api/forms/AbstractControl#parent
To get the parent error:
const parent = control.parent;
const errors = parent.errors;

Categories