Error while passing an action() per component - javascript

I have the following button component.
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'app-button',
template: ` <ion-button color="{{color}}" (click)="action()">
{{title}}
</ion-button>`
})
export class ButtonComponent implements OnInit {
#Input() public title: string;
#Input() public color = 'primary';
#Input() public action = '';
constructor() { }
ngOnInit() {
}
}
but when I pass the action() to it does not work, does anyone know what I need to do to be able to pass this property as a component?

(click)="action()" means it is expecting a method named action, you need to modify a little,
Try like this:
Working Demo
Parent:
title = 'Btn Title';
action = "save"
<app-button [title]="title" [action]="action"></app-button>
Child:
<ion-button color="{{color}}" (click)="this[action]()">
{{title}}
</ion-button>
or,
<ion-button color="{{color}}" (click)="onClick()">
{{title}}
</ion-button>
onClick() {
this[this.action]()
}

If you want to give the action function from your parent component you should use #Output() instead of #Input() like this:
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-button',
template: ` <ion-button color="{{color}}" (click)="action.emit(null)">
{{title}}
</ion-button>`
})
export class ButtonComponent implements OnInit {
#Input() public title: string;
#Input() public color = 'primary';
#Output() public action: EventEmitter = new EventEmitter();
constructor() { }
ngOnInit() {
}
}
and now in your parent component you can call what function you want like this:
<app-button (action)="yourFunction($event)"></app-button>
try this, if any questions feel free to ask.

Related

Angular - Pass function as input

I have a modalComponent that I create dynamically.
<div class="modal">
<div class="modal-body">
Test
</div>
<div class="modal-footer">
<button (click)="callbackFunction()">success</button>
<button>abort</button>
</div>
</div>
This component has an Input callbackFunction that'a function that I want to invoke from my parent component.
import {
Component,
Input,
OnInit,
QueryList,
ViewChildren
} from "#angular/core";
import { ModalService } from "../modal.service";
#Component({
selector: "app-modal",
templateUrl: "./modal.component.html",
styleUrls: ["./modal.component.css"]
})
export class ModalComponent implements OnInit {
#Input() callbackFunction: () => void;
constructor(private modalService: ModalService) {}
ngOnInit() {}
}
After that I created a service:
import {
ApplicationRef,
ComponentFactoryResolver,
ComponentRef,
Injectable,
Injector
} from "#angular/core";
import { ModalComponent } from "./modal/modal.component";
#Injectable()
export class ModalService {
dialogComponentRef: ComponentRef<ModalComponent>;
open(callbackFunction: any) {
const modalComponentFactory = this.cfResolver.resolveComponentFactory(ModalComponent);
const modalComponent = modalComponentFactory.create(this.injector);
modalComponent.instance.callbackFunction = callbackFunction;
this.dialogComponentRef = modalComponent;
document.body.appendChild(modalComponent.location.nativeElement);
this.appRef.attachView(modalComponent.hostView);
}
close() {
this.appRef.detachView(this.dialogComponentRef.hostView);
}
constructor(
private appRef: ApplicationRef,
private cfResolver: ComponentFactoryResolver,
private injector: Injector
) {}
}
After componentFactoryResolver I pass my function as instance.
In my parent controller I create a function
sayHello(
this.myService.doSomething();
}
and after that I create a function for opening a modal
open(this.sayHello());
When I click on the button and I invoke callback function, "this" is not referred to Parent component but to Modal Component and sayHello is undefined. How can I fix this situation?
I don't want to use emit.
This is my stackblitz: Example
Basically there are three solutions for this: Output + EventEmitter, #ViewChild and Subject
ViewChild solution
This one can be used when the button is defined on the Parent and you want to get something from the Child.
///////parent.component.ts
...
import { ChildComponent } from 'child/child.component';
...
export class ParentComponent {
#ViewChild(ChildComponent) childComponent: ChildComponent;
public buttonClick(): void {
let childResponse = this.childComponent.getValues();//will return '1234'
...
}
}
///////child.component.ts
export class ChildComponent {
valueInsideChild = '1234';
public getValues(): string {
return this.valueInsideChild;
}
}
Output + EventEmitter solution
In this scenario the child itself sends something to the parent(aka the button is inside the child)
implementation on stackblic
//////parent.component.html
<child-selector
($buttonClicked)=clickAction($event)>
</child-selector>
//////parent.component.ts
...
export class ParentComponent {
public clickAction(value: string): void {
console.log(value);//will log 'something1234 when child button is clicked
}
}
//////child.component.ts
...
import { Output, Component, EventEmitter } from '#angular/core';
...
export class ChildComponent {
#Output() $buttonClicked = new EventEmitter<string>();
public click(): void {
this.$buttonClicked.emit('something1234');
}
}
//////child.component.html
<button (click)="click()">
Subject
Interface responses using your modalService+subject+observables
///app.component.ts
...
export class AppComponent {
...
open() {
//subscribe to the observable :)
this.modalService.open(this.sayHello).subscribe(response => {
alert(response.text);
});
}
...
}
///modal.component.html
...
<button (click)="click()">success</button>
...
///modal.component.ts
...
export class ModalComponent {
constructor(private modalService: ModalService) {}
...
public click(): void {
this.modalService.close({text: 'Hello World'});
}
}
///modal.service.ts
...
import { Subject, Observable } from 'rxjs';
...
export class ModalService {
...
private _modalResponse = new Subject<any>();
...
open(): Observable<any> {//this is your open function
...
return this._modalResponse.asObservable();//return an observable where the modal responses will be emitted
}
close(response: any): void {
//receives a value from the modal component when closing
this.appRef.detachView(this.dialogComponenRef.hostView);
this._modalResponse.next(response);//emit the response on the Observable return when open was called
}
}
I suggest you to use an Output and a EventEmitter to call the parent component function from the child component, Angular documentation provides a good example on how to do it.
https://angular.io/guide/inputs-outputs#sending-data-to-a-parent-component

