BACK
BACK
BACK
Loading...

Optimizing REST Services With Angular [Tips from Our Dedicated Angular Developer]

One of the main stages of developing an Angular application is writing services that will work with data, and interact with the Angular REST API. Here we are to consider one of the options for optimizing and customizing the REST service for Angular developer.

Let’s say we have an application while it’s made up of one module. This module is responsible for obtaining/changing/storing books. The easiest Angular REST service for this module can look like this:

@Injectable()

export class BooksService {

  constructor(

      public http: HttpClient

  ) {

  }




  public getBook(id: string): Observable<Book> {

      return this.http.get<Book>('http://empty.com/books/' + id);

  }




  public getBooks(): Observable<Book[]> {

      return this.http.get<Book[]>('http://empty.com/books');

  }

  // ...

}

Let’s suppose that from the very beginning we specified the URL as a normal string. As the functioning of the module grows, the number of methods of its service will also grow. And if it suddenly changes, it would need a replacement for each method. The situation when the server with the desired API changes the address is quite frequent.

We will create one more module, working for example with magazines, and a REST service for it. Now you need twice as much time to replace the server address. Therefore, we take it to a separate Config file, both services now look like this:

@Injectable()

export class BooksService {

  constructor(

      public http: HttpClient

  ) {

  }




  public getBook(id: string): Observable<Book> {

      return this.http.get<Book>(`${Config.Domain}/books/${id}`);

  }




  public getBooks(): Observable<Book[]> {

      return this.http.get<Book[]>(`${Config.Domain}/books`);

  }

  // ...

}





___




@Injectable()

export class JournalsService {

  constructor(

      public http: HttpClient

  ) {

  }




  public getJournal(id: string): Observable<Journal> {

      return this.http.get<Journal>(`${Config.Domain}/journals/${id}`);

  }




  public getJournals(): Observable<Journal[]> {

      return this.http.get<Journal[]>(`${Config.Domain}/journals`);

  }

  // ...

}


hire Angular experts at Mobilunity

We will continue optimizing the formation of the URL address. To do this, let’s name the specific resource in the “URL” property of the service class and take it from there:

@Injectable()

export class BooksService {

  protected url = 'books';




  constructor(

      public http: HttpClient

  ) {

  }




  public getBook(id: string): Observable<Book> {

      return this.http.get<Book>(`${Config.Domain}/${this.url}/${id}`);

  }




  public getBooks(): Observable<Book[]> {

      return this.http.get<Book[]>(`${Config.Domain}/${this.url}`);

  }

  // ...

}





___




@Injectable()

export class JournalsService {

  protected url = 'journals';




  constructor(

      public http: HttpClient

  ) {

  }




  public getJournal(id: string): Observable<Journal> {

      return this.http.get<Journal>(`${Config.Domain}/${this.url}/${id}`);

  }




  public getJournals(): Observable<Journal[]> {

      return this.http.get<Journal[]>(`${Config.Domain}/${this.url}`);

  }

  // ...

}

Now that the url is dynamically generated, the methods of both services have the same logic, only the data types are different. Therefore, we will create another, abstract rest-service. In it, the required functionality will be defined, and the data type will be specified when inheriting. Also, we change the access to the url property from private to protected, so that the same property is available in the class of the parent and the heir.

Now the services of both modules look like this:

@Injectable()

export class BooksService extends AbstractRestService<Book> {

  protected url = 'books';




  constructor(

      http: HttpClient

  ) {

      super(http);

  }




}

All logic is now in the abstract service:

export abstract class AbstractRestService<T> {

  protected url: string;




  protected constructor(

      public http: HttpClient

  ) {

  }




  public getItem(id: string): Observable<T> {

      return this.http.get<T>(`${Config.Domain}/${this.url}/${id}`);

  }




  public getItems(): Observable<T[]> {

      return this.http.get<T[]>(`${Config.Domain}/${this.url}`);

  }

  // ...

}

