Search code examples
angulartypescriptangular-forms

How do I initialise a reactive form using ngOnInit?


I have been trying to initialise my reactive form with using an observable that I subscribed to. In the form class template, I used the ngOnInit hook to obtain the object that I want, in this case, the product object.

First code is the class template, second code is the html template where I pass the product using a behavioural subject.

I then, will get the error "Property 'product' is used before its initialisation." in the first line of the form control name instance. I think this is because the product form is created before the ngOnInit hook, therefore it could not access the object. Any ideas? Have been stuck here for so long, will appreciate any help!

product-form-component.ts:

export class ProductFormComponent {

  productReceived: Subscription;
  editMode: boolean = false;
  product: product;


  constructor(private productService: ProductService) { }

  ngOnInit() {
    this.productReceived = this.productService.$emitProduct.subscribe((product: product) => {
      if (product) {
        this.product = product;
        this.editMode = true;
      }
    });

  }

   productForm = new FormGroup({
    name: new FormControl(`${this.product}`, [Validators.required, Validators.minLength(3)]), // THIS LINE
    description: new FormControl(''),
    specification: new FormControl(''),
    price: new FormControl(''),
    imagePath: new FormControl(''),
    warranty: new FormControl(''),
  });

  ngOnDestroy() {
    this.productReceived.unsubscribe();
  }

}

product-item.component.html:

<div style="cursor: pointer; width: 450px;" class="card rounded p-3"
  aria-current="true">
  <div>
    <div class="d-flex w-100 justify-content-between mb-1">
      <div class="d-flex" style="gap: 0.75rem;">
        <h5>{{ product.name | titlecase}}</h5>
        <span style="color: darkred;">${{product.price}}</span>
      </div>
      <div class="dropdown">
        <button class="btn btn-dark dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
          Manage Product
        </button>
        <ul class="dropdown-menu">
          <li><a class="dropdown-item" >Add to Cart</a></li>
          <li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#staticBackdrop" style="cursor: pointer;" (click)="formOpen(product)">Edit</a></li>
          <li><a class="dropdown-item" style="cursor: pointer;">Delete</a></li>
        </ul>
      </div>
    </div>
    <div class="d-flex flex-row align-items-center" [routerLink]="product.name" (click)="emitProduct(product)">
      <img [src]="product.imagePath[0]" style="max-height: 175px">
      <ul class="product-desc">
        <li>{{productDescList[0]}}</li>
        <li>{{productDescList[1]}}</li>
      </ul>
    </div>
  </div>
</div>

<app-modal-pop></app-modal-pop>

product-form.component.html:

<form [formGroup]="productForm">

  <h5>{{editMode? 'Edit Product': 'Create Product'}}</h5>

  <div>
    <app-input-field label="Name" [control]="productForm.get('name')"></app-input-field>
  </div>
  <div>
    <app-input-field label="Description" [control]="productForm.get('description')"></app-input-field>
  </div>
  <div>
    <app-input-field label="Specification" [control]="productForm.get('specification')"></app-input-field>
  </div>
  <div>
    <app-input-field label="Price" [control]="productForm.get('price')"></app-input-field>
  </div>
  <div>
    <app-input-field label="Image Path" [control]="productForm.get('imagePath')"></app-input-field>
  </div>
  <div>
    <app-input-field label="Warranty" [control]="productForm.get('warranty')"></app-input-field>
  </div>

  <button type="submit" class="btn btn-secondary">Create</button>

</form>

input-field.component.html:

<label class="label">{{label}}:</label>
<input type="text" [formControl]="control" id="name" [ngClass]="{'alert-danger': showErrors()}">
<ng-container *ngIf="control.errors && control.dirty && control.touched">
  <div style="color: red;" *ngIf="control.errors.required">
    Name is required!
  </div>
  <div style="color: red;" *ngIf="control.errors.minlength">
    3 minimum characters!
  </div>
</ng-container>


Solution

  • You are accessing this.product before it's created.

    this.productService.$emitProduct.subscribe it's fired AFTER you use this.product it in the form initialization.

    Create the form in the constructor and to initialize it in the OnInit function

    export class ProductFormComponent {
    
      productReceived?: Subscription;
      editMode: boolean = false;
      product?: product;
      productForm: FormGroup;
    
    
      constructor(private productService: ProductService) { 
          this.productForm = new FormGroup({
              name: new FormControl('', [Validators.required,Validators.minLength(3)]),
              description: new FormControl(''),
              specification: new FormControl(''),
              price: new FormControl(''),
              imagePath: new FormControl(''),
              warranty: new FormControl(''),
          });
        }
    
        ngOnInit() {
            this.productReceived = this.productService.$emitProduct.subscribe((product: product) => {
                if (product) {
                    this.product = product;
                    this.editMode = true;
                    this.productForm.patchValue({...this.product})
                }
            });
          }
    
        ngOnDestroy() {
          this.productReceived.unsubscribe();
        }
    }