How to Clear the ngModel value coming from other component? - javascript

I have a component in which [(ngModel)]=attrProps._value is getting set on user input and also getting value when already present by default.
Now there is a button which leads to a toggle button for value option when the user wants to have custom value.
But when the user clicks on the refresh button the value in the input field should get clear?
#Component({
selector: 'n-customValue',
template: `
<mat-form-field class="inputfield-attr-view">
<input matInput [(ngModel)]="attrProps._value" placeholder="{{bKeeperService.getAttributeType(attrProps).displayAs}}">
<mat-icon matSuffix (click)="revertIcon()" class="editButton">
<img class="attributeview-button" src="../../assets/icons/refresh.svg">
</mat-icon>
</mat-form-field>
`
})
export class customValueComponent implements OnInit {
#Output() clickEvent: EventEmitter<string> = new EventEmitter<string>();
constructor(public myservice: customService) { }
ngOnInit(): void { }
revertIcon() {
this.clickEvent.emit('REVERT_EVENT');
}
}
I tried using element ref but could not solve it.
#Component({
selector: 'n-customvalue',
template: `
<mat-form-field class="inputfield-attr-view">
<input matInput [(ngModel)]="attrProps._value" #searchInput placeholder="{{bKeeperService.getAttributeType(attrProps).displayAs}}">
<mat-icon matSuffix (click)="revertIcon()" class="editButton">
<img class="attributeview-button" src="../../assets/icons/refresh.svg">
</mat-icon>
</mat-form-field>
`
})
export class customvalueComponent implements OnInit {
#Output() clickEvent: EventEmitter<string> = new EventEmitter<string>();
#Input('nAttrProps') nAttrProps;
#ViewChild('searchInput') searchInput: ElementRef;
attrPropsValue;
constructor(public myservice: customService) { }
ngOnInit(): void { }
revertIcon() {
this.clickEvent.emit('REVERT_EVENT');
this.searchInput.nativeElement.value = '';
}
}

Related

Unable to populate tick mark on angular checkbox

