4

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

3
  • Can you provide StackBlitz? Commented Apr 14, 2019 at 5:14
  • Do you want to select a default value for <select>? Commented Apr 14, 2019 at 5:16
  • 1
    use e.g. currecncyControl:new FromControl(this.currencies[0]). And be carefully, you forget put [value] to ours options Commented Apr 14, 2019 at 12:42

2 Answers 2

1

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( '' ),
Sign up to request clarification or add additional context in comments.

1 Comment

I don't think that's the issue. Data gets populated through patchValue() method.
1

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'] );
  }
}

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.