4
votes

In my application,ngOnInit() loads currencies, categories, and manufacturers. I am using Angular 7 reactive forms for this. Data loads as expected and dropdowns populated with values and the first option selected and showed to the user. So, here is the problem, after finishing form and clicking on submit (using default dropdown values), I see an empty object for the category, currency, and manufacturer.

ProductNewComponent.ts

import {Component, OnInit} from '@angular/core';
import {Router} from '@angular/router';
import {ProductService} from '../service/product.service';
import {FormControl, FormGroup, Validators} from '@angular/forms';
import {CATEGORY_API_URL, CURRENCY_API_URL, MANUFACTURER_API_URL, PRODUCT_API_URL, SERVER_URL} from '../../../app.constants';
import {Product} from '../model/product';
import {Price} from '../model/price';
import {CategoryService} from '../../category/service/category.service';
import {Category} from '../../category/model/category';
import {Currency} from '../model/currency';
import {Manufacturer} from '../model/manufacturer';

@Component({
  selector: 'app-product-new',
  templateUrl: './product-new.component.html',
  styleUrls: ['./product-new.component.css']
})
export class ProductNewComponent implements OnInit
{
  categories: Array<Category>;
  currencies: Array<Currency>;
  manufacturers: Array<Manufacturer>;

  productForm = new FormGroup({
    id: new FormControl({value:'',disabled:true}, Validators.minLength(2)),
    name: new FormControl(''),
    description: new FormControl(''),
    price: new FormControl(''),
    amount: new FormControl(''),
    categoryControl: new FormControl(''),
    currencyControl: new FormControl( '' ),
    manufacturerControl: new FormControl( '' ),
  });
  constructor(private productService:ProductService, private categoryService:CategoryService,private router:Router) {}

  ngOnInit()
  {
    this.loadCategories();
    this.loadCurrencies();
    this.loadManufacturers();
  }


  createProduct()
  {
    const product=new Product();
    product.name=this.productForm.value.name;
    product.description=this.productForm.value.description;
    product.price=new Price(this.productForm.value.currencyControl, this.productForm.value.price);
    product.category=this.productForm.value.categoryControl;
    product.manufacturer=this.productForm.value.manufacturerControl;

    product.createdBy='Admin';
    product.createdDate='';
    product.lastModifiedBy='Admin';
    product.lastModifiedDate='Admin';


    const url=SERVER_URL+PRODUCT_API_URL+'create';

    this.productService.createProduct(url,product).subscribe(
      value =>
      {
        console.log('Successfully created product');
      },error1 =>
      {
        console.log('Failed to create product');
      },
      ()=>{
        this.router.navigate(['/product/list']);
      });
  }


  private loadCategories()
  {
    const url=SERVER_URL+CATEGORY_API_URL+'list';

    this.categoryService.getCategories(url).subscribe(
      categories =>
      {
        this.categories=categories;
      },
        error1 =>
      {
      },
      ()=>{
      });
  }

  private loadCurrencies()
  {
    const url=SERVER_URL+CURRENCY_API_URL+'list';

    this.productService.getCurrencies( url ).subscribe(
      currencies =>
      {
        this.currencies=currencies;
      },
      error1 =>
      {
      },
      () =>
      {
      } );
  }


  private loadManufacturers()
  {
    const url=SERVER_URL+MANUFACTURER_API_URL+'list';

    this.productService.getManufacturers( url ).subscribe(
      manufacturers =>
      {
        this.manufacturers=manufacturers;
      },
      error1 =>
      {
      },
      () =>
      {
      } );
  }

  manufacturersDataAvailable(): boolean
  {
    return this.manufacturers!==undefined;
  }

  categoriesDataAvailable(): boolean
  {
    return this.categories!==undefined;
  }

  currenciesDataAvailable(): boolean
  {
    return this.currencies!==undefined;
  }

  goBack() {
    this.router.navigate(['/product']);
  }
}

product.component.html

<div>
<h2>Create New Product</h2>