I have angular code something like this: and I want to populate checkbox ticked for the option which are default selected
export class InformationComponent implements OnInit{
dataIds: FormControl;
#Input()
requestBody:Request;
requestForm: FormGroup;
constructor(private _formBuilder: FormBuilder){}
ngOnInit(){
this.dataIds = new FormControl(this.requestBody.dataIds);
this.requestForm = this._formBuilder.group({
dataIds: this.dataIds;
})
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<form [formGroup]="reqForm" #mainForm>
<div>
<app-data-list [dataIds]="dataIds" [disabled]="disabled"> </app-data-list>
</div>
</form>
and below is my app-data-list component
export class EntityListComponent implemments OnInit, OnDestroy{
#Input()
public disabled:boolean;
public entitiesFilter:FormControl = new FormControl();
protected entityList = new RequestEntity[];
#Input()
public dataIds:FormControl;
#ViewChild('multiSelect', {static:true}) multiselect:MatSelect;
protected _onDestroy = new Subject<void>();
public filteredEntityList:ReplaySubject<RequestEntity[]> = new ReplaySubject<RequestEntity[]>
ngOnInit(){
this.myservice.getData().subscribe((resp:RequestEntity[])=>{
entityList = resp;
})
this.filteredEntityList.next(this.entityList.slice());
this.entitiesFilter.valueChanges.pipe(takeUntil(this._onDestroy)).subscribe(() => { this.filterEntitiesList();});
}
ngAfterViewInit(){
this.setInitialValue();
}
setInitialValue(){
this.filteredEntitiesList.pipe(take(1), takeUntil(_onDestroy)).subscribe(() => {
this.multiSelect.compareWith =(a:RequestEntity, b:RequestEntity) => a && b && a.entity == b.entity;
})
}
}
<div [formGroup]="form">
<mat-form-field>
<mat-label> Data ids</mat-label>
<mat-select [formControl]="dataIds" [multiple]="true" #multiSelect [disabled]="disabled">
<ngx-mat-select-search [formControl]="entitiesFilter" placeholderLabel="Search">
</ngx-mat-select-search>
<mat-option *ngFor="let entity of filterdEntitiesList |async" [value]"entity"> {{entity?.entity?.entityName}} </mat-option>
</mat-select>
</mat-form-field>
</div>
but my code is not pre populating the checked option against selected dataIds from back end but I had similar snippet in my code which does exactly same thing

Angular dynamic html at the click of a button

I am creating an HTML form in Angular. I am running into an issue. I want to be able to display a duplicate of an HTML block with a new form control at the click of a button.
Here is what thing look like now:
I would like the user to be able to click the button labeled Click me and have a duplicate of the HTML block display but with a different form control. Do you guys have any suggestions of how I can do that? Here is what I have so far.
import { Component, OnInit, Directive, ViewChild } from '#angular/core';
import { FormControl, FormGroupDirective, NgForm, Validators, FormBuilder, FormGroup } from '#angular/forms';
#Component({
selector: 'app-questions',
templateUrl: './questions.component.html',
styleUrls: ['./questions.component.scss']
})
export class QuestionsComponent implements OnInit {
jobForm: FormGroup = this.fb.group({
companyName: this.fb.control('', [Validators.required ]),
position: this.fb.control('', [Validators.required ]),
description: this.fb.control('', [Validators.required ]),
startDate: this.fb.control('', [Validators.required ]),
endDate: this.fb.control('', [Validators.required ])
});
constructor(private readonly fb: FormBuilder) { }
ngOnInit(): void {
}
displayForm() {
console.log(this.jobForm);
}
}
<h3>Education:</h3>
<form [formGroup]="jobForm">
<mat-form-field >
<mat-label>Company Name: </mat-label>
<input matInput type="text" formControlName="companyName"/>
<mat-error *ngIf="jobForm.controls.companyName.errors">Company name is required</mat-error>
</mat-form-field>
<mat-form-field >
<mat-label>Position: </mat-label>
<input matInput type="text" formControlName="position"/>
<mat-error *ngIf="jobForm.controls.position.errors">Position is required</mat-error>
</mat-form-field>
<mat-form-field >
<mat-label>Select start and end date:</mat-label>
<mat-date-range-input [rangePicker]="picker">
<input matStartDate placeholder="Start date" formControlName="startDate">
<input matEndDate placeholder="End date" formControlName="endDate">
</mat-date-range-input>
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-date-range-picker #picker></mat-date-range-picker>
</mat-form-field>
<mat-form-field >
<mat-label>Description: </mat-label>
<textarea matInput type="text" formControlName="description"></textarea>
<mat-error *ngIf="jobForm.controls.description.errors">Job description is required</mat-error>
</mat-form-field>
</form>
<button (click)="displayForm()">Click me</button>
After a user hits the click me button id like to generate a duplicate form so they can fill out the details.
Thanks
I believe what you're asking for is a way to dynamically add a form group to the page. If that is the case then the solution below should help.
You can use the *ngFor structural directive to iterate over a FormGroup array. The following adjustments will need to be made:
import { Component, OnInit, Directive, ViewChild } from '#angular/core';
import { FormControl, FormGroupDirective, NgForm, Validators, FormBuilder, FormGroup } from '#angular/forms';
#Component({
selector: 'app-questions',
templateUrl: './questions.component.html',
styleUrls: ['./questions.component.scss']
})
export class QuestionsComponent implements OnInit {
jobForms: FormGroup[] = []; // Declare an empty array
constructor(private readonly fb: FormBuilder) { }
ngOnInit(): void {
this.addFormRow(); // Add an empty form group to the array
}
//displayForm() {
// console.log(this.jobForm);
//}
// Add an additional row to the jobForms array - to be called from the template
addFormRow() {
this.jobForms.push(this.fb.group({
companyName: this.fb.control('', [Validators.required ]),
position: this.fb.control('', [Validators.required ]),
description: this.fb.control('', [Validators.required ]),
startDate: this.fb.control('', [Validators.required ]),
endDate: this.fb.control('', [Validators.required ])
}));
}
}
<h3>Education:</h3>
<form *ngFor="let formGroup of jobForms"
[formGroup]="formGroup">
<mat-form-field >
<mat-label>Company Name: </mat-label>
<input matInput type="text" formControlName="companyName"/>
<mat-error *ngIf="jobForm.controls.companyName.errors">Company name is required</mat-error>
</mat-form-field>
<mat-form-field >
<mat-label>Position: </mat-label>
<input matInput type="text" formControlName="position"/>
<mat-error *ngIf="jobForm.controls.position.errors">Position is required</mat-error>
</mat-form-field>
<mat-form-field >
<mat-label>Select start and end date:</mat-label>
<mat-date-range-input [rangePicker]="picker">
<input matStartDate placeholder="Start date" formControlName="startDate">
<input matEndDate placeholder="End date" formControlName="endDate">
</mat-date-range-input>
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-date-range-picker #picker></mat-date-range-picker>
</mat-form-field>
<mat-form-field >
<mat-label>Description: </mat-label>
<textarea matInput type="text" formControlName="description"></textarea>
<mat-error *ngIf="jobForm.controls.description.errors">Job description is required</mat-error>
</mat-form-field>
</form>
<!-- Call addFormRow() to add a FormGroup to the array -->
<button (click)="addFormRow()">Click me</button>
If you can use Lodash library it would be easy
//const newjobFormGroup = _.cloneDeep(jobForm) as FormGroup;
CopyJobForm(toCopyForm: FormGroup){
//return _.cloneDeep(jobForm) as FormGroup;
return _.cloneDeep(FormGroup) as FormGroup;
}
Or
this answer for a full deep clone
/**
* Deep clones the given AbstractControl, preserving values, validators, async validators, and disabled status.
* #param control AbstractControl
* #returns AbstractControl
*/
export function cloneAbstractControl<T extends AbstractControl>(control: T): T {
let newControl: T;
if (control instanceof FormGroup) {
const formGroup = new FormGroup({}, control.validator, control.asyncValidator);
const controls = control.controls;
Object.keys(controls).forEach(key => {
formGroup.addControl(key, cloneAbstractControl(controls[key]));
})
newControl = formGroup as any;
}
else if (control instanceof FormArray) {
const formArray = new FormArray([], control.validator, control.asyncValidator);
control.controls.forEach(formControl => formArray.push(cloneAbstractControl(formControl)))
newControl = formArray as any;
}
else if (control instanceof FormControl) {
newControl = new FormControl(control.value, control.validator, control.asyncValidator) as any;
}
else {
throw new Error('Error: unexpected control value');
}
if (control.disabled) newControl.disable({emitEvent: false});
return newControl;
}

