Blog

- August 29, 2016

The HTTP support allows us to interact with remote servers within HTML pages leveraging reactive programming. That being said it suffers from limitations at the level of the API itself. The API wasn’t as easy to use as that of version 1 and this can create difficulties for writing concise and robust code. Moreover, it wasn’t possible to exchange binary data without hacking the framework.

In this article, we will describe the latest improvements of the HTTP module and how to use them.

Let’s put up a remote backend by creating a Web API with the APISpark platform.

Restlet Cloud Web API used

Since we tackle the HTTP support of Angular 2, we need a backend to show its new cool features in action. Restlet Cloud is the perfect match since we can create a Web API very quickly. We can make use of the quickstart wizard for a predefined Web API to handle contacts and companies.

angular2-webapi

Of course, using APISpark we can define Web APIs with specific entities, but that’s outside the scope of this article. For more details, you can refer to the Restlet Cloud user guide.

Using text payloads

If you used Angular 2 before its RC2 version, you know that you needed to create text payloads to send on your own. If you use the JSON format, you need to explicitly call the JSON.stringify method.

Imagine that we want to add a new contact in our Web API. The expected structure for the payload can be found out within the APISpark console.

angular2-contact-representation

For example, if you wanted to send a JSON payload within a POST method, you would write the following:

let input = {
  firstName: 'first name',
  (...)
};
let body = JSON. stringify(input);

let headers = new Headers();
headers.append('Content-Type', 'application/json');

this.http.post('https://angular2.apispark.net/v1/contacts/', body, { headers })
   .subscribe(...);

It’s really verbose and most of the things can be deduced. From RC2 version, Angular 2 hides all this plumbing code to let you use something more concise:

let input = {
  firstName: 'first name',
  (...)
};

this.http.post('https://angular2.apispark.net/v1/contacts/', input)
   .subscribe(...);

Under the hood, Angular 2 does the following for you:

  • Detects that the input is a raw object.
  • Automatically adds the Content-Type header with the value application/json.
  • Converts the input object into a string using JSON.stringify and uses internally as input.

Other kinds of objects can be provided as an input of methods of the Http class. That’s the case for string and number types. In this case, they will be sent as a string content and the Content-Type header and the value text/plain will be used.

In some cases, you may want to override what Angular 2 does by default regarding this header. For this reason, the framework allows you to specify explicitly which value you want to use. Here is a sample:

let input = {
  firstName: 'first name',
  (...)
};

let headers = new Headers();
headers.append('Content-Type', 'application/json');

this.http.post('https://angular2.apispark.net/v1/contacts/', input, { headers })
     .subscribe(...);

Let’s now look at how to handle binary content in latest versions of the Angular 2 framework.

Handling binary content

As you also probably noticed, Angular 2 only supported text payloads. It was an important restriction and some awful hacks were necessary to achieve that. This gap has been progressively filled in RC2 and finally in RC4 versions.

Add binary support in our Web API

Our Web API only supports, at the moment, structured store, i.e. to handle entities like contact or company. Restlet Cloud supports files but we need to create a file store and import it within our Web API. This can be done quickly and easily. For more details, you can refer to the file store page in the user guide.

We simply add a folder “files” in this file store to be able to handle a set of files in the store.

angular2-filestore

angular2-filestore-import

We have now everything we need to use the binary support of Angular 2.

Sending content as binary

As described previously, it’s now possible to provide inputs other than string as parameters of methods of the Http class. For binary inputs, Blob and ArrayBuffer are supported as input.

We will first simulate binary content and will later describe how to use files as this level in the section “Uploading files”.

Imagine that we want to send CSS content as a blob, we can instantiate the Blob object and provide its content as constructor parameter:

let input = new Blob(['body { color: red; }']);
this.http.post('https://angular2.apispark.net/v1/files/somecssfile.css', input)
     .subscribe(...);

Angular 2 does the following for you here:

  • Detects that the input is a binary object.
  • Automatically adds the Content-Type header with the value application/octet-stream.
  • Provides the blob as input

A variant can be used to specify a dedicated content type that Angular 2 will use within the request. Two different approaches can be used. The first one consists of specifying a type when instantiating the Blob object. The other uses the Headers class of the framework as shown previously.

let input = new Blob(['body { color: red; }'], {type: 'text/css'});
this.http.post('https://angular2.apispark.net/v1/files/somecssfile.css', input)
     .subscribe(...);

