0

In my angular app the array productLocations is assigned in ngOnInit method in subscription and later on used in a method but it still is undefined. From searching on Stackoverflow everyone is saying to move assignment in subscription. It is already in subscription but I receive error. How to correctly execute things in sequence?

ERROR TypeError: Cannot read property 'forEach' of undefined
    at AddProductComponent.populateProductLocation

Component File

export class AddProductComponent implements OnInit, OnDestroy {

  form: FormGroup;
  productManufacturerOptions: ProductManufacturer[] = [];
  filteredProductManufacturerOptions: Observable<ProductManufacturer[]>;
  productModelOptions: ProductModel[] = [];
  filteredProductModelOptions: Observable<ProductModel[]>;
  productCategoryOptions: ProductCategory[] = [];
  filteredProductCategoryOptions: Observable<ProductCategory[]>;

  product: Product = new Product();
  branches: Branch[] = [];
  productLocations: ProductLocation[];
  productPrices: ProductPrice[];
  priceLists: PriceList[] = [];
  editMode = false;
  private _unsubscribeAll: Subject<any>;
  fileToUpload: File = null;

  constructor(
    private http: HttpClient,
    private _formBuilder: FormBuilder,
    private productManufacturerService: ProductManufacturerService,
    private productModelService: ProductModelService,
    private productCategoryService: ProductCategoryService,
    private productService: ProductService,
    private branchService: BranchService,
    private router: Router,
    private route: ActivatedRoute,
    private _fuseProgressBarService: FuseProgressBarService,
    private priceListService: PriceListService,
    private cd: ChangeDetectorRef
  ) {
    this._unsubscribeAll = new Subject();
    this.initForm();
    if (this.route.snapshot.url[0].path == 'edit' && localStorage.getItem("editProductId")) {
      this.editMode = true;
    }
  }

  ngOnInit(): void {
    this._fuseProgressBarService.show();
    if (this.editMode) {
      this.productService.get(localStorage.getItem("editProductId"))
        .subscribe((result: any) => {
          this.product = new Product(result);
          console.log("Product", this.product);
          // this.productLocations = result.locations;
          this.productLocations = this.product.locations;
          this.productPrices = this.product.prices;
          this.populateUpdateData(result);
        });
    }

    forkJoin([this.productManufacturerService.getList(),
    this.productModelService.getList(),
    this.productCategoryService.getList(),
    this.branchService.getList(),
    this.priceListService.getList()
    ])
      .subscribe((results: any) => {
        this.productManufacturerOptions = results[0];
        this.productManufacturerStart();

        this.productModelOptions = results[1];
        this.productModelStart();

        this.productCategoryOptions = results[2];
        this.productCategoryStart();

        this.branches = results[3];
        this.priceLists = results[4];
      },
        error => { },
        () => {
          if (this.editMode) {
            this.populateProductLocations();
            this.populatePriceLists();
          }
          else {
            this.initProductLocations();
            this.initPriceLists();
          }
          this._fuseProgressBarService.hide();
        });
  }

  initForm() {
    this.form = this._formBuilder.group({
      id: [''],
      partNumber: ['', Validators.required],
      description: ['', Validators.required],
      replaceNumbers: this._formBuilder.array([]),
      manufacturer: [null, Validators.required],
      model: [null, Validators.required],
      category: [null, Validators.required],
      cost: ['', Validators.required],
      locations: this._formBuilder.array([]),
      prices: this._formBuilder.array([]),
      featuredImage: [null]
    });
  }

  initProductLocations() {
    this.branches.forEach(branch => {
      this.initProductLocation(branch)
    });
  }
  initProductLocation(branch) {
    const control = <FormArray>this.form.controls['locations'];
    var a = this._formBuilder.group({
      branch: [branch, Validators.required],
      location: [null, Validators.required],
      quantity: [null, Validators.required]
    });
    control.push(a);
  }
  populateProductLocations() {
    this.branches.forEach(branch => {
      this.populateProductLocation(branch)
    });
  }
  populateProductLocation(branch) {
    const control = <FormArray>this.form.controls['locations'];
    var a;
    var productLocationBranchIds = [];    
    this.productLocations.forEach(productLocation => {
      productLocationBranchIds.push(productLocation.branch.id)
    });
    console.log("ProdIDS", productLocationBranchIds);
    if (productLocationBranchIds.includes(branch.id)) {
      this.productLocations.forEach(productLocation => {
        if (branch.id == productLocation.branch.id) {
          a = this._formBuilder.group({
            branch: [productLocation.branch, Validators.required],
            location: [productLocation.location, Validators.required],
            quantity: [productLocation.quantity, Validators.required]
          });
        }
      });
    }
    else {
      console.log("In else");
      a = this._formBuilder.group({
        branch: [branch, Validators.required],
        location: [null, Validators.required],
        quantity: [null, Validators.required]
      });
    }
    control.push(a);
  }


