I want to call a function with an argument when an element is loaded.
Just like nginit in angualrjs. Can we do it in Angular 4 and above?
<div *ngFor="let item of questionnaireList"
(onload)="doSomething(item.id)" >
</div>
My Typescript function:
doSomething(id) {
console.log(id);
}
You need to write a directive
import {Directive, Input, Output, EventEmitter} from '#angular/core';
#Directive({
selector: '[ngInit]'
})
export class NgInitDirective {
#Input() isLast: boolean;
#Output('ngInit') initEvent: EventEmitter<any> = new EventEmitter();
ngOnInit() {
if (this.isLast) {
setTimeout(() => this.initEvent.emit(), 10);
}
}
}
Using in html
<div *ngFor="let quetionnaireData of questionnairelist ; let $last = last" [isLast]='$last'
(ngInit)="doSomething('Hello')"></div>
Also you declare your directive in app.module
#NgModule({
declarations: [
..
NgInitDirective
],
......
})
Use ngOnInit() and the #Input directive.
For example, in your child component:
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'my-component',
template: `
<h3>My id is: {{itemId}}</h3>
`
})
export class MyComponent implements OnInit
{
#Input() itemId: string;
//other code emitted for clarity
public ngOnInit(): void
{
// Now you can access to the itemId field e do what you need
console.log(this.itemId);
}
}
In your parent component
<div *ngFor="let item of questionnairelist">
<my-component itemId='{{item.Id}}'></my-component>
</div>
Your Function:
ExecuteMyFunction(value:any):void{
console.log(value);
}
If you wants to pass parameter which declared in component itself and set from component then try as below:
notificationMessage:string='';
#Input() message:string;
ngAfterViewInit(){
this.ExecuteMyFunction(this.notificationMessage);
}
If you set variable as Input parameter and set from other component then try as below: ngOnChanges will fire every time when your Input variable value is changed.
import { Component, OnChanges, Input } from '#angular/core';
ngOnChanges(changes: any) {
if (changes.message != null && changes.message.currentValue != null) {
this.ExecuteMyFunction(this.message);
}
}
HTML:
<ng-container *ngFor="let item of items">
<div *ngIf="doSomething(item.id)"></div>
</ng-container>
TS:
doSomething(value){
//logic
return true;
}
import { Router,NavigationEnd } from '#angular/router';
constructor( private router: Router ) {
this.router.events.subscribe((e) => {
if (e instanceof NavigationEnd) {
// Function you want to call here
}
});
}
Related
to make change detection lesser, we replace hostlistener with from event from RXJS and runoutside of angular.
this is how my angular code looks like
ngOnInit() {
this.windowKeyDown();
// this.subject$ = this.subject.asObservable();
}
constructor(private _ngZone: NgZone) {}
//#HostListener('window:keydown', ['$event'])
handleKeyboardEvent(event: KeyboardEvent) {
console.log('handle key fired');
this.keypressed = event.key;
this.iskeyPressed = true;
}
windowKeyDown() {
console.log('windowKeyDown');
fromEvent(window, 'keydown')
.pipe(this.outsideZone(this._ngZone))
.subscribe(event => this.handleKeyboardEvent(<KeyboardEvent>event));
}
outsideZone<T>(zone: NgZone) {
return function(source: Observable<T>) {
return new Observable(observer => {
let sub: Subscription;
zone.runOutsideAngular(() => {
sub = source.subscribe(observer);
});
return sub;
});
};
}
and HTML binding is :
<h2>keypressed: {{keypressed}}</h2>
<h2>iskeyPressed: {{iskeyPressed}}</h2>
in this binding variable it self not updating now, can you please guide what's wrong with my code?
minimum step to repro : https://stackblitz.com/edit/keypress-example-vu3mej?file=app%2Fapp.component.html
I would recommend setting the ChangeDetectionStrategy for this component to OnPush. You can read more about it here. Do not put any logic in this component for which you want automatic change detection as changedetection is disabled for the component as a whole.
Below is a code sample that shows how you can subscribe to the keyboard events directly from the template (using the async pipe).
https://stackblitz.com/edit/angular-change-detection-strategy-onpush-g6mjkr?file=src%2Fapp%2Fchild%2Fchild.component.ts
import {
Component,
Input,
ChangeDetectionStrategy,
OnInit
} from '#angular/core';
import { fromEvent, Observable } from 'rxjs';
import { map, startWith, tap } from 'rxjs/operators';
#Component({
selector: 'child',
template: `
<ng-container *ngIf="keyboard$ | async as keyBoard">
<h2>keypressed: {{ keyBoard.keypressed }}</h2>
<h2>iskeyPressed: {{ keyBoard.iskeyPressed }}</h2>
</ng-container>
`,
styleUrls: [],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent implements OnInit {
keyboard$: Observable<{ keypressed: any; iskeyPressed: boolean }>;
ngOnInit() {
console.log('bla')
this.keyboard$ = fromEvent(window, 'keydown').pipe(
map(event => ({ keypressed: event.key, iskeyPressed: true })),
startWith({keypressed: null, iskeyPressed: null})
);
}
}
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
I defined a property here in my function
evs: string
...
openArticle(url){
this.evs = url
console.log(this.evs)
this.navCtrl.navigateForward('/url-page')
}
And I a trying to pass the value of 'this.evs' to another ts file and use its value but I do not know how to do this. I tried exporting it like this.
export const webpage = this.evs
but this.evs has no value until someone performs the openArticle function ad so I keep getting the error. "Cannot read property 'evs' of undefined"
What i need to do is tranfer the variable to the 'url-page' page and use the value of this.evs only after the openArticle function has bee called. How do I go about this?
As per my understanding you are trying to share data between two components.
So choose one of them as per your requirements.
Parent to Child: Sharing Data via Input().
Child to Parent: Sharing Data via Output() and EventEmitter.
Unrelated Components: Sharing Data with a Service.
This link will be helpful.
If the components have a parent/child relationship, You can share data between them via #Inpput() and #Output() decorators.
Sharing data from Parent to Child using #Input() :
<h3>Parent Component</h3>
<label>Parent Component</label>c
<input type="number" [(ngModel)]='parentValue'/>
<p>Value of child component is: </p>
<app-child [value]='parentValue'></app-child>
And in the child component, the 'parentValue' can be received as :
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {
#Input() value: number;
constructor() { }
ngOnInit() {
}
}
Now, in the case of sending data from Child to Parent, we can use an #Output() event emitter. So the parent would have a function to receive the emitted data from child as :
parent-app.component.html
<app-child [value]="parentValue" (childEvent)="childEvent($event)"></app-child>
parent-app.component.ts
childEvent(event) {
console.log(event);
}
And, the child.component.ts would look like :
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {
#Input() PData: number;
#Output() childEvent = new EventEmitter();
constructor() { }
onChange(value) {
this.childEvent.emit(value);
}
ngOnInit() {
}
}
If the components do not have a parent/child relationship, a shared service can be used, say, SharedService which has a BehavioralSubject, that emits value from either component, and the other component can then catch the changed value.
Eg:
import { Injectable } from '#angular/core';
import { BehaviorSubject } from "rxjs/BehaviorSubject";
#Injectable()
export class SharedService {
comp1Val: string;
_comp1ValueBS = new BehaviorSubject<string>('');
comp2Val: string;
_comp2ValueBS = new BehaviorSubject<string>('');
constructor() {
this.comp1Val;
this.comp2Val;
this._comp1ValueBS.next(this.comp1Val);
this._comp2ValueBS.next(this.comp2Val);
}
updateComp1Val(val) {
this.comp1Val = val;
this._comp1ValueBS.next(this.comp1Val);
}
updateComp2Val(val) {
this.comp2Val = val;
this._comp2ValueBS.next(this.comp2Val);
}
And component1 as follows :
import { Injectable } from '#angular/core';
import { BehaviorSubject } from "rxjs/BehaviorSubject";
#Injectable()
export class SharedService {
comp1Val: string;
_comp1ValueBS = new BehaviorSubject<string>('');
comp2Val: string;
_comp2ValueBS = new BehaviorSubject<string>('');
constructor() {
this.comp1Val;
this.comp2Val;
this._comp1ValueBS.next(this.comp1Val);
this._comp2ValueBS.next(this.comp2Val);
}
updateComp1Val(val) {
this.comp1Val = val;
this._comp1ValueBS.next(this.comp1Val);
}
updateComp2Val(val) {
this.comp2Val = val;
this._comp2ValueBS.next(this.comp2Val);
}
Component 2 :
import { Component, AfterContentChecked } from '#angular/core';
import { SharedService } from "../../common/shared.service";
#Component({
selector: 'app-component2',
templateUrl: './component2.component.html',
styleUrls: ['./component2.component.css']
})
export class Component2Component implements AfterContentChecked {
comp1Val: string;
comp2Val: string;
constructor(private sharedService: SharedService) {
this.sharedService.comp2Val = "Component 2 initial value";
}
ngAfterContentChecked() {
this.comp1Val = this.sharedService.comp1Val;
}
addValue(str) {
this.sharedService.updateComp2Val(str);
}
}
You can find more on different types of subjects here
I am actually trying to inject the array and the data inside the array to another component but is constantly getting errors.
My list.component.ts
Here i injected the itemList array from app.component and this component is working just fine. No errors here.
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
import {List} from './list.model'
#Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css']
})
export class ListComponent implements OnInit {
#Input() itemList: List[] = [];
#Output() onItemSelected: EventEmitter<List>;
private currentItem: List;
constructor(){
this.onItemSelected = new EventEmitter();
}
onClick(list: List): void {
this.currentItem = list;
this.onItemSelected.emit(list);
console.log(`clicking list title: ${list.title}`);
}
isSelected(list: List): boolean {
if (!list || !this.currentItem) {
return false;
}
return list.title === this.currentItem.title;
}
ngOnInit() {
}
}
list.component.html
Here i try to inject both the array and then using ngFor i try to inject the single list also.
<div class="ui grid posts">
<app-list-row
[lists]="itemList"
*ngFor="let list of itemList"
[list]="list"
(click)='onClick(list)'
[class.selected]="isSelected(list)">
</app-list-row>
</div>
list-row.component.ts
I am mainly trying to input the array in this component so that i can use the splice method to delete my list. I tried the delete list;method but this says i cannot use delete in strict mode. Therefore i am trying to input the array and use the splice method.
import { Component, OnInit, Input} from '#angular/core';
import {List} from '.././list/list.model';
#Component({
selector: 'app-list-row',
inputs: ['list: List'],
templateUrl: './list-row.component.html',
styleUrls: ['./list-row.component.css'],
host: {'class': 'row'},
})
export class ListRowComponent implements OnInit {
list: List;
#Input() lists: List[];
deletelist(list: List): void {
let index: number = this.lists.indexOf(list);
if (index !== -1) {
this.lists.splice(index, 1);
}
}
ngOnInit() {
}
}
list-row.component.html
In this write a div and use a lable of delete icon and give an event of click with the "deleteList(list)".
<div class="Eight wide column left aligned title">
<div class="value">
<div class = "hello">
<b>
{{ list.title | uppercase }}
</b>
<div style="float: right;" class="ui label">
<i class="delete icon"
(click)="deleteList(list)"
></i>
</div>
</div>
</div>
</div>
These are my codes and i dont know whether i can do the dependency injection of both the array and its single data in the array. If i can, what ways are there to do it. while running in server the console error is
Unhandled Promise rejection: Template parse errors:
Can't bind to 'list' since it isn't a known property of 'app-list-row'.
1. If 'app-list-row' is an Angular component and it has 'list' input, then verify that it is part of this module.
2. If 'app-list-row' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '#NgModule.schemas' of this component to suppress this message.
("
[lists]="itemList"
*ngFor="let list of itemList"
[ERROR ->][list]="list"
(click)='onClick(list)'
[class.selected]="isSelected(list)">
"): ListComponent#4:2 ; Zone: <root> ; Task: Promise.then ; Value: SyntaxError {__zone_symbol__error: Error: Template parse errors:
Can't bind to 'list' since it isn't a known property of 'app-list-row'…, _nativeError: ZoneAwareError, __zone_symbol__currentTask: ZoneTask, __zone_symbol__stack: "Error: Template parse errors:↵Can't bind to 'list'…ttp://localhost:4200/polyfills.bundle.js:6060:47)", __zone_symbol__message: "Template parse errors:↵Can't bind to 'list' since …lected]="isSelected(list)">↵"): ListComponent#4:2"} Error: Template parse errors:
Can't bind to 'list' since it isn't a known property of 'app-list-row'.
1. If 'app-list-row' is an Angular component and it has 'list' input, then verify that it is part of this module.
2. If 'app-list-row' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '#NgModule.schemas' of this component to suppress this message.
("
[lists]="itemList"
*ngFor="let list of itemList"
[ERROR ->][list]="list"
(click)='onClick(list)'
[class.selected]="isSelected(list)">
Thank you.
Add #Input() to list variable in ListRowComponent class and check if it is working or not and remove inputs from metadata.
import { Component, OnInit, Input} from '#angular/core';
import {List} from '.././list/list.model';
#Component({
selector: 'app-list-row',
templateUrl: './list-row.component.html',
styleUrls: ['./list-row.component.css'],
host: {'class': 'row'},
})
export class ListRowComponent implements OnInit {
#Input() list: List;
#Input() lists: List[];
deletelist(list: List): void {
let index: number = this.lists.indexOf(list);
if (index !== -1) {
this.lists.splice(index, 1);
}
}
ngOnInit() {
}
}
or
Remove :List from inputs as
import { Component, OnInit, Input} from '#angular/core';
import {List} from '.././list/list.model';
#Component({
selector: 'app-list-row',
templateUrl: './list-row.component.html',
inputs :['list']
styleUrls: ['./list-row.component.css'],
host: {'class': 'row'},
})
export class ListRowComponent implements OnInit {
list: List;
#Input() lists: List[];
deletelist(list: List): void {
let index: number = this.lists.indexOf(list);
if (index !== -1) {
this.lists.splice(index, 1);
}
}
ngOnInit() {
}
}
I got the answer. I did not do two input bindings but i created a custom event in the list-row.component and emmited the list to list.Component.
import { Component, Input, Output, EventEmitter} from '#angular/core';
import {List} from '.././list/list.model';
#Component({
selector: 'app-list-row',
templateUrl: './list-row.component.html',
styleUrls: ['./list-row.component.css'],
host: {
'class': 'row'
}
})
export class ListRowComponent {
#Input() list: List;
#Output() deleted = new EventEmitter<List>();
deletedl() {
const listing: List = this.list;
this.deleted.emit(listing);
}
In the template I used the click event to call the deletel() method.
<div class="Eight wide column left aligned title">
<div class="value">
<div class = "hello">
<b>
{{ list.title | uppercase }}
</b>
<div style="float: right;" class="ui label">
<i class="delete icon"
(click)="deletedl()">
</i>
</div>
</div>
</div>
</div>
Then I just called the event in the list.component
list.component.html
<div class="ui grid posts">
<app-list-row
*ngFor="let list of itemList"
[list]="list"
(click)='onClick(list)'
[class.selected]="isSelected(list)"
(deleted)="deletedl($event)">
</app-list-row>
</div>
Then i called a method in list.component to delete the list from the array using Splice method.
list.component
import { Component, Input} from '#angular/core';
import {List} from './list.model';
#Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css']
})
export class ListComponent {
#Input() itemList: List[] = [];
private currentItem: List;
onClick(list: List): void {
this.currentItem = list;
console.log(`clicking list title: ${list.title}`);
}
isSelected(list: List): boolean {
if (!list || !this.currentItem) {
return false;
}
return list.title === this.currentItem.title;
}
deletedl(list: List) {
console.log(`deleting list title: ${list.title}`);
let index: number = this.itemList.indexOf(list);
if (index !== -1) {
this.itemList.splice(index, 1);
}
}
}
I have learned that if we want to get some input from the parent component than using property binding helps and when we want to run some output from our parent component then event binding is helpful.
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;