Jghorton14
Jghorton14

Reputation: 754

How to create DRY Angular Code with Observable types?

I am having an issue with the amount of code duplication in both my component as well as my service class. I would like to reduce this where possible while retaining strong typing. Is there a way to reduce the amount of similar code by passing in the interface or something similar?

Lets say we have 5 types:

I have defined all of my types where each type is different except for the Primary Key(id):

interface House {
  address: string;
  squareFt: number;
  id: string;
  ...
}

interface Person {
  name: string;
  age: number;
  id: string
  ...
}

I have my component: app.component.ts

export class AppComponent implements OnInit {
  house: House;
  person: Person;
  animal: Animal;
  plant: Plant;
  car: Car;

  constructor(public dataService: DataService){ }

  ngOnInit() {
    // House
    this.dataService.getHouseData().subscribe(
      (house: House) => {
        this.house = house;
        ...
      },
      (error) => console.error(error),
      () => console.log('Finished House Data')
    );

    // Person
    this.dataService.getPersonData().subscribe(
      (person: Person) => {
        this.person = person;
        ...
      },
      (error) => console.error(error),
      () => console.log('Finished Person Data')
    );

    ...
  }

I have this service: dataService.ts

export class DataService {
  constructor(public http: HttpClient) {}

  handleError(error: Response) {
    // handlesErrors
  }

  retryWithBackOff(delay, maxRetry) {
   // retry http call with exponential backoff
  }

  // Get House Data
  public getHouseData(): Observable<House> {
    return this.http.get<House>(myURL).pipe(
      this.retryWithBackoff(1000, 3),
      catchError(this.handleError)
    )
  }

  // Get Person Data
  public getPersonData(): Observable<Person> {
    return this.http.get<Person>(myURL).pipe(
      this.retryWithBackoff(1000, 3),
      catchError(this.handleError)
    )
  }
  ...
}

What I would like to do is something like this to reduce my code: app.component.ts

export class AppComponent implements OnInit {
  house: House;
  person: Person;
  animal: Animal;
  plant: Plant;
  car: Car;

  constructor(public dataService: DataService){ }

  ngOnInit() {
    const obj = [
      {
        url: 'house'
        type: House
      }, {
        url: 'person'
        type: Person
      },  
      ...
    ];

    _.map(obj, (val) => {
      this.dataService.getData<val.type>().subscribe(
        (data: val.type) => {
          if(val.url === 'house') this.house = data;
          if(val.url === 'person') this.person = data;
          ...
        },
        (error) => console.error(error),
        () => console.log('Finished retrieving data')
      );
    }    
  }

Then: dataService.ts

export class DataService {
  constructor(public http: HttpClient) {}

  handleError(error: Response) {
    // handlesErrors
  }

  retryWithBackOff(delay, maxRetry) {
   // retry http call with exponential backoff
  }

  // Get Data
  public getData<T>(): Observable<t> {
    return this.http.get<T>(myURL).pipe(
      this.retryWithBackoff(1000, 3),
      catchError(this.handleError)
    )
  }
}

Is something like this possible?

Upvotes: 1

Views: 159

Answers (1)

bryan60
bryan60

Reputation: 29335

So, this is just a different approach, combining your streams helps to clean up the code.

  ngOnInit() {
    forkJoin(
      this.dataService.getHouseData(),
      this.dataService.getPersonData()
    ).subscribe(
      ([house: House, person: Person]) => {
        this.house = house;
        this.person = person;
      },
      (error) => console.error(error),
      () => console.log('Finished Data')
    );
  }

but even better, is not subscribing at all and using the async pipe:

  ngOnInit() {
    this.house$ = this.dataService.getHouseData()
    this.person$ = this.dataService.getPersonData()
  }

and in templates you'd use these like so:

<div *ngIf="house$ | async as house">{{house | json}}</div>

<div *ngIf="person$ | async as person">{{person | json}}</div>

or even combine the methods if you like:

  ngOnInit() {
    this.data$ = forkJoin({
      house: this.dataService.getHouseData(),
      person: this.dataService.getPersonData()
    })
  }

<div *ngIf="data$ | async as data">
  {{data.house | json}}
  {{data.person | json}}
</div>

Upvotes: 1

Related Questions