Accessing angular ngform status in parent component - forms

This is the Parent HTML
<parent-component>
<child></child>
<button type="submit" [disabled]="!childForm.valid">
</parent-component>
This is Child HTML
<child>
<form #childform=ngform>
<input required type="text">
</form>
</child>
I need to access the childform status in the parent component

Child template:
<form #childform=ngForm>
<input required type="text">
</form>
Child component:
export class ChildComponent implements OnInit {
#Output() validityChange = new EventEmitter<boolean>();
#ViewChild('childform') form: NgForm;
private validStatus: boolean;
ngOnInit() {
if (!this.form) return;
this.form.valueChanges
.subscribe(_ => {
if(this.validStatus !== this.form.valid) {
this.validStatus = this.form.valid;
this.validityChange.emit(this.form.valid);
});
}
}
Parent template:
<child (validityChange)="childFormValid = $event"></child>
<button type="submit" [disabled]="!childFormValid">
Parent component:
export class ChildComponent implements OnInit {
private childFormValid: boolean;
...
}

Probably you could do it using #ViewChild:
Child component template file:
<form #form="ngForm">
<input type="text" ngModel required name="input">
</form>
and use #ViewChild in child component TS file like this:
#ViewChild('form') form: NgForm;
Parent component template file:
<button type="submit" *ngIf="childForm" [disabled]="!childForm.valid">Submit</button>
and in your parent component TS file reference to child component as well:
export class ParentComponent implements AfterViewInit {
#ViewChild('child') child: ChildComponent;
public childForm;
ngAfterViewInit(): void {
this.childForm = this.child.form.form;
}
}

Use an output
<child (myOutput)="myFunction($event)"></child>
In your parent component :
myFunction(event: Event) {
console.log(event); // hello world
}
In your child component
import { Output, EventEmitter } from '#angular/core';
// ...
#Output() myOutput: new EventEmitter();
doSomething() {
thisMyOutput.emit('hello world');
}

Child Component
export class ChildparentComponent implements OnInit {
#Output() toParent = new EventEmitter<string>();
constructor() { }
onChange(value:string){
this.toParent.emit(value);
}
In Child Html
<app-childparent (toParent)="childValue = $event"></app-childparent>
In Parent
Access this child value in parent template like {{childValue}}
A working example of the Same
https://rahulrsingh09.github.io/AngularConcepts/#/inout
Its Backend Code
https://github.com/rahulrsingh09/AngularConcepts/blob/master/src/app/parentchild/parentchild.component.html

Related

Angular 5 - Can't Pass Data to Modal

Short summary of what i actually want to do.
If i click on the heroes table. I need to pass the heroes data from the row clicked to a Modal.
I read that there are two ways to get Data into a Modal. (NGMODAL)
With #Input and #Output or through a shared Service.
But it doesn't matter which way I am using. The modal opens (and fetches data) always before the data is passed.
I've simply got a table in which all my heroes are displayed.
On a Click on the delete button in a table row I wan't to open the modal and pass the data from my table row. (I want to pass the whole hero, but shouldn't be a difference to just passing the name of the hero).
After that i want to show.
Do you really want to delete hero with name...?
[Cancel], [Yes, Delete] . Onclick on the Yes, Delete Button I want to delete the hero.
At the moment I can't even display the heroes name I want to delete
I hope anyone can tell my what I can do and how.
Thank you.
Here is my code:
Shows my Heroes: hero.component.html
<div class="container">
<h2>Heroes</h2>
<input type="text" placeholder="Suchbegriff eingeben" class="form-control has-float-label" [(ngModel)]="filter" >
<br>
<div *ngIf="heroes">
<table class="heroes table table-hover table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let hero of heroes | filter:filter:'name':'name'">
<td>{{hero.name}}</td>
// I could use (click)="pusHero(hero)" and open the modal afterwars. Than i would get the right data through input. But when i click to open the modal first there is no data, or the data from the row clicked before
<td>
<ngbd-modal-component (click)="selectRow(hero) [(delhero)]="delhero""></ngbd-modal-component> </td>
</tbody>
</table>
</div>
</div>
heroes.component.ts
import { Component, OnInit, ViewEncapsulation , Input , EventEmitter} from '#angular/core';
import { Hero } from '../../model/hero';
import { HeroService } from '../hero.service';
import { CommonModule } from '#angular/common';
import { Observable } from 'rxjs/Observable';
import { Response } from '#angular/http/src/static_response';
// For use of map
import 'rxjs/Rx';
// Für Pipe und Suche in Liste
import { Pipe, PipeTransform } from '#angular/core';
import { FilterPipe } from '../../pipes/filter.pipe';
// Component Decorator imported from Component
#Component({
// Unique Selector
selector: 'app-hero',
// URL of Template
templateUrl: './hero.component.html',
// URL of stylesheet
styleUrls: ['./hero.component.css']
// encapsulation: ViewEncapsulation.None
})
export class HeroesComponent implements OnInit {
heroes: Observable<Hero[]>;
hero: Hero;
filter = '';
// Is Input for Child Component. In this case modal
delhero: Hero;
constructor(
private heroService: HeroService,
// Modal Service
// private modalService: NgbModal,
// private ngbdModalComponent: NgbdModalComponent,
// private ngbdModalContent: NgbdModalContent,
) {
// this.searchableList = ['name','age']
}
selectRow(hero): void {
console.log("Select the tables data");
console.log(hero);
this.heroService.selectRow(hero);
}
}
Hero Service
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { HttpResponse } from '#angular/common/http';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { catchError, map, tap } from 'rxjs/operators';
import { Hero } from '../model/hero';
import { MessageService } from '../message.service';
import { Response } from '#angular/http/src/static_response';
// For use of map
import 'rxjs/Rx';
import { headersToString } from 'selenium-webdriver/http';
const httpOptions = {
headers: new HttpHeaders(
{
'Content-Type': 'application/json'
}
)
};
#Injectable()
export class HeroService {
private hero: Hero;
constructor(private http: HttpClient, private messageService: MessageService) { }
selectRow(input){
this.hero = input;
}
getRow(){
console.log(this.hero);
return this.hero;
}
}
My Modal
modal.component.html
<button class="btn btn-danger btn-xs more" (click)="open()"> <span class="fa fa-trash"></span></button>
modal.component.ts
import { Component, Input, EventEmitter, SimpleChange, Output, OnInit } from '#angular/core';
import { NgbModal, NgbActiveModal } from '#ng-bootstrap/ng-bootstrap';
import { NgbdModalContent } from './modal.content.component';
import { Hero } from '../../model/hero';
import { HeroService } from '../../hero/hero.service';
// Modal Component
#Component({
selector: 'ngbd-modal-component',
templateUrl: './modal.component.html'
})
export class NgbdModalComponent {
// #Input() delhero: Hero;
// hero: Hero;
// #Output() getDeleteHero = new EventEmitter();
name = '';
constructor(private modalService: NgbModal,
private heroService: HeroService) {}
ngOnInit(){
// this.open();
// console.log(this.delhero);
}
open() {
var name = this.heroService.getRow();
// this.getDeleteHero.emit();
console.log("Modal Component hero");
// console.log(this.delhero);
this.name = this.heroService.getRow().name;
const modalRef = this.modalService.open(NgbdModalContent);
modalRef.componentInstance.name = name;
}
delete(){
console.log("Delete Hero through Hero Service");
}
}
modal.content.component.html
<div class="modal-header ">
<h4 class="modal-title">Delete Hero?</h4>
<button type="button" class="close" aria-label="Close" (click)="activeModal.dismiss('Cross click')">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<p>Delete Hero with Name: {{name}}?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" (click)="activeModal.close('Close click')">No, do not delete</button>
<button type="button" class="btn btn-primary">Ja, löschen</button>
</div>
modal.content.component.ts
import { Component, Input, EventEmitter } from '#angular/core';
import { NgbModal, NgbActiveModal } from '#ng-bootstrap/ng-bootstrap';
import { HeroService } from '../../hero/hero.service';
import { Hero } from '../../model/hero';
// Modal Content
#Component({
selector: 'ngbd-modal-content',
templateUrl: './modal.content.component.html'
// styleUrls: ['./modal.content.component.css']
})
export class NgbdModalContent {
// #Input() Hero;
name = "";
constructor(
public activeModal: NgbActiveModal,
privateheroService: HeroService
) {}
ngOnInit() {
//Called after the constructor, initializing input properties, and the first call to ngOnChanges.
//Add 'implements OnInit' to the class.
console.log("NG On Init ModalsContent");
this.name = this.heroService.getRow().name;
console.log(this.name);
}
}
Not sure if this helps but I found this after having the same problem.
I ended up with working code. Sorry it's not your code but I was having trouble following it. I had to set a variable and pass it in via the click code:
HTML:
<ng-template #content let-c="close" let-d="dismiss">
<div class="modal-header">
<h4 class="modal-title">Option Entry</h4>
<button type="button" class="close" aria-label="Close" (click)="d('Cross click')">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<form>
<div class="form-group">
<label class="control-form-label" for="optDescription">Description:</label>
<input type="text" class="form-control" name="description" ngModel id="optDescription" [(ngModel)]="editOption">
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-dark" (click)="c('Close click')">Update</button>
</div>
</ng-template>
<div *ngIf='optionList && optionList.length'>
<div *ngFor='let opt of optionList'>
<ngb-accordion #acc="ngbAccordion" activeIds="ngb-panel-0" class="accordianheading">
<ngb-panel>
<ng-template ngbPanelTitle>
<span>{{opt.description}}</span>
<button class='btn btn-sm btn-outline-primary btnline pull-right' (click)='$event.preventDefault(); $event.stopPropagation(); editItem(content, opt)'>Edit</button>
</ng-template>
<ng-template ngbPanelContent>
<js-option-lists [OptionListID]=opt.id></js-option-lists>
</ng-template>
</ngb-panel>
</ngb-accordion>
</div>
</div>
and:
.ts file
import { Component, OnInit, Input } from '#angular/core';
import { IOptionListHeader } from '../dtos/option-list-header';
import { OptionListService } from '../services/felixApi/option-list.service';
import { NgbModal, ModalDismissReasons } from '#ng-bootstrap/ng-bootstrap';
import { NgModel, NgForm } from '#angular/forms';
#Component({
selector: 'js-option-lists',
templateUrl: './option-lists.component.html',
styleUrls: ['./option-lists.component.css']
})
export class OptionListsComponent implements OnInit {
#Input() OptionListID: number;
errorMessage: any;
optionList: IOptionListHeader[] = [];
updatedOption: any;
closeResult: string;
editOption = '';
constructor(private _optionListService: OptionListService, private modalService: NgbModal) { }
// for now read in all options but will need to read by level
ngOnInit() {
this.getChildren(this.OptionListID);
}
getChildren (id) {
this._optionListService.getOptionListChildren(id)
.subscribe(
optionList => {
this.optionList = optionList;
},
error => this.errorMessage = <any>error);
}
// Patch change
patchOrderNo(updatedOption: any) {
this._optionListService.updateOption(updatedOption).subscribe(
res => {
this.getChildren(this.OptionListID);
},
err => {
console.log('error code ' + err.status);
}
);
}
editItem(content, opt: IOptionListHeader) {
this.editOption = opt.description;
this.modalService.open(content).result.then((result) => {
this.closeResult = `Closed with: ${result}`;
this.updatedOption = { id: opt.id, description: this.editOption };
this.patchOrderNo(this.updatedOption);
}, (reason) => {
this.closeResult = `Dismissed ${this.getDismissReason(reason)}`;
console.log('cancelled');
});
}
private getDismissReason(reason: any): string {
if (reason === ModalDismissReasons.ESC) {
return 'by pressing ESC';
} else if (reason === ModalDismissReasons.BACKDROP_CLICK) {
return 'by clicking on a backdrop';
} else {
return `with: ${reason}`;
}
}
}

