7
votes

Using this resource, I want to implement formControlName up multiple nested levels.

Angular 2 - formControlName inside component

Say the actual formGroup lives 3 component levels above a child formControlName component,

ControlValueAccessor works if the Parent component is right next to child. However multiple levels above (grandfather) form does not work.

Is there an alternative to Service, or multiple input/outputs ? Or are these the only method?

A--> Component with formGroup 
   B---> Component container
      C---> Component container
        D ---> Component with FormControlName (should pass to Component A)

Component A will collect multiple form control names from different children components similar to this,

InputText.ts

export class InputTextComponent implements  AfterViewInit, ControlValueAccessor  {
  @Input() disabled: boolean;
  @Output() saveValue = new EventEmitter();

  value: string;
  onChange: () => void;
  onTouched: () => void;

  writeValue(value: any) {
    this.value = value ? value : "";
  }

  registerOnChange(fn: any) {this.onChange = fn}

  registerOnTouched(fn: any) {this.onTouched = fn}

  setDisabledState(isDisabled) {this.disabled = isDisabled}
}

InputText.html

 <input .. />
2
question is unclear - can you make lives 3 levels above a child more explicit i.e. are these nested components, what is your form? You can make components that pass along whole formgroup values (or mapped values)Andrew Allen
the <form [formGroup]="..."> lies 3 components above the formControlName in the Dom treeuser12250118
Actually you are not obliged to use formControlName you can simply use formControl.get('path.to.prop') for the controls.Sergey
hi @Sergey can you write in example, and I can send points? I have Angular 8 by the way;' is it good practice to do what suggested? or more deprecated practice? thanks againuser12250118

2 Answers

9
votes

You can consider four options:

1) provide ControlContainer on your component with FormControlName

d.component.ts

@Component({
  ...
  viewProviders: [
    {
      provide: ControlContainer,
      useExisting: FormGroupDirective
    }
  ]
})
export class DComponent implements OnInit {

Ng-run Example

2) create simple directive that provides ControlContainer

@Directive({
  selector: '[provideContainer]',
  providers: [
    {
      provide: ControlContainer,
      useExisting: FormGroupDirective
    }
  ]
})
export class ProvideContainerDirective {
}

then place this directive somewhere at the top of nodes hierarchy in your

d.component.html

<ng-container provideContainer>
  <input formControlName="someName">
</ng-container>

Ng-run Example

3) use FormControlDirective instead of FormControlName directive

FormControlDirective requires FormControl instance to be passed

<input [formControl]="control">

You can get this instance either though DI:

d.component.ts

export class DComponent implements OnInit {
  control;
  constructor(private parentFormGroupDir: FormGroupDirective) { }

  ngOnInit() {
    this.control = this.parentFormGroupDir.control.get('someName');
  }

Ng-run Example

or use some service that ties your components.

d.component.ts

export class DComponent implements OnInit {
  control: FormControl;

  constructor(private formService: FormService) { }

  ngOnInit() {
    this.control = this.formService.get('someName');
  }

Ng-run Example

4) pass FormGroup as Input props down to the children or get it through DI or service and then wrap your input[formControlName] with formGroup directive

d.component.html

<ng-container [formGroup]="formGroup">
 <input formControlName="..."
</ng-container>

Ng-run Example

0
votes

Stackblitz

i think this is what you're looking for

follow the stackblitz example

I've created 3 components comp1 comp2 comp3

I've created the signup form in appModule and passing the formGroup to comp1 => comp2 => comp3

In comp3 I've created formControl of age property and binding it. On changing the value of age from comp3 it will get reflected in parent component, that is appComponent

Hope this helps!

Cheers!