Compare commits

...

4 Commits

7 changed files with 135 additions and 28 deletions

View File

@ -24,4 +24,10 @@ The [Parent Form](apps/parent-form) application explores this approach.
Another approach is to allow the outermost, or parent, component create the full Reactive Form. Each child component is given the `FormGroup` containing the portion of the form that it is responsible for rendering. Another approach is to allow the outermost, or parent, component create the full Reactive Form. Each child component is given the `FormGroup` containing the portion of the form that it is responsible for rendering.
The [Global Form](apps/global-form) application explores this approach. The [Global Form](apps/global-form) application explores this approach.
## Parent Component Creates Form ; Child Components Define Structure
Another approach is to allow the parent component to maintain control of creating the full Reactive Form, while allowing each child component to define the shape of the form data by means of static factory methods defined within the child component code.
The [Static Method](apps/static-factory-methods/README.md) application explores this approach.

View File

@ -78,6 +78,6 @@ Calling `this.parent.addControl(....)` is what ensures that the controls created
## Cons ## Cons
- The creation of the form controls is tightly coupled with the templates - The creation of the form controls is tightly coupled with the templates, to the point that if a child component is not rendered for some reason, the form controls won't exist either.
- Since each child component encapsulates its form controls, the overall shape of the form data is not always clear - Since each child component encapsulates its form controls, the overall shape of the form data is not always clear

View File

@ -0,0 +1,87 @@
# Parent Component Creates Form ; Child Components Define Structure
Another approach for refactoring a component into child sub-components where the parent component is responsible for creating the entre Reactive Form would be to define static factory methods within each child component rather than within a full-fledged service. As with the [Parent Component Creates Form and Passes Form Controls Into Child Components (Global Form)](../global-form/README.md) approach, the appropriate form controls would be passed into the children.
In many ways, this approach is a hybrid between the Parent Form and Global Form approaches.
```typescript
export class AppComponent implements OnInit, OnDestroy {
contact: Contact;
form: FormGroup;
private subscription: Subscription;
constructor(private service: ContactService, private fb: FormBuilder) {}
public ngOnInit() {
this.subscription = this.service
.loadContact()
.subscribe((data: Contact) => {
this.contact = data;
this.form = this.fb.group({
name: NameComponent.buildForm(data.name),
addresses: AddressListComponent.buildForm(data.addresses),
});
});
}
}
```
The HTML templating will be identical to the Global Form approach.
## Static Form Builder Methods
Rather than having a separate factory service, this approach uses static methods on each of the child sub-classes. This approach intentionally couples the logic for creating a sub-form structure with the component that would display it, keeping the logic in one place rather than separating it between components and an otherwise unrelated service. The rule-of-thumb in this approach is that the component which needs to display the form to a user will best know what the structure of that form needs to be.
### Name Component
```typescript
static buildForm(name: Name): FormGroup {
return new FormGroup({
firstName: new FormControl(name ? name.firstName : ''),
lastName: new FormControl(name ? name.lastName : ''),
middleName: new FormControl(name ? name.middleName : ''),
prefix: new FormControl(name ? name.prefix : ''),
suffix: new FormControl(name ? name.suffix : ''),
});
}
```
### Address List Component
```typescript
static buildForm(addresses: Address[]): FormArray {
const list: FormArray = new FormArray([]);
if (addresses) {
addresses.forEach(addr => {
list.push(AddressComponent.buildForm(addr));
});
}
return list;
}
```
### Address Component
```typescript
static buildForm(addr: Address): FormGroup {
return new FormGroup({
line1: new FormControl(addr ? addr.line1 : ''),
line2: new FormControl(addr ? addr.line2 : ''),
city: new FormControl(addr ? addr.city : ''),
state: new FormControl(addr ? addr.state : ''),
postalCode: new FormControl(addr ? addr.postalCode : ''),
});
}
```
## Pros
- The child components encapsulate the form controls and their display, while keeping the form creation logic separate from the actual template rendering
- The child components can easily be re-used
## Cons
- The overall shape of the form from the parent component's perspective is not always clear

View File