Angular form input value undefined

I'm trying to get the value of an input field in my first ever Angular form, but it is always undefined, and I can't figure out why. I'm importing FormsModule correctly, and I can reference the form object fine, so I must be missing something obvious.
My components HTML
<form #searchValue='ngForm' class="" (ngSubmit)='submitSearch(searchValue)'>
<div>
<input type="text" name="q" placeholder="search">
</div>
</form>
And my components ts method (Shortened)
import { Component, OnInit } from '#angular/core';
import { FormsModule } from '#angular/forms';
#Component({
selector: 'google-search',
templateUrl: './google.component.html',
styleUrls: ['./google.component.css']
})
export class GoogleComponent implements OnInit {
constructor() { }
ngOnInit() {
}
submitSearch(formData) {
console.log(this.searching);
console.log(formData.value.q);
}
}
Any ideas to why this is?
You need to mark the input with ngModel so angular will know that this is one of form's controls:
<input type="text" ngModel name="q" placeholder="search">
Or you can define the variable first in your component, and then bind the input to it via [(ngModel)] directive:
export class GoogleComponent implements OnInit {
q: string;
submitSearch() {
console.log(this.q);
}
}
<form class="" (ngSubmit)='submitSearch()'>
<div>
<input type="text" name="q" [(ngModel)]="q" placeholder="search">
</div>
</form>
One way binding (just [ngModel]="q") could be enough if you just want to read the value from input.
Some like this should work..
Maybe you want to read about model binding and forms.
html component
<form #searchValue='ngForm' class="some-css-class" (ngSubmit)='submitSearch()'>
<div>
<input type="text" name="q" [(ngModel)]="searchValue" placeholder="search">
<input type="submit" name="btn" placeholder="Submit">
</div>
</form>
In component.ts
import { Component, OnInit } from '#angular/core';
import { FormsModule } from '#angular/forms';
#Component({
selector: 'google-search',
templateUrl: './google.component.html',
styleUrls: ['./google.component.css']
})
export class GoogleComponent implements OnInit {
searchValue: string = '';
constructor() { }
ngOnInit() { }
submitSearch() {
console.log(this.searchValue);
}
}

