Search code examples
typescriptrefactoringdrysingle-responsibility-principle

Handling code duplication in typescript code


I am refactoring a poorly written Angular2 code which has service methods like:

  fooServiceName(body): Observable<any> {
    let headers = new Headers();
    this.loginService.writeAuthToHeaders(headers);
    return this.http.put(this.loginService.url() + '/url', body, { headers: headers })
    .map(response => response.json());
  }

  barResource(body): Observable<any> {
    let headers = new Headers();
    this.loginService.writeAuthToHeaders(headers);
    return this.http.post(this.loginService.url() + '/url', body, { headers: headers })
    .map(response => response.json());
  }

I see that following lines are used repetitively at many places:

let headers = new Headers();
this.loginService.writeAuthToHeaders(headers);

I thought to write a separate method which would call these lines but the issue is that someone new to the project has to remember to call that method, is there any better approach?

Update:

Adding missing method definition:

  /** function to write username and password to local storage of browser*/
  public writeAuthToHeaders(headers: Headers): void {
    headers.append('Accept', 'application/json');
    headers.append('Content-Type', 'application/json');
    headers.append('Authorization', 'Basic ' + btoa(getUsername() + ':' + this.getPassword()));
  }

Solution

  • You could write a complex type if the classes are almost identical, and pass differences to the constructor. I wrote the following as an example:

    Generic.Service.ts

    import { Http } from '@angular/http';
    import { Observable } from 'rxjs/Rx';
    
    export class GenericService<T> {
        protected http: Http;
    
        constructor(private TCreator: { new (properties: any): T }, private url: string) {}
    
        public get(id: number): Observable<T> {
            return this.requestSingle(`${id}`);
        }
    
        public getAll(): Observable<T[]> {
            return this.requestMultiple('/all.json');
        }
    
        private requestSingle(addedUrl?: string): Observable<T> {
            return this.request(addedUrl)
            .map((response: Response) => new this.TCreator(response.json()));
        }
    
        private requestMultiple(addedUrl?: string): Observable<T[]> {
            return this.request(addedUrl)
            .map((response: Response) => {
                const results: any = response.json();
                for (let i = 0; i < results.length; i++) {
                    results[i] = new this.TCreator(results[i]);
                }
                return results;
            });
        }
    
        private request(addedUrl?: string): Observable<any> {
            return this.http.get(this.url + (addedUrl ? addedUrl : ''));
        }
    }
    

    Example usage:

    Vegetable.Service.ts

    import { Injectable } from '@angular/core';
    import { Http } from '@angular/http';
    
    import { GenericService } from './generic.service';
    import { Vegetable } from '../app.component';
    
    @Injectable()
    export class VegetableService extends GenericService<Vegetable> {
        constructor(protected http: Http) {
            super(Vegetable, '/vegetables');
        }
    }
    

    Vegetable.ts

    interface IVegetable {
      id: number;
      name: string;
      type: string;
    }
    
    export class Vegetable implements IVegetable {
      public id: number;
      public name: string;
      public type: string;
    
      constructor(properties: IVegetable) {
        this.id = properties.id;
        this.name = properties.name;
        this.type = properties.type;
      }
    }
    

    Some.Component.ts

    import { Component, OnInit } from '@angular/core';
    
    import { VegetableService } from './services/vegetable.service';
    import { IVegetable } from '.components/vegetable.component';
    
    @Component({
      selector: 'some-component',
      templateUrl: './some.component.html'
    })
    export class SomeComponent implements OnInit {
      public vegetables: IVegetable[] = [];
    
      constructor(private vegetableService: VegetableService) { ... }
    
      public ngOnInit(): void {
        ...
        this.vegetableService.getAll().subscribe((result) => this.vegetables = result);
        ...
      }
    }