3

I'm not sure how can I use a custom component if it's wrapper under another component.

Like:

ComponentA_withForm
|
--ComponentA1_withWrapperOfCustomInput
  |
  --ComponentA11_withCustomInput

if I have a structure like this:

ComponentA_withForm
|
--ComponentA11_withCustomInput

Everything's fine

But for my case (tons of async data) I need a wrapper... Is it possible somehow to do this?

Here is my fiddle code:

ComponentA:

import { Component } from '@angular/core';
import { FormBuilder } from '@angular/forms';

@Component({
  selector: 'my-app',
  template: `<form [formGroup]="form"><custom-input-wrapper formControlName="someInput"></custom-input-wrapper></form> <p>value is: {{formVal | json}}</p>`
})
export class AppComponent {
  form = this.fb.group({
    someInput: [],
  });

  get formVal() {
    return this.form.getRawValue();
  }

  constructor(private fb: FormBuilder) { }
}

ComponentA1:

import { Component } from '@angular/core';

@Component({
  selector: 'custom-input-wrapper',
  template: '<custom-input></custom-input>',
})
export class CustomInputWrapperComponent {
  constructor() { }
}

ComponentA11:

import { Component, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

@Component({
  selector: 'custom-input',
  template: `Hey there! <button (click)="inc()">Value: {{ value }}</button>`,
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => CustomInputComponent),
    multi: true,
  }],
})
export class CustomInputComponent implements ControlValueAccessor {

  private value = 0;

  writeValue(value: number): void {
    this.value = value;
  }

  registerOnChange(fn: (_: any) => void): void {
    this.onChangeFn = fn;
  }

  registerOnTouched(fn: any): void {
  }

  inc() {
    this.value = this.value + 1;
    this.onChangeFn(this.value);
  }

  onChangeFn = (_: any) => { };
}

And here I have a working sample: https://stackblitz.com/edit/angular-qmrj3a

so: basically removing & refactoring code not to use CustomInputWrapperComponent makes my code working. But I need this wrapper and I'm not sure how to pass formControlName then.

I don't want a dirty solution with passing parent formGroup :)

3
  • Why not implement ControlValueAccessor in your CustomInputWrapperComponent as well ? Commented Jun 19, 2019 at 13:45
  • @abd995I think it's dirty way as well :) Commented Jun 19, 2019 at 14:00
  • I think that's most cleanest solution you could possibly have. Passing in the form control or form group is I guess the dirty solution. Commented Jun 19, 2019 at 14:02

2 Answers 2

4

Since you don't want a dirty solution ;) , you could just implement ControlValueAccessor in the CustomInputWrapperComponent also. That way any change in the parent will be reflected in the child, any change in the child will be reflected in the parent as well with just few lines of code.

Wrapper Component

@Component({
  selector: 'custom-input-wrapper',
  template: '<custom-input [formControl]="value"></custom-input>',
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => CustomInputWrapperComponent),
    multi: true,
  }]
})
export class CustomInputWrapperComponent implements AfterViewInit, ControlValueAccessor  {
  public value = new FormControl();

  constructor() { }

  ngAfterViewInit() {
    this.value.valueChanges.subscribe((x) => {
      this.onChangeFn(x);
    });
  }

  writeValue(value: number): void {
    this.value.setValue(value);
  }

  registerOnChange(fn: (_: any) => void): void {
    this.onChangeFn = fn;
  }

  registerOnTouched(fn: any): void {
  }

 onChangeFn = (_: any) => { };
}

Parent Template

<form [formGroup]="form"><custom-input-wrapper formControlName="someInput"></custom-input-wrapper></form> <p>value is: {{formVal | json}}</p>

I have made a stackbitz demo here - https://stackblitz.com/edit/angular-csaxcz

Sign up to request clarification or add additional context in comments.

4 Comments

If possible could you add the relevant parts of your code to your answer? Link-only answers become irrelevant if the link no longer works, in case of stackblitz demos its also a job for the viewer to find the relevant parts. I would strongly recommend to add comments to the code in case you write them for someone else (like someone asking here on Stack Overflow).
@SaschaM78 I have added the some parts of the code to the answer. Thanks
thanks a lot for adding the relevant code parts, this makes the answer way more comprehensible.
@abd995 thanks for this solution, it worked great. Only thing I modified, was instead of using valueChanges to trigger the onchange fn for the wrapper component, I grabbed the inner component using ViewChild and then used it to trigger the onchange like: this.innerComponent.registerOnTouched(this.onTouched); since I didn't want writeValue to trigger the onchange.
2

you cannot use formControlName on custom-input-wrapper because it doesn't implement ControlValueAccessor. implementing ControlValueAccessor on custom-input-wrapper might be a solution but it seems to be overkill. Instead pass the control from formGroup to custom-input-wrapper as an @Input() and pass the inputed formControl to custom-input

app.component

@Component({
  selector: 'my-app',
  template: `<form [formGroup]="form"><custom-input-wrapper [formCtrl]="form.get('someInput')"></custom-input-wrapper></form> <p>value is: {{formVal | json}}</p>`
})
export class AppComponent {
  form = this.fb.group({
    someInput: [],
  });

  get formVal() {
    return this.form.getRawValue();
  }

  constructor(private fb: FormBuilder) { }
}

custom-input-wrapper.component

@Component({
  selector: 'custom-input-wrapper',
  template: '<custom-input [formControl]="formCtrl"></custom-input>',
})
export class CustomInputWrapperComponent {
  @Input() formCtrl: AbstractControl;
  constructor() { }
}

here is a working demo https://stackblitz.com/edit/angular-3lrfqv

2 Comments

As the OP had already mentioned. I don't want a dirty solution with passing parent formGroup. Passing formGroup as input might not be the solution he might be looking for.
Angular docs state that "ControlValueAccessor defines an interface that acts as a bridge between the Angular forms API and a native element in the DOM". imho, implementing ControlValueAccessor in CustomInputWrapperComponent is a dirty solution because there is no native element in CustomInputWrapperComponent that needs to be bridged with Angular Forms API.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.