How can I create my own component for FormControls?

I would like to create a form and use a new, custom component for its controls. So I created a new component and included it into the parent form. But although the parent form has a formGroup, Angular complains that it does not.
The error:
Error: formControlName must be used with a parent formGroup directive.
You'll want to add a formGroup
directive and pass it an existing FormGroup instance (you can create one in your class).
The parent form has:
<form [formGroup]="learningObjectForm" (ngSubmit)="onSubmit()" novalidate>
<div>
<button type="submit"
[disabled]="learningObjectForm.pristine">Save</button>
</div>
<ava-form-control [label]="'Resource'"></ava-form-control>
</form>
And in the .ts:
constructor(private fb: FormBuilder) {
this.createForm();
}
createForm() {
this.learningObjectForm = this.fb.group({
text: '',
});
}
In the custom component I have
import { Component, Input, OnInit } from '#angular/core';
#Component({
selector: 'ava-form-control',
template: ` <div>
<label>{{label}} :</label>
<input formControlName="{{name}}">
</div>
`
})
export class FormControlComponent implements OnInit {
#Input() label: string;
#Input() name: string;
constructor() {}
ngOnInit() {
if (this.name === undefined) {
// turns 'The Label' into 'theLabel'
this.name = this.label[0].toLowerCase().concat(this.label.slice(1));
this.name = this.name.split(' ').join('');
console.log(this.label, this.name);
}
}
}
You should also be passing the formGroup instance along with control name to your custom component. And then create a form control under that formGroup in custom component. Your custom component will create the control virtually under the same formGroup that you have provided.
<form [formGroup]="learningObjectForm" (ngSubmit)="onSubmit()" novalidate>
<div>
<button type="submit"
[disabled]="learningObjectForm.pristine">Save</button>
</div>
<ava-form-control [label]="'Resource'" [formGroup]="learningObjectForm" [controlName]="'mycontrol'"></ava-form-control>
</form>
custom.component.ts
import { Component, Input, OnInit } from '#angular/core';
#Component({
selector: 'ava-form-control',
template: ` <div>
<label>{{label}} :</label>
<input [formControl]="formGroup.controls[controlName]>
</div>
`
})
export class FormControlComponent implements OnInit {
#Input() label: string;
#Input() formGroup: FormGroup;
#Input() controlName: string;
constructor() {}
ngOnInit() {
let control: FormControl = new FormControl('', Validators.required);
this.formGroup.addControl(this.controlName, control);
}
}
With this your parent component can access all the form controls defined within their respective custom components.
I played around with the accepted answer for a long time, and never had any luck.
I had much better results implementing the ControlValueAccessor interface as shown here:
https://alligator.io/angular/custom-form-control/
It's actually pretty simple, I also rigged up an Example StackBlitz

