How to deal with many subscriptions in an Angular form with less code and good practices

I have an Angular form where I have many select options, these select options are managed by Subscriptions but I am looking there is too much code for every Subscription.

Can someone suggest me a good practice to reduce the amount of code?

Thanks in advance!

Here is an example: https://github.com/jreategui07/testing-subscriptions-form

Mock server runs with: mockserver -p 8080 -m mock

    this.colorSC = this.catalogueService.getColors().subscribe(
      (response: any) => {
        console.log(response.body);
        this.colorsData = response.body;
      },
      error => 
      {
        console.warn('Error', error);
      }
    );

    this.brandSC = this.catalogueService.getSizes().subscribe(
      (response: any) => {
        console.log(response.body);
        this.brandsData = response.body;
      },
      error => 
      {
        console.warn('Error', error);
      }
    );

    this.sizeSC = this.catalogueService.getFruits().subscribe(
      (response: any) => {
        console.log(response.body);
        this.sizesData = response.body;
      },
      error => 
      {
        console.warn('Error', error);
      }
    );

    this.fruitSC = this.catalogueService.getColors().subscribe(
      (response: any) => {
        console.log(response.body);
        this.fruitsData = response.body;
      },
      error => 
      {
        console.warn('Error', error);
      }
    );

Upvotes: 1

Views: 123

Answers (1)

Joy Biswas
Joy Biswas

Reputation: 6527

You can just hook up the observables to the select's option as observable and use the async pipe to bind the data

Declare Observables for options

colorSC$ : Observable<any>;
brandSC$ : Observable<any>;
sizeSC$  : Observable<any>;
fruitSC$ : Observable<any>;

NOTE: Use an interface instead of any I am not sure the data type so for this answer I am using any. You can define your interfaces and use them here to declare these Observable variables

You can now assign these observables, You can do it in

constructor(private fb: FormBuilder, private catalogueService: CatalogueService) { 
  this.anyForm = this.fb.group({
    color: [''],
    brand: [''],
    size: [''],
    fruit: [''],
  });

  this.colorSC$ = catalogueService.getColors();
  this.brandSC$ = catalogueService.getSizes();
  this.sizeSC$  = catalogueService.getFruits();
  this.fruitSC$ = catalogueService.getColors();
}

and in your template use the async pipe to get the items eg: colorSC$ | async Note that there are safe checks for code and description so that the template doesn't error out item?.code and item?.description

<form (ngSubmit)="onSubmit()" [formGroup]="anyForm">
  <label>Color:</label>
  <select formControlName="color">
    <option *ngFor="let item of colorSC$ | async" [value]="item?.code">{{ item?.description }}</option>
  </select>
  <br><br>

  <label>Brand:</label>
  <select formControlName="brand">
    <option *ngFor="let item of brandSC$ | async" [value]="item?.code">{{ item?.description }}</option>
  </select>
  <br><br>

  <label>Size:</label>
  <select formControlName="size">
    <option *ngFor="let item of this.sizeSC$ | async" [value]="item?.code">{{ item?.description }}</option>
  </select>
  <br><br>

  <label>Fruit:</label>
  <select formControlName="fruit">
    <option *ngFor="let item of fruitSC$ | async" [value]="item?.code">{{ item?.description }}</option>
  </select>
  <br><br>

  <button type="submit">Ok</button>
</form>

In your service you do not have to map them as you are doing nothing useful you are returning the same thing

export class CatalogueService {
  requestOptions: any;
  constructor(public http: HttpClient) {
    const headers = new HttpHeaders({
      'Content-Type': 'text/plain; charset=utf-8'
    });
    this.requestOptions = {headers, observe: 'response'};
  }
  getColors() {
    return this.http.get('http://localhost:8080/getColors', this.requestOptions).pipe(map(res => res.body));
  }
  getBrands() {
    return this.http.get('http://localhost:8080/getBrands', this.requestOptions).pipe(map(res => res.body));
  }
  getSizes() {
    return this.http.get('http://localhost:8080/getSizes', this.requestOptions).pipe(map(res => res.body));
  }
  getFruits() {
    return this.http.get('http://localhost:8080/getFruits', this.requestOptions).pipe(map(res => res.body));
  }
}

So you can get rid of ngOnInit ngOnDestroy since you won't be needing them for this use case. Its better to hook it up at the constructor level as they can operate at constructor level itself


Upvotes: 2

Related Questions