get result in parent component after selectAll and unSelectAll event emitted from child (Angular)

Hi I am now working on angular to build a multiselect dropdown using ng-multiselect-dropdown(https://www.npmjs.com/package/ng-multiselect-dropdown).
I used parent-child component communication through event emitter:
in child.component.ts:
import {Component, EventEmitter, Input, OnInit, Output} from '#angular/core';
import {IDropdownSettings} from 'ng-multiselect-dropdown';
export interface IMultiDropdownConfig {
placeholder: string;
header: string;
dropdownSettings: IDropdownSettings;
}
#Component({
selector: 'app-multi-dropdown',
templateUrl: './multi-dropdown.component.html',
styleUrls: ['./multi-dropdown.component.scss']
})
export class MultiDropdownComponent implements OnInit {
#Input() dropdownItems: any[];
#Input() selectedItems;
#Input() header: string;
#Input() placeholder: string;
#Input() dropdownSettings: IDropdownSettings;
#Input() loading;
#Output() itemSelectEvent = new EventEmitter();
#Output() itemDeselectEvent = new EventEmitter();
#Output() selectAllEvent = new EventEmitter();
#Output() deselectAllEvent = new EventEmitter();
#Output() selectedItemsChange = new EventEmitter();
constructor() { }
ngOnInit(): void {
}
onSelectItem(event) {
this.selectedItemsChange.emit(this.selectedItems);
}
onDeselectItem(event) {
this.selectedItemsChange.emit(this.selectedItems);
}
onSelectAll(event) {
this.selectedItemsChange.emit(this.selectedItems);
}
onDeselectAll(event) {
this.selectedItemsChange.emit(this.selectedItems);
}
}
in child.component.html:
<div class="multi-dropdown-item">
<div class="multi-dropdown-header">{{header}}</div>
<div *ngIf="!this.loading" class="multi-dropdown-body">
<ng-multiselect-dropdown
[placeholder]="placeholder"
[data]="dropdownItems"
[(ngModel)]="selectedItems"
[settings]="dropdownSettings"
(onSelect)="onSelectItem($event)"
(onDeSelect)="onDeselectItem($event)"
(onSelectAll)="onSelectAll($event)"
(onDeSelectAll)="onDeselectAll($event)">
</ng-multiselect-dropdown>
</div>
</div>
Then in parent.component.html:
<app-multi-dropdown
[loading]="filterPropertiesMap.get(filterEntry).loading"
[dropdownItems]="filterPropertiesMap.get(filterEntry).items"
[(selectedItems)]="filterPropertiesMap.get(filterEntry).selectedItems"
[dropdownSettings]="filterPropertiesMap.get(filterEntry).setting"
[placeholder]="filterPropertiesMap.get(filterEntry).name"
[header]="filterPropertiesMap.get(filterEntry).name"
(itemSelectEvent)="onItemSelect($event)"
(itemDeselectEvent)="onItemDeselect($event)"
(selectAllEvent)="onSelectAll($event)"
(deselectAllEvent)="onDeselectAll($event)"
></app-multi-dropdown>
in parent.component.ts I didn't do anything but log:
onItemSelect($event) {
console.log("onItemSelect");
}
onItemDeselect($event) {
console.log("onItemDeselect");
}
onSelectAll($event) {
console.log("onSelectAll");
}
onDeselectAll($event) {
console.log("onDeselectAll");
}
in above code filterPropertiesMap defines settings.
You may see that what I am doing is in child component, I created eventemitters for select, deselect, and in the function I emitt this.selectedItems.
But I don't think this is a good way to implement this, and actually, it doesn't work well.
sometimes, when I select or deselect, it doesn't changed immediately.
So how to implement this? when I select deselect, selectAll, deselectAll. my parent component can react immediately and correctly.
Also the weird thing is: when I load the page, I will have some default values chose, for example 6,7,8,9. Then I select all and it still 6,7,8,9. and then after that I deselect all agin select all, the field will change to all(for example 1,2,3,4,5,6,7,8,9). Does event emitter has delay or will ignore some choices??
Edit:
I tried to extract all the necessary snippets of code to build a project here:
https://drive.google.com/file/d/1BlV2EtdwZhqqpkdiC0_mlaw_r3w6Bder/view?usp=sharing
I hope when I all the event(select, deselect, selectAll, deselectAll) can be emitted and received by parent component
sorry one mistake: the app-multi-dropdown tag in parent component should be app-child
You can use it as part of an Angular reactive form, and just emit every time it value changes.
The HTML of the child component could be something like this:
<form [formGroup]="myForm">
<ng-multiselect-dropdown
name="dropdownItems"
[settings]="dropdownSettings"
[placeholder]="placeholder"
[data]="dropdownItems"
formControlName="dropdownItems">
</ng-multiselect-dropdown>
</form>
And the .ts file:
import { IDropdownSettings } from 'ng-multiselect-dropdown';
import { FormGroup, FormBuilder } from '#angular/forms';
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.scss']
})
export class ChildComponent implements OnInit {
#Input() placeholder: string;
#Input() defaultValues: any[];
#Input() dropdownItems: any[];
#Input() dropdownSettings: IDropdownSettings;
#Output() onDropdownChange = new EventEmitter();
myForm: FormGroup;
constructor(
private _fb: FormBuilder
) { }
ngOnInit(): void {
this.myForm = this._fb.group({
dropdownItems: ['']
});
// Set default values as real values
this.onDropdownChange.emit(this.defaultValues);
this.myForm.valueChanges.subscribe(val => {
this.onDropdownChange.emit(val.dropdownItems)
})
}
}
EDIT: Based on your last update:
With the code you updated and my peace of code, I build this functional project running on stackblitz: https://stackblitz.com/edit/angular-ivy-g7w3dz
Hope I got the logic properly
you haven't set up your emitter properly. do this:
#Output()selectAllEvent = new EventEmitter<any>();
onSelectAll(event) {
this.selectAllEvent.emit(this.selectedItems);
}
then on the parent:
<app-multi-dropdown
(selectAllEvent)="onSelectAll($event)"
></app-multi-dropdown>