Angular 2 nested forms with child components and validation

I'm trying achieve a nested form with validation in Angular 2, I've seen posts and followed the documentation but I'm really struggling, hope you can point me in the right direction.
What I am trying to achieve is having a validated form with multiple children components. These children components are a bit complex, some of them have more children components, but for the sake of the question I think we can attack the problem having a parent and a children.
What am I trying to accomplish
Having a form that works like this:
<div [formGroup]="userForm" novalidate>
<div>
<label>User Id</label>
<input formControlName="userId">
</div>
<div>
<label>Dummy</label>
<input formControlName="dummyInput">
</div>
</div>
This requires having a class like this:
private userForm: FormGroup;
constructor(private fb: FormBuilder){
this.createForm();
}
private createForm(): void{
this.userForm = this.fb.group({
userId: ["", Validators.required],
dummyInput: "", Validators.required]
});
}
This works as expected, but now I want to decouple the code, and put the "dummyInput" functionality in a separate, different component. This is where I get lost. This is what I tried, I think I'm not far from getting the answer, but I'm really out of ideas, I'm fairly new to the scene:
parent.component.html
<div [formGroup]="userForm" novalidate>
<div>
<label>User Id</label>
<input formControlName="userId">
</div>
<div>
<dummy></dummy>
</div>
</div>
parent.component.ts
private createForm(): void{
this.userForm = this.fb.group({
userId: ["", Validators.required],
dummy: this.fb.group({
dummyInput: ["", Validators.required]
})
});
children.component.html
<div [formGroup]="dummyGroup">
<label>Dummy Input: </label>
<input formControlName="dummyInput">
</div>
children.component.ts
private dummyGroup: FormGroup;
I know something is not right with the code, but I'm really in a roadblock. Any help would be aprreciated.
Thanks.
you can add an Input in your children component to pass the FormGroup to it.and use FormGroupName to pass the name of your FormGroup :)
children.component.ts
#Input('group');
private dummyGroup: FormGroup;
parent.component.html
<div [formGroup]="userForm" novalidate>
<div>
<label>User Id</label>
<input formControlName="userId">
</div>
<div formGroupName="dummy">
<dummy [group]="userForm.controls['dummy']"></dummy>
</div>
</div>
Not going to lie, don't know how I didn't find this post earlier.
Angular 2: Form containing child component
The solution is to bind the children component to the same formGroup, by passing the formGroup from the parent to the children as an Input.
If anyone shares a piece of code to solve the problem in other way, I'll gladly accept it.
The main idea is that you have to treat the formGroup and formControls as variables, mainly javascript objects and arrays.
So I'll put some code in to make my point. The code below is somewhat like what you have. The form is constructed dynamically, just that it is split into sections, each section containing its share of fields and labels.
The HTML is backed up by typescript classes. Those are not here as they do not have much special. Just the FormSchemaUI, FormSectionUI and FormFieldUI are important.
Treat each piece of code as its own file.
Also please take note that formSchema: FormSchema is a JSON object that I receive from a service. Any properties of the UI classes that you do not see defined are inherited from their base Data clases. Those are not presented here.
The hierarchy is: FormSchema contains multiple sections. A section contains multiple fields.
<form (ngSubmit)="onSubmit()" #ciRegisterForm="ngForm" [formGroup]="formSchemaUI.MainFormGroup">
<button kendoButton (click)="onSubmit(ciRegisterForm)" [disabled]="!canSubmit()"> Register {{registerPageName}} </button>
<br /><br />
<app-ci-register-section *ngFor="let sectionUI of formSchemaUI.SectionsUI" [sectionUI]="sectionUI">
</app-ci-register-section>
<button kendoButton (click)="onSubmit(ciRegisterForm)" [disabled]="!canSubmit()"> Register {{registerPageName}} </button>
</form>
=============================================
<div class="row" [formGroup]="sectionUI.MainFormGroup">
<div class="col-md-12 col-lg-12" [formGroupName]="sectionUI.SectionDisplayId">
<fieldset class="section-border">
<legend class="section-border">{{sectionUI.Title}}</legend>
<ng-container *ngFor='let fieldUI of sectionUI.FieldsUI; let i=index; let even = even;'>
<div class="row" *ngIf="even">
<ng-container>
<div class="col-md-6 col-lg-6" app-ci-field-label-tuple [fieldUI]="fieldUI">
</div>
</ng-container>
<ng-container *ngIf="sectionUI.Fields[i+1]">
<div class="col-md-6 col-lg-6" app-ci-field-label-tuple [fieldUI]="sectionUI.FieldsUI[i+1]">
</div>
</ng-container>
</div>
</ng-container>
</fieldset>
</div>
</div>
=============================================
{{fieldUI.Label}}
=============================================
<ng-container>
<div class="row">
<div class="col-md-4 col-lg-4 text-right">
<label for="{{fieldUI.FieldDisplayId}}"> {{fieldUI.Label}} </label>
</div>
<div class="col-md-8 col-lg-8">
<div app-ci-field-edit [fieldUI]="fieldUI" ></div>
</div>
</div>
</ng-container>
=============================================
<ng-container [formGroup]="fieldUI.ParentSectionFormGroup">
<ng-container *ngIf="fieldUI.isEnabled">
<ng-container [ngSwitch]="fieldUI.ColumnType">
<input *ngSwitchCase="'HIDDEN'" type="hidden" id="{{fieldUI.FieldDisplayId}}" [value]="fieldUI.Value" />
<ci-field-textbox *ngSwitchDefault
[fieldUI]="fieldUI"
(valueChange)="onValueChange($event)"
class="fullWidth" style="width:100%">
</ci-field-textbox>
</ng-container>
</ng-container>
</ng-container>
=============================================
export class FormSchemaUI extends FormSchema {
SectionsUI: Array<FormSectionUI>;
MainFormGroup: FormGroup;
static fromFormSchemaData(formSchema: FormSchema): FormSchemaUI {
let formSchemaUI = new FormSchemaUI(formSchema);
formSchemaUI.SectionsUI = new Array<FormSectionUI>();
formSchemaUI.Sections.forEach(section => {
let formSectionUI = FormSectionUI.fromFormSectionData(section);
formSchemaUI.SectionsUI.push(formSectionUI);
});
formSchemaUI.MainFormGroup = FormSchemaUI.buildMainFormGroup(formSchemaUI);
return formSchemaUI;
}
static buildMainFormGroup(formSchemaUI: FormSchemaUI): FormGroup {
let obj = {};
formSchemaUI.SectionsUI.forEach(sectionUI => {
obj[sectionUI.SectionDisplayId] = sectionUI.SectionFormGroup;
});
let sectionFormGroup = new FormGroup(obj);
return sectionFormGroup;
}
}
=============================================
export class FormSectionUI extends FormSection {
constructor(formSection: FormSection) {
this.SectionDisplayId = 'section' + this.SectionId.toString();
}
SectionDisplayId: string;
FieldsUI: Array<FormFieldUI>;
HiddenFieldsUI: Array<FormFieldUI>;
SectionFormGroup: FormGroup;
MainFormGroup: FormGroup;
ParentFormSchemaUI: FormSchemaUI;
static fromFormSectionData(formSection: FormSection): FormSectionUI {
let formSectionUI = new FormSectionUI(formSection);
formSectionUI.FieldsUI = new Array<FormFieldUI>();
formSectionUI.HiddenFieldsUI = new Array<FormFieldUI>();
formSectionUI.Fields.forEach(field => {
let fieldUI = FormFieldUI.fromFormFieldData(field);
if (fieldUI.ColumnType != 'HIDDEN')
formSectionUI.FieldsUI.push(fieldUI);
else formSectionUI.HiddenFieldsUI.push(fieldUI);
});
formSectionUI.SectionFormGroup = FormSectionUI.buildFormSectionFormGroup(formSectionUI);
return formSectionUI;
}
static buildFormSectionFormGroup(formSectionUI: FormSectionUI): FormGroup {
let obj = {};
formSectionUI.FieldsUI.forEach(fieldUI => {
obj[fieldUI.FieldDisplayId] = fieldUI.FieldFormControl;
});
let sectionFormGroup = new FormGroup(obj);
return sectionFormGroup;
}
}
=============================================
export class FormFieldUI extends FormField {
constructor(formField: FormField) {
super();
this.FieldDisplayId = 'field' + this.FieldId.toString();
this.ListItems = new Array<SelectListItem>();
}
public FieldDisplayId: string;
public FieldFormControl: FormControl;
public ParentSectionFormGroup: FormGroup;
public MainFormGroup: FormGroup;
public ParentFormSectionUI: FormSectionUI;
public ValueChange: EventEmitter<any> = new EventEmitter<any>();
static buildFormControl(formFieldUI:FormFieldUI): FormControl {
let nullValidator = Validators.nullValidator;
let fieldKey: string = formFieldUI.FieldDisplayId;
let fieldValue: any;
switch (formFieldUI.ColumnType) {
default:
fieldValue = formFieldUI.Value;
break;
}
let isDisabled = !formFieldUI.IsEnabled;
let validatorsArray: ValidatorFn[] = new Array<ValidatorFn>();
let asyncValidatorsArray: AsyncValidatorFn[] = new Array<AsyncValidatorFn>();
let formControl = new FormControl({ value: fieldValue, disabled: isDisabled }, validatorsArray, asyncValidatorsArray);
return formControl;
}
}
To get a reference to the parent form simply use this (maybe not available in Angular 2. I've tested it with Angular 6):
TS
import {
FormGroup,
ControlContainer,
FormGroupDirective,
} from "#angular/forms";
#Component({
selector: "app-leveltwo",
templateUrl: "./leveltwo.component.html",
styleUrls: ["./leveltwo.component.sass"],
viewProviders: [
{
provide: ControlContainer,
useExisting: FormGroupDirective
}
]
})
export class NestedLevelComponent implements OnInit {
//form: FormGroup;
constructor(private parent: FormGroupDirective) {
//this.form = form;
}
}
HTML
<input type="text" formControlName="test" />
import { Directive } from '#angular/core';
import { ControlContainer, NgForm } from '../../../node_modules/#angular/forms';
#Directive({
selector: '[ParentProvider]',
providers: [
{
provide: ControlContainer,
useFactory: function (form: NgForm) {
return form;
},
deps: [NgForm]
}`enter code here`
]
})
export class ParentProviderDirective {
constructor() { }
}
<div ParentProvider >
for child
</div>
An alternative to the FormGroupDirective (as described in #blacksheep's answer) is the use of ControlContainer like so:
import { FormGroup, ControlContainer } from "#angular/forms";
export class ChildComponent implements OnInit {
formGroup: FormGroup;
constructor(private controlContainer: ControlContainer) {}
ngOnInit() {
this.formGroup = <FormGroup>this.controlContainer.control;
}
The formGroup can be set in the direct parent or further up (parent's parent, for example). This makes it possible to pass a from group over various nested components, without the need for a chain of #Input()s to pass the formGroup along. In any parent set the formGroup to make it available via ControlContainer in the child:
<... [formGroup]="myFormGroup">

vuejs v2 pass data to component and update parent when changed

What I want to build is a Form which can detect errors from its Inputs. The inputs are rendered (in my current setup) in a section in the form. But after hours of work, it doesn't work.
What is the best approach to make the concept will work? Should I used slots for this or is there a other way?
This is my code:
//Form.vue
<template>
<form method="POST" action="/projects">
<slot></slot>
<div class="control">
<button class="button is-primary">Create</button>
</div>
</template>
<script>
import {FormHelper} from './classes/FormHelper.js';
export default {
/*
* The component's properties.
*/
props: {
fields: Object
},
data() {
return {
form: new FormHelper(this.fields) //this must be kwown in the Input.
};
},
}
</script>
//Input.vue
<template>
<div class="control">
<label for="name" class="label">{{label}}</label>
<input type="text" id="name" name="name" class="input">
<!--<span class="help is-danger" v-if="form.errors.has('name')" v-text="form.errors.get('name')"></span>-->
</div>
</template>
<script>
export default{
/*
* The component's properties.
*/
props: {
placeholder: String,
label: String,
name: String,
originalValue: String,
},
}
</script>
Implementation in the browser:
<vue-form :fields="{'name': 'piet', 'description': 'arie'}">
<vue-input
label="The Name"
name="name"
></vue-input>
<vue-input
label="The Description"
name="description"
></vue-input>
</vue-form>
you can use custom event.
in cild component
this.$emit('EventName', someData)
and in parent you can handle it with
v-on:EventName="doSmth"
From the documentation:
In Vue.js, the parent-child component relationship can be summarized as props down, events up. The parent passes data down to the child via props, and the child sends messages to the parent via events. Let’s see how they work next.
How to pass props
Following is the code to pass props to child component: (with v-model value is passed as props, or with v-bind in myMessage parentMsg is passed as props)
<div>
<input v-model="parentMsg">
<br>
<child v-bind:my-message="parentMsg"></child>
</div>
How to emit event
Following is how you update parent: whenever in child you increment: you intimate parent by calling this.$emit('increment') which is handled by parent.
HTML:
<div id="counter-event-example">
<p>{{ total }}</p>
<button-counter v-on:increment="incrementTotal"></button-counter>
<button-counter v-on:increment="incrementTotal"></button-counter>
</div>
JS:
Vue.component('button-counter', {
template: '<button v-on:click="increment">{{ counter }}</button>',
data: function () {
return {
counter: 0
}
},
methods: {
increment: function () {
this.counter += 1
this.$emit('increment')
}
},
})
new Vue({
el: '#counter-event-example',
data: {
total: 0
},
methods: {
incrementTotal: function () {
this.total += 1
}
}
})