<br/>
<form  [formGroup]="productForm">
  <div class="form-group">
    <label for="id">Product Id</label>
    <input type="text" class="form-control"  id="id" formControlName="id">
    <small id="emailHelp" class="form-text text-muted">Automatically generated by system</small>
  </div>
  <div class="form-group">
    <label for="name">Name</label>
    <input type="text" class="form-control"  id="name" formControlName="name" required placeholder="Enter Product Name">
  </div>
  <div class="form-group">
    <label for="description">Description</label>
    <input type="text" width="200" height="100" class="form-control"  id="description" formControlName="description" required placeholder="Enter Product Description">
  </div>
  <div class="form-group">
    <label for="currencyControl">Price</label> <br/>
    <label>
      <select class="form-control" formControlName="currencyControl" name="currencyControl" id="currencyControl">
        <option *ngFor="let currency of currencies">
          {{currency.name}}
        </option>
      </select>
    </label>
    <input formControlName="amount" id="amount" placeholder="Enter Product Price (in USD)" required
           style="margin: 10px; padding: 5px" type="text">
  </div>

  <div class="form-group">
    <label>Category:
      <select class="form-control" name="categoryControl" formControlName="categoryControl">
        <option *ngFor="let category of categories">
          {{category.name}}
        </option>
      </select>
    </label>
  </div>

  <div class="form-group">
    <label>Manufacturer:
      <select class="form-control" formControlName="manufacturerControl" name="manufacturerControl">
        <option *ngFor="let manufacturer of manufacturers">
          {{manufacturer.name}}
        </option>
      </select>
    </label>
  </div>

  <button type="submit" class="btn btn-primary"  (click)="createProduct()">Submit</button>
  <button type="button" class="btn btn-primary"  style="margin-left: 30px" (click)="goBack()">Cancel</button>

</form>

</div>

If I change the select statement to use [ngValue]="category" and when the data loaded from the server I see no default value in dropdowns and throws an error property ng value is not provided by any applicable directives nor by option element

2
Can you provide StackBlitz?Prashant Pimpale
Do you want to select a default value for <select>?Prashant Pimpale
use e.g. currecncyControl:new FromControl(this.currencies[0]). And be carefully, you forget put [value] to ours optionsEliseo

2 Answers

1
votes

I think this is a duplicte of Angular 4 - Select default value in dropdown [Reactive Forms]

Problem is that you set the default value to '' at:

 categoryControl: new FormControl(''),
currencyControl: new FormControl( '' ),
manufacturerControl: new FormControl( '' ),
1
votes

I found the solution. For some reason, Angular does not update the category option (category object) when the update applied through patchValue() method. Based on this documentation , I implemented compareCategoryFn and compareManufacturerFn. This compares old values with new values and updates the dropdown with the new value which is received through patchValue() method

product-edit.component.html

    <div *ngIf="productDataAvailable()">
  <h2>Update Product</h2>
  <br/>
  <form [formGroup]="productForm">
    <div class="form-group">
      <label for="id">Product Id</label>
      <input class="form-control" formControlName="id" id="id" name="id"type="text">
      <small class="form-text text-muted" id="emailHelp"></small>
    </div>
    <div class="form-group">
      <label for="name">Name</label>
      <input class="form-control" formControlName="name" id="name" type="text">
    </div>
    <div class="form-group">
      <label for="description">Description</label>
      <input  type="text" class="form-control" formControlName="description" id="description" required>
    </div>
    <div class="form-group">
      <label for="amount">Price (In $)</label> <br/>
      <input type="text" formControlName="amount" id="amount"  required >
    </div>

    <div class="form-group">
      <label>Category:
        <select [compareWith]="compareCategoryFn"  class="form-control"formControlName="categoryControl" name="categoryControl">
          <option *ngFor="let category of categories" [ngValue]="category">
            {{category.name}}
          </option>
        </select>
      </label>
    </div>

    <div class="form-group">
      <label>Manufacturer:
        <select  [compareWith]="compareManufacturerFn"  class="form-control" formControlName="manufacturerControl" name="manufacturerControl">
          <option *ngFor="let manufacturer of manufacturers" [ngValue]="manufacturer">
            {{manufacturer.name}}
          </option>
        </select>
      </label>
    </div>

    <button (click)="updateProduct()" class="btn btn-primary" type="submit">Update</button>
    <button (click)="goBack()" class="btn btn-primary" style="margin-left: 30px" type="button">Cancel</button>

  </form>

</div>

product-edit.component.ts

 import {Component, OnInit} from '@angular/core';
import {Product} from '../model/product';
import {ProductService} from '../service/product.service';
import {ActivatedRoute, Router} from '@angular/router';
import {CATEGORY_API_URL, CURRENCY_API_URL, MANUFACTURER_API_URL, PRODUCT_API_URL, SERVER_URL} from '../../../app.constants';
import {FormControl, FormGroup} from '@angular/forms';
import {Currency} from '../model/currency';
import {Category} from '../../category/model/category';
import {Manufacturer} from '../../manufacturer/model/manufacturer';
import {CategoryService} from '../../category/service/category.service';