Failing to access child component using ViewChild in Angular

I have a dialog box that is displaying a separate child component. The child component is below:
#Component({
selector: 'userEdit',
templateUrl: './edituser.component.html',
styleUrls: ['./edituser.component.css']
})
export class EditUserComponent implements OnInit {
public theName: string;
public theAddress: string;
constructor() {
this.theName = '';
this.theAddress = '';
}
ngOnInit() {
}
}
The dialog box code and template are below:
#Component({
selector: 'app-userdialog',
templateUrl: './userdialog.component.html',
styleUrls: ['./userdialog.component.css']
})
export class UserDialogComponent implements OnInit {
#ViewChild('userEdit', {static: false})
userEdit: EditUserComponent;
constructor(
public dlgRef: MatDialogRef<UserDialogComponent>,
#Inject(MAT_DIALOG_DATA) public theData: UsrStuff) { }
ngOnInit() {
}
ngAfterViewInit() {
console.log('Name: ' + this.userEdit.theName);
}
addUser() {
// TODO: implement adding a user
}
closeBox() {
this.dlgRef.close();
}
}
and
<div id="attribdlg">
<h3>Add New User</h3>
<userEdit theName="" theAddress=""></userEdit>
<mat-dialog-actions>
<button mat-raised-button color="primary" (click)="addUser()">Add User</button>
<button mat-raised-button color="primary" (click)="closeBox()">Done</button>
</mat-dialog-actions>
</div>
Based on the documentation and examples Ihave seen, this setup should enable me to print to the console the value pf userEdit's theName property in the ngAfterViewInit() function.
Unfortunately, this does not appear to be working.When the console log is called, I get the following failure message:
null: TypeError: Cannot read property 'theName' of undefined
Obviously, there is some kind of initialization of the child component that is supposed to happen, but I do not see this being done anywhere in the documentation! I am missing something.
How can I get this child component and its properties available to my dialog?
Two options:
Set an id to the component you wish to have with ViewChild():
TypeScript:
#ViewChild('userEdit', {static: false})
HTML:
<userEdit #userEdit theName="" theAddress=""></userEdit>
Select by directive or component:
TypeScript:
#import { EditUserComponent } from '...';
#ViewChild(EditUserComponent, {static: false})
HTML:
<userEdit theName="" theAddress=""></userEdit>
I highly recommend you to use app perfix for the component's selector!!!
#Component({
...
selector: 'app-user-edit',
...
})