Let’s do another small optimization. Now the URL address is dynamically generated, but what if, for example, the version of API appears between the domain name and the resource name? So the current address would be ‘http://empty.com/api_v1/books’. One of the options is to add the version to the string where our domain is stored. We will lose the ability to dynamically change the version of API and, in addition, the variable Domain will actually cease to contain the domain and you can only guess what is stored there.
Therefore, we form the URL address into a separate get-property of our service:

export abstract class AbstractRestService<T> {

   protected url: string;




   protected get apiUrl(): string {

       return [Config.Domain, this.url].join('/');

   }




   protected constructor(

       public http: HttpClient

   ) {

   }




   public getItem(id: string): Observable<T> {

       return this.http.get<T>(`${this.apiUrl}/${id}`);

   }




   public getItems(query?: string): Observable<T[]> {

       return this.http.get<T[]>(`${this.apiUrl}${query}`);

   }

   // ...

}

Now the URL address is formed in one place from the array of incoming parameters and the specific arguments of each method. Sometimes additional methods can be used to form a certain part of the URL address.

The next important improvement is the creation of instances of the objects we need from the incoming data. At this stage, we only specify the type of data that we expect to receive, but in fact the data remains in the JSON format. In order to create the desired object, we add the specific service to the factory, which returns the desired instance of the class. We declare the factory in an abstract REST service as a function that will return an instance of the type we need:

export abstract class AbstractRestService<T> {

  protected url: string;

  protected abstract factory(data): T;




  protected get apiUrl(): string {

      return [Config.Domain, this.url].join('/');

  }




  protected constructor(

      public http: HttpClient

  ) {

  }

}

And we implement this functionality in the service:

@Injectable()

export class BooksService extends AbstractRestService<Book> {

  protected url = 'books';




  protected factory(data: object): Book {

      const book: Book = <Book>data;

      return new Book(

          book.id,

          book.title

      );

  }




  constructor(

      http: HttpClient

  ) {

      super(http);

  }




}

Because incoming data is not typed, you can specify a forced type for quick access, for example, to properties of the Book type.

In most cases, we get either an array or one object, you can use the decorator to add the logic of creating instances to the method:

@ReceivesItems

public getItems(query?: string): Observable<T[]> {

  return this.http.get<T[]>(`${this.apiUrl}${query}`);

}

The decorator is a function in which you can pass three arguments:

Target – either the class constructor will be written here, if the decorated method is static or an instance of the class, if the method is normal.

propertyKey (a string with the name of the method descriptor) is an object that contains the parameters of the method. In this example, we turn to the descriptor.value property, where the method’s function definition is stored and overwrite it. But first we store the method’s original in the variable.

Let’s write a new function into the value property. In the dataObservable goes the result the original method operation, which returns Observable. After that we will return this Observable, but already with the map method, where with the help of the factory we will create the objects we need. In this case “this” will point to BooksService:

function ReceivesItems<T>(target: any, propertyKey: string, descriptor: any) {

  const method = descriptor.value;

  descriptor.value = function (...args) {

      const dataObservable = <Observable<T[]>>method.apply(this, args);




      return dataObservable.pipe(map((data: T[]) => {

          let items = data || [];

          if (items.length) {

              items = items.map((item) => this.factory(item));

          }

          return items;

      }));

  };

}

Now we have an easy in maintenance and support but at the same time effective REST service. Following similar principles, you can continue improving and supplementing its functionality, and use this approach when working with other parts of the Angular application.

We can provide you with Angular developer who can fix all your RESR APIs problems. Just contact us now!

Request a quote

We will contact you as soon as posible.

Attach File (max file size 5MB; allowed extensions: doc, txt, pdf, docx)

Your email address will not be published. Required fields are marked *

Contact us Request a Quote

Your email address will not be published.

Required fields are marked *

Attach File

(max file size 5MB; allowed extensions: doc, txt, pdf, docx)

subscribe to newsletter

Your email address will not be published.

Required fields are marked *

Ask a Question

Your email address will not be published.

Required fields are marked *

Sorry, this page isn't quite ready yet

redirecting to the old site

5

Mobilunity

cannot account for customer alterations, as the site may reflect changes made after the project was completed.

Mobilunity - Dedicated Developers
5