@Component( {
              selector: 'app-product-edit',
              templateUrl: './product-edit.component.html',
              styleUrls: ['./product-edit.component.css']
            } )
export class ProductEditComponent implements OnInit
{
  product: Product;
  categories: Array<Category>;
  currencies: Array<Currency>;
  manufacturers: Array<Manufacturer>;

  productForm=new FormGroup(
    {
               id: new FormControl( {value: '', disabled: true} ),
               name: new FormControl( '' ),
               description: new FormControl( '' ),
               price: new FormControl( '' ),
               amount: new FormControl( '' ),
               categoryControl: new FormControl(null ),
               currency: new FormControl( '' ),
               manufacturerControl: new FormControl( null )
             } );


  constructor(private productService: ProductService,
              private categoryService: CategoryService,
              private route: ActivatedRoute,
              private router: Router)
  {
  }

  ngOnInit()
  {
    this.loadCategories();
    this.loadCurrencies();
    this.loadManufacturers();
    this.getProduct();
  }

  private getProduct()
  {
    const id=this.route.snapshot.paramMap.get( 'id' );
    const url=SERVER_URL+PRODUCT_API_URL+'find/'+id;
    this.productService.getProductDetails( url ).pipe()
        .subscribe(
          data =>
          {
            this.product=data;
            this.productForm.patchValue(
              {
                      id: data.id,
                      name: data.name,
                      description: data.description,
                      price: data.price,
                      amount: data.price.amount,
                      currency: data.price.currency,
                      categoryControl: data.category,
                      manufacturerControl: data.manufacturer,
                    });
          },
          error =>
          {
            console.log( error );
          },
          () => console.log( 'getProduct() success' ) );
  }

  private updateProduct()
  {
    const id=this.route.snapshot.paramMap.get( 'id' );
    const url=SERVER_URL+PRODUCT_API_URL+'update';

    const product = new Product();
    product.id=Number( id );
    product.name=this.productForm.value.name;
    product.description=this.productForm.value.description;

    product.category=this.productForm.value.categoryControl;
    product.manufacturer=this.productForm.value.manufacturerControl;
    product.price=this.productForm.value.price;
    product.price.amount=this.productForm.value.amount;

    product.lastModifiedBy='Admin';
    product.lastModifiedDate='Admin';


    this.productService.updateProduct( url, product ).subscribe(
      value =>
      {
        console.log( 'Successfully updated product' );
      }, error1 =>
      {
        console.log( 'Failed to update product' );
      },
      () =>
      {
        this.router.navigate( ['/product/list'] );
      } );
  }

  private loadCategories()
  {
    const url=SERVER_URL+CATEGORY_API_URL+'list';

    this.categoryService.getCategories( url ).subscribe(
      categories =>
      {
        this.categories=categories;
        console.log( 'Successfully loaded categories' );
      },
      error1 =>
      {
        console.log( 'Failed to load categories' );
      },
      () =>
      {
      } );
  }

  private loadCurrencies()
  {
    const url=SERVER_URL+CURRENCY_API_URL+'list';

    this.productService.getCurrencies( url ).subscribe(
      currencies =>
      {
        this.currencies=currencies;
      },
      error1 =>
      {
        console.log( 'Failed to load currencies' );
      },
      () =>
      {
      } );
  }

  private loadManufacturers()
  {
    const url=SERVER_URL+MANUFACTURER_API_URL+'list';

    this.productService.getManufacturers( url ).subscribe(
      manufacturers =>
      {
        this.manufacturers=manufacturers;
        console.log( 'Successfully loaded manufacturers' );
      },
      error1 =>
      {
        console.log( 'Failed to load manufacturers' );
      },
      () =>
      {
      } );
  }

  productDataAvailable(): boolean
  {
    return this.product!==undefined;
  }


  compareCategoryFn(c1: Category, c2: Category): boolean
  {
    return c1 && c2 ? c1.id === c2.id : c1 === c2;
  }

  compareManufacturerFn(c1: Manufacturer, c2: Manufacturer): boolean
  {
    return c1 && c2 ? c1.id === c2.id : c1 === c2;
  }

  goBack()
  {
    this.router.navigate( ['/product/list'] );
  }
}