Multiple instance of angular 4 directive called from a component mesed up the input values

I have a component in angular 4 that is called three times. In template metadata I have a div with a directive with some bindings like this.
#import {gServ} from '../gServ.service';
#Component: ({
selector: 'sr-comp',
template: `<div gDirective [cOptions]="dataChart">`
})
export class SGComponent implements OnInit {
#Input('report') public report: IReportInstance;
cOptions:any;
constructor(private gServ: gServ) {
}
ngOnInit(){
this.cOptions = {};
this.cOptions = this.gServ.objectMerge(this.gServ.defaultOpt, this.report.opt);
//this.report.opt is binded to a component when is instantiated.
//this.gServ.objectMerge is a function that merge the two objects
}
}
this.cOptions change for every instance of the component, then in the directive I have this:
import { Directive, ElementRef, HostListener, Input, OnInit } from '#angular/core';
#Directive({
selector: '[gDirective]'
})
export class SGDirective implements OnInit {
public _element: any;
#Input() public cOptions: string;
constructor(public element: ElementRef) {
this._element = this.element.nativeElement;
}
ngOnInit() {
console.log(this.cOptions);
}
}
The problem is that console.log(this.cOptions); always print the same object, even when component set cOptions with diferent values in ngOnInit method of the compnent.
Do you have some idea what is wrong?
Your component property binding [cOptions]="dataChart" doesn't look good, reason being your dataChart is not even defined. it should be like [DIRECTIVE_PROPERTY]="COMPONENT_PROPERTY" and your COMPONENT_PROPERTY is not even defined in SGComponent component class.
Your component class should be something like this:
#import {gServ} from '../gServ.service';
#Component: ({
selector: 'sr-comp',
template: `<div gDirective [cOptions]="Options">`
})
export class SGComponent implements OnInit {
#Input('report') public report: IReportInstance;
Options:any;
constructor(private gServ: gServ) {
}
ngOnInit(){
this.Options = {};
this.Options = this.gServ.objectMerge(this.gServ.defaultOpt, this.report.opt);
}
}
#Ashwani points out a valid problem with your code. The way your template is wiring things up, nothing will ever be passed to the SGDirective input.
Another potential problem you could be running into has to do with the gServ code. If gServ is a singleton (which is probably the case) and it is returning the same object to each of the SGComponents, then all the SGDirectives will have the same value. A simple way to test this is to put {{Options | json}} in the SGComponent template.
To create a new instance of the gServ service for each SGComponent you can add a providers array to the #Component metadata. It would look like this:
import {gServ} from '../gServ.service';
#Component({
selector: 'sr-comp',
template: `{{Options | json}}<div gDirective [cOptions]="Options"></div>`
providers: [gServ],
})
export class SGComponent implements OnInit {
#Input('report') public report: IReportInstance;
Options:any;
constructor(private gServ: gServ) {
}
ngOnInit(){
this.Options = this.gServ.objectMerge(this.gServ.defaultOpt, this.report.opt);
}
}
You have probably the same return/value at this.gServ.objectMerge) (you can test it wihtout calling the service, and passing each one one different objet make by you)
#import {gServ} from '../gServ.service';
#Component: ({
selector: 'sr-comp',
template: `<div gDirective [cOptions]="dataChart">`
})
export class SGComponent implements OnInit {
//#Input('report') public report: IReportInstance;
cOptions:any;
constructor(private gServ: gServ) {
}
ngOnInit(){
this.cOptions = {nicolas: 'nicolas1'}; //change this in the next component that use the directive
}
}
If that is the case, your problem is that gServ is provide at the same rootComponent. with angular, service provider at the same rootComponent are singleton.
And use the same type in your directive and your component!!

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