0

My app has many Fees. All Fees share the same attributes except a few here and there. Say I have this structure:

// Base Fee
interface IFee {
    id: string;
    name: string;
    price: string;
    date: string;
}

interface IWebFee extends IFee {
    url: string;
    links: number;
}

interface IBookFee extends IFee {
    pageCount: number;
    bookTitle: string;
}

So lets say I wanted to create a Form to edit a BookFee. Content projection wont work since there wont be any context. So I tried creating an embedded view... but I still cant access the parent FormGroup to append controls to it. Here is what I have (which throws an error for missing control because I cant access the FormGroup from the BaseFeeFormComponent):

base-fee-form.component.ts

@Component({
    selector: 'app-base-fee-form',
    ...
    providers: [
    {
        provide: ControlContainer,
        useFactory: (comp: BaseFeeFormComponent) => comp.ngForm,
        deps: [BaseFeeFormComponent],
    },
  ],
})
export class BaseFeeFormComponent implements AfterContentInit {
    @ContentChild('feeChild') templateChild: TemplateRef<any>;
    @ViewChild('mountRef', { read: ViewContainerRef }) vcRef: ViewContainerRef;
    @ViewChild('ngForm') ngForm: FormGroupDirective;
    form: FormGroup;

    constructor(protected _fb: FormBuilder) {
        this.form = this._fb.group({
            name: [],
            price: [],
            date: [],
        });
    }

    ngAfterContentInit() {
        setTimeout(() => this.vc.createEmbeddedView(this.templateChild));
    }
}

base-fee-form.component.html

<form [formGroup]="form" #ngForm="ngForm">
    <div class="control-group">
        <span>Name: </span>
        <input type="text" formControlName="name" />
    </div>

    <div class="control-group">
        <span>Price: </span>
        <input type="text" formControlName="price" />
    </div>

    <div class="control-group">
        <span>Date: </span>
        <input type="date" formControlName="date" />
    </div>

    <div #mountRef></div>
</form>

book-fee-form.component.ts

@Component({
  selector: 'app-book-fee-form',
  templateUrl: './book-fee-form.component.html',
  styleUrls: ['./book-fee-form.component.css'],
  encapsulation: ViewEncapsulation.None,
})
export class BookFeeFormComponent {
  constructor(
      // private _formDirective: FormGroupDirective,
      private _fb: FormBuilder
    ) {
      // this._formDirective.form.addControl('pageCount', this._fb.control(0));
      // this._formDirective.form.addControl('bookTitle', this._fb.control(null));
  }

  ngOnInit() {}
}

book-fee-form.component.html

<app-base-fee-form>
  <ng-template #feeChild>
    <div class="control-group">
      <span>Page Count: </span>
      <input type="text" formControlName="pageCount" />
    </div>

    <div class="control-group">
      <span>Book Title: </span>
      <input type="text" formControlName="bookTitle" />
    </div>
  </ng-template>
</app-base-fee-form>

How do I access the parent NgForm to append the needed controls to the existing FormGroup? Rather, is there an easier way to do this? I'm trying to avoid creating components for each form that share nearly identical templates and functions.

I've created a Stackblitz to show my problem: StackBlitz

1 Answer 1

0

One solution would be to represent the controls as objects, and generate them dynamically

export type control = {
  label: string;
  type: string;
  formControlName: string;
};

base-fee-form.component.html

<form [formGroup]="form" #ngForm="ngForm">
  <ng-container *ngFor="let control of controls">
    <div class="control-group">
      <span>{{ control.label }}: </span>
      <input
        type="{{ control.type }}"
        formControlName="{{ control.formControlName }}"
      />
    </div>
  </ng-container>
</form>

Define your common controls in the base component

export class BaseFeeFormComponent {
  controls: control[] = [
    { label: 'Name', type: 'text', formControlName: 'name' },
    { label: 'Price', type: 'text', formControlName: 'price' },
    { label: 'Date', type: 'date', formControlName: 'date' },
  ];
  form: FormGroup;

  constructor(protected _fb: FormBuilder) {
    this.form = this._fb.group({
      name: [],
      price: [],
      date: [null],
    });
  }
}

Then extend the base component to add new controls

export class BookFeeFormComponent extends BaseFeeFormComponent {
  constructor(protected _fb: FormBuilder) {
    super(_fb);
    this.controls = this.controls.concat([
      { label: 'Page Count', type: 'text', formControlName: 'pageCount' },
      { label: 'Book Title', type: 'text', formControlName: 'bookTitle' },
    ]);
    this.form = this._fb.group({
      ...this.form.controls,
      pageCount: [],
      bookTitle: [],
    });
  }
}

You could make custom html for each component, or just point the child components to the base html

@Component({
  selector: 'app-book-fee-form',
  templateUrl: '../base-fee-form/base-fee-form.component.html',
...

Stackblitz: https://stackblitz.com/edit/angular-ivy-rwm5jw?file=src/app/book-fee-form/book-fee-form.component.ts

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.