@ -1,4 +1,9 @@
import { Component, Input, OnInit } from '@angular/core'; import {
ChangeDetectionStrategy,
Component,
Input,
OnInit,
} from '@angular/core';
import { FormArray } from '@angular/forms'; import { FormArray } from '@angular/forms';
import { Address } from '@nested-forms/contact'; import { Address } from '@nested-forms/contact';
import { AddressComponent } from '../address/address.component'; import { AddressComponent } from '../address/address.component';
@ -6,7 +11,8 @@ import { AddressComponent } from '../address/address.component';
@Component({ @Component({
selector: 'nested-forms-address-list', selector: 'nested-forms-address-list',
templateUrl: './address-list.component.html', templateUrl: './address-list.component.html',
styleUrls: ['./address-list.component.css'] styleUrls: ['./address-list.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class AddressListComponent implements OnInit { export class AddressListComponent implements OnInit {
@Input() addressArray: FormArray; @Input() addressArray: FormArray;
@ -23,9 +29,7 @@ export class AddressListComponent implements OnInit {
return list; return list;
} }
constructor() { } constructor() {}
ngOnInit() {
}
ngOnInit() {}
} }

View File

@ -1,15 +1,21 @@
import { Component, OnInit, Input } from '@angular/core'; import {
import { FormGroup, FormControl } from '@angular/forms'; ChangeDetectionStrategy,
Component,
Input,
OnInit,
} from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { Address } from '@nested-forms/contact'; import { Address } from '@nested-forms/contact';
@Component({ @Component({
selector: 'nested-forms-address', selector: 'nested-forms-address',
templateUrl: './address.component.html', templateUrl: './address.component.html',
styleUrls: ['./address.component.css'] styleUrls: ['./address.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class AddressComponent implements OnInit { export class AddressComponent implements OnInit {
@Input() addressGroup: FormGroup; @Input() addressGroup: FormGroup;
static buildForm(addr: Address): FormGroup { static buildForm(addr: Address): FormGroup {
return new FormGroup({ return new FormGroup({
line1: new FormControl(addr ? addr.line1 : ''), line1: new FormControl(addr ? addr.line1 : ''),
@ -20,9 +26,7 @@ export class AddressComponent implements OnInit {
}); });
} }
constructor() { } constructor() {}
ngOnInit() {
}
ngOnInit() {}
} }

View File

@ -1,17 +1,20 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { import {
Contact, ChangeDetectionStrategy,
ContactService, Component,
} from '@nested-forms/contact'; OnDestroy,
OnInit,
} from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { Contact, ContactService } from '@nested-forms/contact';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { NameComponent } from './name/name.component';
import { AddressListComponent } from './address-list/address-list.component'; import { AddressListComponent } from './address-list/address-list.component';
import { NameComponent } from './name/name.component';
@Component({ @Component({
selector: 'nested-forms-root', selector: 'nested-forms-root',
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrls: ['./app.component.css'], styleUrls: ['./app.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class AppComponent implements OnInit, OnDestroy { export class AppComponent implements OnInit, OnDestroy {
contact: Contact; contact: Contact;
@ -19,10 +22,7 @@ export class AppComponent implements OnInit, OnDestroy {
private subscription: Subscription; private subscription: Subscription;
constructor( constructor(private service: ContactService, private fb: FormBuilder) {}
private service: ContactService,
private fb: FormBuilder,
) {}
public ngOnInit() { public ngOnInit() {
this.subscription = this.service this.subscription = this.service

View File

@ -1,11 +1,17 @@
import {
ChangeDetectionStrategy,
Component,
Input,
OnInit,
} from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms'; import { FormControl, FormGroup } from '@angular/forms';
import { Component, OnInit, Input } from '@angular/core';
import { Name } from '@nested-forms/contact'; import { Name } from '@nested-forms/contact';
@Component({ @Component({
selector: 'nested-forms-name', selector: 'nested-forms-name',
templateUrl: './name.component.html', templateUrl: './name.component.html',
styleUrls: ['./name.component.css'] styleUrls: ['./name.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class NameComponent implements OnInit { export class NameComponent implements OnInit {
@Input() nameGroup: FormGroup; @Input() nameGroup: FormGroup;