// or

let headers = new Headers({'Content-Type': 'text/css'});
let input = new Blob(['body { color: red; }']);
this.http.post('https://angular2.apispark.net/v1/files/somecssfile.css', input, { headers })
     .subscribe(...);

In both cases, Angular 2 will add the Content-Type header with the value text/css.

Something similar can be done with the ArrayBuffer object. Here is an example:

let input = new ArrayBuffer(512);
(...)
this.http.post('https://angular2.apispark.net/v1/files/somecssfile.css', input, { headers })
     .subscribe(...);

// or

let headers = new Headers({'Content-Type': 'text/css'});
let input = new ArrayBuffer(512);
(...)
this.http.post('https://angular2.apispark.net/v1/files/somecssfile.css', input, { headers })
     .subscribe(...);

Receiving binary content

If you already use a JavaScript or library that supports AJAX like Angular 1 or jQuery, you probably know that the request must be configured to be able to receive binary content. Things are the same with Angular 2 and we need to specify which kind of buffer we want to use to store the response.

First, upload an image file into the file store using its file browser:

filestore-upload-image

Regarding binary content, we can specify Blob or ArrayBuffer value within the request options.

this.http.get(‘https://angular2.apispark.net/v1/files/angularjs.png‘,
         { responseType: ResponseContentType.Blob })
     .subscribe(...);

The second step consists of getting the response payload as a Blob object leveraging the blob method of the response.

this.http.get(‘https://angular2.apispark.net/v1/files/angularjs.png‘,
         { responseType: ResponseContentType.Blob })
     .map(res => res.blob())
     .subscribe(blobContent => {
       (...)
     });

Now we get the image content, we can, for example, preview it in the template using a dedicated image element.