  initPriceLists() {
    this.priceLists.forEach(priceList => {
      this.initPriceList(priceList)
    });
  }
  initPriceList(priceList) {
    const control = <FormArray>this.form.controls['prices'];
    var a = this._formBuilder.group({
      priceList: [priceList, Validators.required],
      price: [null, Validators.required]
    });
    control.push(a);
  }

  populatePriceLists() {
    this.priceLists.forEach(priceList => {
      this.populatePriceList(priceList)
    });
  }
  populatePriceList(priceList) {
    const control = <FormArray>this.form.controls['prices'];
    var a;
    var productPricesPriceListIds = [];
    this.productPrices.forEach(productPrice => {
      productPricesPriceListIds.push(productPrice.priceList.id)
    });
    if (productPricesPriceListIds.includes(priceList.id)) {
      this.productPrices.forEach(productPrice => {
        if (priceList.id == productPrice.priceList.id) {
          a = this._formBuilder.group({
            priceList: [productPrice.priceList, Validators.required],
            price: [productPrice.price, Validators.required]
          });
        }
      });
    }
    else {
      a = this._formBuilder.group({
        priceList: [priceList, Validators.required],
        price: [null, Validators.required]
      });
    }
    control.push(a);
  }

  get replaceNumbers(): FormArray {
    return this.form.get('replaceNumbers') as FormArray;
  }
  createReplaceNumber(): FormGroup {
    return this._formBuilder.group({
      partNumber: ['', Validators.required]
    });
  }
  addExistingReplaceNumber(value): FormGroup {
    return this._formBuilder.group({
      partNumber: [value, Validators.required]
    });
  }
  addReplaceNumber() {
    this.replaceNumbers.push(this.createReplaceNumber());
  }
  removeReplaceNumber(index: number) {
    this.replaceNumbers.removeAt(index);
  }

  populateUpdateData(product) {
    this.form.get('id').setValue(product.id);
    this.form.get('partNumber').setValue(product.partNumber);
    this.form.get('description').setValue(product.description);
    this.form.get('manufacturer').setValue(product.manufacturer);
    this.form.get('model').setValue(product.model);
    this.form.get('category').setValue(product.category);
    this.form.get('cost').setValue(product.cost);
    let rs = product.replaceNumbers;
    rs.forEach(element => {
      this.replaceNumbers.push(this.addExistingReplaceNumber(element.partNumber));
    });
  }

  ngOnDestroy(): void {
    this._unsubscribeAll.next();
    this._unsubscribeAll.complete();
  }
}
usmanwalana
  • 1,002
  • 2
  • 14
  • 31
  • Does this answer your question? [Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference](https://stackoverflow.com/questions/23667086/why-is-my-variable-unaltered-after-i-modify-it-inside-of-a-function-asynchron) – Heretic Monkey Apr 24 '20 at 14:54

1 Answers1

1

It's likely happening because the following block is being executed sooner than the subscription:

          if (this.editMode) {
            this.populateProductLocations();
            this.populatePriceLists();
          }

To verify this, you can do a console.log (or put a breakpoint) in that block and also in the subscription where you are assigning a value.

You should consider refactoring your code, so that this.populateProductLocations() comes after this.productLocations = this.product.locations; in the same code block, or waits until this.productService.get(localStorage.getItem("editProductId")) completes (maybe with a swithcMap, combineLatest or withLatestFrom).

with ForkJoin

forkJoin should work too since it waits for all observables to complete, and since you already have it, you can move your code into the forkJoin, something like this (I commented out existing code to clarify what was added):

forkJoin([
  // this.productManufacturerService.getList(),
  // this.productModelService.getList(),
  // this.productCategoryService.getList(),
  // this.branchService.getList(),
  // this.priceListService.getList(),
  this.productService.get(localStorage.getItem("editProductId"))
])
  .subscribe((results: any) => {
    // this.productManufacturerOptions = results[0];
    // this.productManufacturerStart();

    // this.productModelOptions = results[1];
    // this.productModelStart();

    // this.productCategoryOptions = results[2];
    // this.productCategoryStart();

    // this.branches = results[3];
    // this.priceLists = results[4];

    this.product = new Product(result);
    this.productLocations = this.product.locations;
    this.productPrices = this.product.prices;
    this.populateUpdateData(results[5]);
  },
ulmas
  • 2,203
  • 16
  • 22