Refresh a drop-down in Angular when database changes occur

2.5k Views Asked by At

I have the following button in my HTML file:

<td width="75%" colspan="2">
   <button mat-raised-button color="primary" type="button" style='margin-right:5px' style='margin-left:auto' (click)="openAddProductDialog()">Add Product</button>
</td>

And the following drop-down:

<td colspan="2" width="100%">
    <mat-form-field class="generate-full-width">
        <mat-select placeholder="Product" formControlName="product" name="product" (selectionChange)="getDefaultValues()">
            <mat-option *ngFor="let product of products" [value]="product.value">
                                    {{product.viewValue}}
            </mat-option>
        </mat-select>
        <mat-error *ngIf="submitted && hasError('product', 'required')">Product is required</mat-error>
    </mat-form-field>

When this button is clicked, a dialog box opens up with the following HTML file:

form [formGroup]="form" (ngSubmit)="addProduct(form)">
  <h1 mat-dialog-title color="primary">Add Product</h1>
  <mat-dialog-content >
      <div style="width:100%;display: flex;flex-direction: column;">
    <mat-form-field>
      <input matInput formControlName="productId" placeholder="Enter the Product Name">
      <mat-error *ngIf="submitted && hasError('productId', 'required')">Product Name is required</mat-error>
    </mat-form-field>
    <mat-form-field>
      <input matInput formControlName="instances" placeholder="Enter Instances" numbersOnly>
      <mat-hint>Allow only numeric values</mat-hint>
    </mat-form-field>
</mat-dialog-actions>
</form>

There is a submit button which saves the product to the database. Now each time I have to refresh the web page so that the newly added product is reflected in the drop-down in the main form. Adding Live Reload to web browser is not an option because of some constraints. I tried to use onEvent and ngModel but almost broke the application. Please help me with this.

Below is the component for Add Product:

@Component({
  selector: 'add-product-dialog',
  templateUrl: 'add-product-dialog.html',
})
export class AddProductDialog implements OnInit {
  form: FormGroup;
  submitted: boolean = false;
  constructor(
    private fb: FormBuilder,
    public dialogRef: MatDialogRef<AddProductDialog>,
    private generateService: GenerateService,
    @Inject(MAT_DIALOG_DATA) public data) {
  }

  ngOnInit() {
    this.form = this.fb.group({
      productId: ['', Validators.required],
      instances: ['']
    });
  }
  // add product 
  addProduct(form: FormGroup) {
    this.submitted= true;
    const { value, valid } = this.form;
    let productId = this.form.value.productId;
    let instances = this.form.value.instances;
    this.generateService.addNewProduct(productId, instances)
    .subscribe((data) => {
      console.log(data)
    })
    console.log(value)
    if (valid) {
      this.dialogRef.close(value);
    }

  }
  // validation for input fields
  public hasError = (controlName: string, errorName: string) => {
    return this.form.controls[controlName].hasError(errorName);
  }

}

The following is for the dialog box:

openAddProductDialog(): void {
    const dialogRef = this.dialog.open(AddProductDialog, {
      width: '450px',
      disableClose: true,
      autoFocus: true
    });

ngOnInit() {
    this.createForm();
    this.generateService.getProducts()
    .subscribe((data) => {
      console.log(data)
      this.allProducts = data;
      this.allProducts.forEach(element => {
        this.products.push({
          "value" : element["productId"],
          "viewValue" : element["productId"]
        })
        
      });
    });
    this.getDefaultConfig();
  }

    dialogRef.afterClosed().subscribe(result => {
      console.log('The dialog was closed');
    });
  }

This is for the main form. Can provide any other info if needed.

Adding components for addProduct and getProduct in service.ts:

addNewProduct(product: string, instances : number) {
    let headers = new HttpHeaders({
      'Content-Type': 'application/json'
    })

    let options = {headers:headers, observer: 'response'};

    let data = {
        "productId" : product,
        "instances" : instances
    }
let result : Observable<Object>= this.http.post(this.url+'/product/add',data,options);
    console.log("service response------>")
    return result;
  }

getProducts() {
    let headers = new HttpHeaders({
      'Content-Type': 'application/json'
    })

    let options = {headers:headers, observer: 'response'};
    let result : Observable<Object> = this.http.get(this.url+'/product/list',options);
    console.log(result);
    return result;

  }
1

There are 1 best solutions below

5
On

There are two types of changes.

  • Changes made by another user, in which case you need something like sockets.io, SignalR, etc to have the server send you notifications when a database change occurs.

  • Changes made by the same user, which is what the next suggestion is about.

You can create a service called for example ProductsService

@Injectable({
    providedIn: 'root'
})
export class ProductsService implements OnDestroy {
    // Subject to cache product list
    private readonly _products$ = new BehaviorSubject<Product[]|null>(null);
    // This observable will fetch from the database if the product are not already fetched
    // And will return the currently cached product list otherwiser
    public readonly products$ = this._products$.pipe(
        switchMap((p) => p === null ? this.getProducts() : of(p))
    );
    
    // On destroy call complete on the observable making sure all subscribers are unsubscribed.
    public ngOnDestroy(): void {
        this._products$.complete();
    }

    // Get products call getProductsFromDatabase and and uses tap to cache the returned data.
    public getProducts(): Observable<Product[]> {
        return this.getProductsFromDatabase()
            .pipe(
                tap((res) => this._products$.next(res)),
            );
    }
    
    // get products from database
    public getProductsFromDatabase(): Observable<Product[]> {
        // Code to fetch products from database here.
        return of([]);
    }

    // Add product that should be called by dialog component
    public async addProduct(newProduct: Product): Promise<void> {
        // Update the new product option id property to that returned from database.       
        newProduct.id = await this.saveProductToDatabase(newProduct);
        // Add product to cached product list.
        const currentProducts = this._products$.value || [];
        currentProducts.push(newProduct);
        // emit the next change to products$, will cause components subscribing to get new value
        this._products$.next(currentProducts);
    }

    // This performs actual request to save product to database.
    // In case the database will generate an id for the product, return it.
    public async saveProductToDatabase(newProduct: Product): Promise<string> {
        // Code to save new product to database.
        // return the product id in case the 
    }
}

Now your both you page and dialog components should reference this service. Your dialog component should call addProduct to save the product to database.

Your select should select from the product$ observable and use async pipe which will subscribe to changes and update the select.

 <mat-option *ngFor="let product of productService.products$ | async" [value]="product.value">
                                {{product.viewValue}}
 </mat-option>