@Component({
  (...)
  template: `
    Preview: <img #preview/>
  `
})
export class SomeComponent {
  @ViewChild(‘preview') preview;

  constructor(private http:Http, private renderer:Renderer) {
  }

  ngAfterViewInit() {
    this.http.get('https://angular2.apispark.net/v1/files/angularjs.png',
         { responseType: ResponseContentType.Blob })
      .map(res => res.blob())
      .subscribe(blobContent => {
        let urlCreator = window.URL;
        let url = urlCreator.createObjectURL(blobContent);
        this.renderer.setElementProperty(this.preview.nativeElement, 'src', url);
     });
  }
}

We can notice that the window.URL isn’t present on all browsers, specially in IE9. The approach consists of polyfill it using libraries like url-polyfill.

We can notice that similar things can be done for ArrayBuffer objects. The following snippet describes a sample.

this.http.get(‘https://angular2.apispark.net/v1/files/angularjs.png‘,
          { responseType: ResponseContentType.ArrayBuffer })
     .map(res => res.arrayBuffer())
     .subscribe(arrayBufferContent => {
       (...)
     });

Another interesting content is the form one. Before tackling file upload, we will have a look at the way to send data with the url-encoded form structure.

Sending data as forms

Like for JSON payloads, you need to serialize your objects following form structure without forgetting to encode data. This case is done by hand or by leveraging the URLSeachParams class of Angular 2. For example, if you wanted to send a form payload within a POST method, you would write the following:

let input = {
  firstName: 'first name',
  (...)
};
let body = new URLSearchParams();
body.set(‘firstName', input.firstName);

let headers = new Headers();
headers.append('Content-Type', ‘application/x-www-form-urlencoded');

this.http.post('http://....', body.toString(), { headers })
     .subscribe(...);

This can be refactored as described below from the RC2 version:

let input = {
  firstName: 'first name',
  (...)
};
let body = new URLSearchParams();
body.set(‘firstName', input.firstName);

this.http.post('http://....', body, { headers })
     .subscribe(...);

Under the hood, Angular 2 does the following for you:

  • Detects that the input is an object of type URLSearchParams.
  • Automatically adds the Content-Type header with the value application/x-www-form-urlencoded.
  • Converts the input object to a string using its toString method and use internally as input.

The remaining feature consists of how to use files that the user wants to send to the server based on file inputs.

Uploading files

We described in the section “Sending content as binary” how to use Blob and ArrayBuffer objects with HTTP support. Whereas we showed how to use them, the samples we used weren’t entirely representative since we used text contents. In this section, we will describe their use with files in the browser.

Sending a single file

It’s possible to simply send a single file with the HTTP support of Angular 2. The first step consists of defining a file input and reference it within your component using the ViewChild decorator. We can then add a handler to trigger the uploading.

@Component({
  (...)
  template: `
    <div>
      <input #fileInput type="file"/>
      <div (click)="submitFile()">Submit</div>
    </div>
})
export class SomeComponent {
  @ViewChild('fileInput') fileInput;

  submitFile() {
  }
}

The next step is to reference the content to send. This can be done leveraging the files property of the input. It contains a list of blob objects corresponding to the selected files. In our case, this list only contains one element. We can directly provide this object as an input to the put method of the Http class.

The following snippet describes how to upload a selected file from a file input element.

submitFile() {
  let fi = this.fileInput.nativeElement;
  if (fi.files && fi.files[0]) {
    let fileToUpload = fi.files[0];
    this.http.put(`https://angular2.apispark.net/v1/files/${fileToUpload.name}`,
          fileToUpload).subscribe(res => {
            console.log(res);
          });
}

Using multipart form

The support goes a bit further since it also allows you to use FormData objects as parameters of methods of the Http class. The great advantage of doing that is that it supports out of the box multipart forms. For example, we can mix form parameters, binary contents, and files.

Here is a basic sample mixing hints regarding a contact and some text content specified as a Blob object:

var input = new FormData();
input.append(‘firstName', input.firstName);
input.append(‘lastName', input.lastName);
var blob = new Blob(['some content'], {type: 'text/plain'});
input.append('userfile', blob);

this.http.post('http://....', input)
     .subscribe(...);

This approach can be useful if we want to send several files with the same request. Imagine we have several inputs of file types. In this case, we can easily refactor our previous submitFile method that way:

submitFiles() {
  let fi1 = this.fileInput1.nativeElement;
  let fi2 = this.fileInput2.nativeElement;
  if (fi1.files && fi1.files[0] && fi2.files && fi2.files[0]) {
    let fileToUpload1 = fi1.files[0];
    let fileToUpload2 = fi2.files[0];

    let input = new FormData();
    input.append('file1', fileToUpload1);
    input.append('file2', fileToUpload2);

    this.http.post(`https://angular2.apispark.net/v1/files/`,
          input).subscribe(res => {
            console.log(res);
          });
}

Under the hood, a request with the multipart/form-data value for the Content-Type header will be sent. We must acknowledge that the server needs to support multipart format to be able to handle such a feature.

Upload and download progress

At the moment, HTTP support doesn’t support the ability to follow the progress of both downloading and uploading.

First, create a dedicated service containing a set of subjects to notify subscribers of progress events.

@Injectable()
export class ProgressService {
  downloadProgress: Subject<any> = new Subject();
  uploadProgress: Subject<any> = new Subject();
}

We must now extend the BrowserXhr class of Angular 2 to handle progress events on the underlying XHR object, the object responsible for the transport of requests.

@Injectable()
export class CustomBrowserXhr extends BrowserXhr {
  constructor(private service:ProgressService) {}
  build(): any {
    let xhr = super.build();
    xhr.onprogress = (event) => {
      service.downloadProgress.next(event);
    };

    xhr.upload.onprogress = (event) => {
      service.uploadProgress.next(event);
    };
    return <any>(xhr);
  }
}

Don’t forget to specify both ProgressService and CustomBrowserXhr classes as providers where you want to use it.

Conclusion

As described throughout this article, we described that the HTTP support of Angular 2 has been slightly improved in the latest versions to fill gaps. It now provides a better scope and an API easier to use for end developers. Most of the use cases are now supported.

The support of binary contents was also added for both downloading and uploading. Another recently supported feature consists of the Cross Site Request Forgery (XSRF) protection for the application using a cookie. It will be the subject of its own dedicated article.

  • Hi ,

    I’ve tried to implement your solution but in my CustomBrowserXhr, service is always undefined.
    It doesn’t go to the constructor but in the build method.

    Do you have an idea why?

    Kind regards,

    Benjamin

    • Roberto

      I am also facing the same problem. Any solution?

  • cgatian

    Since ProgressService is a singleton, wouldn’t it be shared for all connections using CustomBrowserXhr?

    I think using a provider factory would fix this issue.

  • Archana

    Very Nice Article, i so much appreciate your content…a very clear picture of http.

  • aristide koffi gbede

    hello thank you for example but on your demo i do not see method get with login and password by angular2.
    if you can help me it will good for me .
    thank