Angular: mat-form-field must contain a MatFormFieldControl

I am trying to add a form field with custom telephone number input control. I used the example of the phone from https://material.angular.io/components/form-field/examples.
Here is the code:
<mat-form-field>
<example-tel-input placeholder="Phone number" required></example-tel-input>
<mat-icon matSuffix>phone</mat-icon>
<mat-hint>Include area code</mat-hint>
</mat-form-field>
import {FocusMonitor} from '#angular/cdk/a11y';
import {coerceBooleanProperty} from '#angular/cdk/coercion';
import {Component, ElementRef, Input, OnDestroy} from '#angular/core';
import {FormBuilder, FormGroup} from '#angular/forms';
import {MatFormFieldControl} from '#angular/material';
import {Subject} from 'rxjs';
/** #title Form field with custom telephone number input control. */
#Component({
selector: 'form-field-custom-control-example',
templateUrl: 'form-field-custom-control-example.html',
styleUrls: ['form-field-custom-control-example.css'],
})
export class FormFieldCustomControlExample {}
/** Data structure for holding telephone number. */
export class MyTel {
constructor(public area: string, public exchange: string, public subscriber: string) {}
}
/** Custom `MatFormFieldControl` for telephone number input. */
#Component({
selector: 'example-tel-input',
templateUrl: 'example-tel-input-example.html',
styleUrls: ['example-tel-input-example.css'],
providers: [{provide: MatFormFieldControl, useExisting: MyTelInput}],
host: {
'[class.example-floating]': 'shouldLabelFloat',
'[id]': 'id',
'[attr.aria-describedby]': 'describedBy',
}
})
export class MyTelInput implements MatFormFieldControl<MyTel>, OnDestroy {
static nextId = 0;
parts: FormGroup;
stateChanges = new Subject<void>();
focused = false;
ngControl = null;
errorState = false;
controlType = 'example-tel-input';
id = `example-tel-input-${MyTelInput.nextId++}`;
describedBy = '';
get empty() {
const {value: {area, exchange, subscriber}} = this.parts;
return !area && !exchange && !subscriber;
}
get shouldLabelFloat() { return this.focused || !this.empty; }
#Input()
get placeholder(): string { return this._placeholder; }
set placeholder(value: string) {
this._placeholder = value;
this.stateChanges.next();
}
private _placeholder: string;
#Input()
get required(): boolean { return this._required; }
set required(value: boolean) {
this._required = coerceBooleanProperty(value);
this.stateChanges.next();
}
private _required = false;
#Input()
get disabled(): boolean { return this._disabled; }
set disabled(value: boolean) {
this._disabled = coerceBooleanProperty(value);
this.stateChanges.next();
}
private _disabled = false;
#Input()
get value(): MyTel | null {
const {value: {area, exchange, subscriber}} = this.parts;
if (area.length === 3 && exchange.length === 3 && subscriber.length === 4) {
return new MyTel(area, exchange, subscriber);
}
return null;
}
set value(tel: MyTel | null) {
const {area, exchange, subscriber} = tel || new MyTel('', '', '');
this.parts.setValue({area, exchange, subscriber});
this.stateChanges.next();
}
constructor(fb: FormBuilder, private fm: FocusMonitor, private elRef: ElementRef<HTMLElement>) {
this.parts = fb.group({
area: '',
exchange: '',
subscriber: '',
});
fm.monitor(elRef, true).subscribe(origin => {
this.focused = !!origin;
this.stateChanges.next();
});
}
ngOnDestroy() {
this.stateChanges.complete();
this.fm.stopMonitoring(this.elRef);
}
setDescribedByIds(ids: string[]) {
this.describedBy = ids.join(' ');
}
onContainerClick(event: MouseEvent) {
if ((event.target as Element).tagName.toLowerCase() != 'input') {
this.elRef.nativeElement.querySelector('input')!.focus();
}
}
}
example-tel-input-example.html
<div [formGroup]="parts" class="example-tel-input-container">
<input class="example-tel-input-element" formControlName="area" size="3">
<span class="example-tel-input-spacer">–</span>
<input class="example-tel-input-element" formControlName="exchange" size="3">
<span class="example-tel-input-spacer">–</span>
<input class="example-tel-input-element" formControlName="subscriber" size="4">
</div>
But I get the following error:
ERROR Error: mat-form-field must contain a MatFormFieldControl.
Need to import two module and add those in imports and exports section.
import { MatFormFieldModule } from '#angular/material/form-field';
import { MatInputModule } from '#angular/material/input';
#NgModule ({
imports: [ MatFormFieldModule, MatInputModule ],
exports: [ MatFormFieldModule, MatInputModule ]
})
And the most thing which everybody miss this '/' character. if you see the Angular Material Documentation , they also miss this (Last Checked 16 Jun 2020, don't know they even updated or not). I make an example for clarifications
<!-- Wrong -->
<mat-form-field>
<input matInput>
</mat-form-field>
<!-- Right -->
<mat-form-field>
<input matInput />
</mat-form-field>
Look at the snippet carefully. when <input begin it must close with /> but most people miss the / (backslash) character.
Make sure MatInputModule is imported and <mat-form-field> contains <input> with matInput / matSelect directives.
https://github.com/angular/material2/issues/7898
Import MatInputModule, solved my error
You need to specify your class as a provider for MatFormFieldControl
https://material.angular.io/guide/creating-a-custom-form-field-control#providing-our-component-as-a-matformfieldcontrol
#Component({
selector: 'form-field-custom-control-example',
templateUrl: 'form-field-custom-control-example.html',
styleUrls: ['form-field-custom-control-example.css'],
providers: [
{ provide: MatFormFieldControl, useExisting: FormFieldCustomControlExample }
]
})
Also, don't forget to write the name attribute in the input tag:
name="yourName"
if you are using any 'input' tag in your code along with 'mat-form-field', make sure to include 'matInput' in the input tag
if there is any *ngIf present in child tag of 'mat-form-field', specify the *ngIf condition in 'mat-form-field' tag
Another possible issue closing the input tag. If you copy code from one of the Angular examples (https://material.angular.io/components/datepicker/overview), you may end up with the code:
<mat-form-field appearance="fill">
<mat-label>Choose a date</mat-label>
<input matInput [matDatepicker]="picker">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
</mat-form-field>
The input should have the close tag (slash):
<input matInput [matDatepicker]="picker" />
This will fix your issue
import {
MatFormFieldModule,
MatInputModule
} from "#angular/material";
Error says that mat-form-field should contain at least one form field from which input is taken.
Ex : matInput mat-select etc.
In your case you may add matInput tag to your input fields within example-tel-input-example.html. And also you could move mat-form-field inside example-tel-input-example component and add it against each matInput field.
<div [formGroup]="parts" class="example-tel-input-container">
<mat-form-field>
<input matInput class="example-tel-input-element" formControlName="area" size="3">
</mat-form-field>
<span class="example-tel-input-spacer">–</span>
<mat-form-field>
<input matInput class="example-tel-input-element" formControlName="exchange" size="3">
</mat-form-field>
<span class="example-tel-input-spacer">–</span>
<mat-form-field>
<input matInput class="example-tel-input-element" formControlName="subscriber" size="4">
</mat-form-field>
</div>
Note : mat-icon or mat-hint cannot be considered as form-fields

How to return data that user inputs?

I recently learned Angular2. Currently, I have got a material modal after some searching. However,I can't find how I return the data that the user can input.
In the modal I currently have, there is one input field and one checkbox. When close the console logs "de dialog closed" and "true".
This is my modal HTML:
<h2 mat-dialog-title>Add Group</h2>
<mat-dialog-content>
<div>
<mat-form-field>
<input matInput placeholder="Groupname">
</mat-form-field>
<mat-form-field>
<mat-checkbox >Private group?</mat-checkbox>
</mat-form-field>
<mat-form-field>
<button mat-raised-button color="primary">img</button>
</mat-form-field>
</div>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button mat-dialog-close>cancel</button>
<!-- The mat-dialog-close directive optionally accepts a value as a result for the dialog. -->
<button mat-button [mat-dialog-close]="true" >save</button>
</mat-dialog-actions>
TS code:
import { Component, OnInit, Inject } from '#angular/core';
import { GroupsService} from '../../../services/Groups.service';
import { Groups } from '../../../models/groupModel'
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '#angular/material';
#Component({
selector: 'app-groups',
templateUrl: './groups.component.html',
styleUrls: ['./groups.component.scss'],
})
export class GroupsComponent implements OnInit {
animal: string;
name: string;
groups: Groups[];
constructor(
private groupsService: GroupsService,
public dialog: MatDialog
){}
ngOnInit() {
this.groupsService.getMyGroups()
.then(group =>{
this.groups = group;
console.log(this.groups)
}).catch(error=>{
console.log(error);
});
}
openDialog(): void {
let dialogRef = this.dialog.open(DialogOverviewExampleDialog, {
height: '300px',
width: '300px',
});
dialogRef.afterClosed().subscribe(result => {
console.log('The dialog was closed');
console.log(result);
this.animal = result;
});
}
testgroup(id){
console.log(id)
}
acceptGroup(){
console.log('accept')
}
declineGroup(){
console.log('decline');
}
createGroup(){
console.log("sample");
}
}
#Component({
selector: 'dialog-overview-example-dialog',
templateUrl: './model.html',
})
export class DialogOverviewExampleDialog {
constructor(
public dialogRef: MatDialogRef<DialogOverviewExampleDialog>,
#Inject(MAT_DIALOG_DATA) public data: any) { }
onNoClick(): void {
this.dialogRef.close();
}
}
This part of your code inside HTML template is currently responsible for what you're passing back from the model: [mat-dialog-close]="true". As you see you're just passing true and nothing else. Simplest way to achieve what you want is to create form inside the dialog and pass its value.
<h2 mat-dialog-title>Add Group</h2>
<mat-dialog-content>
<form #modalForm="ngForm">
<div>
<mat-form-field>
<input matInput name="groupName" placeholder="Groupname" ngModel>
</mat-form-field>
<mat-form-field>
<mat-checkbox name="privateGroup" ngModel>Private group?</mat-checkbox>
</mat-form-field>
<mat-form-field>
<button mat-raised-button color="primary">img</button>
</mat-form-field>
</div>
</form>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button mat-dialog-close>cancel</button>
<button mat-button [mat-dialog-close]="modalForm.value">save</button>
</mat-dialog-actions>
You can also pass form value on submit (using (ngSubmit)="mySubmitFunction(modalForm.value)" on form tag ) to the mySubmitFunction defined in DialogOverviewExampleDialog. And then, after some kind of validation for example, pass data by using: this.dialogRef.close(someVariableContainingValues).
use this code i think it will be work:
dialogRef.afterClosed().subscribe(result => {
console.log('The dialog was closed');
console.log(result);
//this.animal = result;
}).()=>{
this.animal = result;
};

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