Advertisement

#12 We Need to Talk to the Server

In this article we will create the ability for our component to communicate with the server. In order to do this we will first create a base http service class that all services that need to communicate with the server will inherit from. Once this is done we will create a http service specific for our component that inherits from the base service. Along the way we will hopefully reduce the size of our vendor bundle a small amount by changing the way we import rxjs.

You can view a completed example of the authenticator component here.

Inject a custom http service

  • WebUi
    • Source
      • components
        • authenticator
          • authenticator.component.ts
          • authenticator-http.service.ts
      • services
        • http.service.ts

We will begin by creating a custom http service (a) that will act as a wrapper around angular's http that we will call HttpService and place in a file with the name 'http.service.ts' within our services folder. For the moment this class will be empty as we set up the dependency injection. As you may have noticed in (a) our http service is an abstract class which means we need to create a class that will inherit from it. We do this by creating a file called 'authenticator-http.service.ts' (b) in our authenticator folder. Just like our base http class this class is mostly empty except for containing references to the relevant urls and a constructor that will pass in the angular http service to our base class. Finally we need to modify our component file so that we can have angular inject our authenticator http service into its constructor as shown in (c).

http.service.ts (1)

import { Http } from "@angular/http";

export abstract class HttpService {
    protected constructor(protected readonly http: Http) {
        
    }
}
(a) Starting point for our http service. All services that need to talk to a server will derive from this base class.

authenticator-http.service.ts (1)

import { Http } from "@angular/http";

import { HttpService } from "../../services/http.service";

export class AuthenticatorHttpService extends HttpService {
    private static readonly checkUsernameUrl = "/account/check-username-availability";
    private static readonly forgotPasswordUrl = "/account/forgot-password";
    private static readonly forgotUsernameUrl = "/account/forgot-username";
    private static readonly loginUrl = "/account/login";
    private static readonly registerUrl = "/account/register";            

    public constructor(http: Http) {
        super(http);
    }
}
(b) Starting point for the service that we will use to talk to our server from the authenticator component.

authenticator.component.ts (1)

...
import { AuthenticatorHttpService } from "./authenticator-http.service";
...
@Component({
    ...
    providers: [AuthenticatorHttpService, DOMReaderService, FormBuilder, UriParserService]
})
export class AuthenticatorComponent implements OnInit {
    ...
    constructor(
        private readonly _authenticatorHttpService: AuthenticatorHttpService, ...
    ) {
        ...
    }
}
(c) Modifying of our component file so that angular will inject an instance of the authenticator http service.

Once we save our file webpack will inject the new bundle into the browser and we will be greeted with an error message (d). We are receiving this error because we have not correctly setup dependency injection as it relates to our service. In the next section we will take care of this error.

Angular error message telling us that it has not been able to
            resolve all of the constructor parameters for our service.
(d) Angular error message telling us that it has not been able to resolve all of the constructor parameters for our service.

Inject angular's http service

  • WebUi
    • Source
      • components
        • authenticator
          • authenticator-http.service.ts

The reason that we are receiving the error shown in (d) is because angular needs additional metadata information in order to inject parameters and we are not providing it. In the case of our component the information is being passed in when the @Component metadata tag is processed by the typescript compiler. We will basically use the same approach to solve our problem with another metadata tag. In this case we will use the @Injectable tag (e) contained in the angular core package.

authenticator-http.service.ts (2)

import { Injectable } from "@angular/core";
...
@Injectable()
export class AuthenticatorHttpService extends HttpService {
    ...
}
(e) Using the @Injectable tag will provide angular the information that is required for the http service to be injected into the constructor.

Of course when one problem is fixed another one takes its place. In the next section we will take care of this new problem.

Error telling us that angular doesn't know what the Http service is.
(f) Error telling us that angular doesn't know what the Http service is.

Can't inject it if we don't have a provider

  • WebUi
    • Source
      • app
        • account-authenticator.site.ts

The problem that we are having now is that angular knows that it needs to inject the http service but it is unaware of what a http service is. To fix this problem we must return to our module definition and import the HttpModule (g) and include it within the imports array. With this done we have fixed all of our current errors. Never fear we will have another one to fix shortly.

account-authenticator.site.ts

...
import { HttpModule } from "@angular/http";
...
@NgModule({
    imports: [
        ...
        HttpModule,
        ...
    ],
    ...
})
export class AuthenticatorModule { }
...
(g) By including the Http service in our imports array angular will know what it is in order to inject it.

Let's trim rxjs down

  • WebUi
    • Source
      • app
        • vendor.ts

Back in the first or second article we talked about ways to reduce the size of our webpack bundles and we even implement a reduction scheme in regards to lodash. At the same time I mentioned that we should be able to reduce the size of importing rxjs but that we would wait to do it until we had more code in place in order to test what we decided to do. The reason that I am mentioning this is that the time has come for us to do some trimming or rxjs. For now the only two components that we need from the package is shown in (h). In the next section we will see why we need these two.

vendor.ts

...
import "rxjs/Observable";
import "rxjs/add/operator/toPromise";
...
(h) Importing the two components of rxjs that we need right now instead of the whole package.

Once these changes are made and the file is saved if we return to the browser we will see that the updated bundle has not been accepted. This is of course because we did not make any changes to the vendor.ts file to enable hot module replacement. You could add the required code to the file if you like but since we do not make changes to this file all that much I will just opt to refresh the browser myself in this case.

Advertisement

Need a method to talk to the outside world

  • WebUi
    • Source
      • components
        • authenticator
          • authenticator-http.service.ts
      • services
        • http.service.ts

Of course our http service and by extension our authenticator http service are useless since they do not provide us a means for talking to our server. We will begin to correct this by first defining a get method on our base http service (i). This, of course, as the name suggests will be the base method for us to perform a get request. For now we do not need the ability to maintain a connection with the server so we will convert our observable to a promise as shown in the doPromise method. This method makes use of the rxjs toPromise operator which is why we needed to include it in our vendor.ts file allowing with Observable. Next we need to create a checkUsernameAvailability method on our authenticator service. This method is basically just a convenience method for providing the correct url to the get method we just created. With these pieces in place we are ready to actually send a request to the server which we will do in the next section.

http.service.ts (2)

import { ..., Response } from "@angular/http";
import { Observable } from "rxjs/Observable";

export abstract class HttpService {
    ...
    protected get = (url: string, success: (response: Response) => void, error: (response: Response) => void) => {
        this.doPromise(this.http.get(url), success, error);
    }

    private doPromise = (observable: Observable<Response>, success: (response: Response) => void, error: (response: Response) => void) => {
        observable
            .toPromise()
            .then((response: Response) => {
                success(response);
            })
            .catch((response: Response) => {
                error(response);
            });
    }
}
(i) Implementing the first method for our http service in the form of a get method.

authenticator-http.service.ts (3)

import { ..., Response } from "@angular/http";
...
export class AuthenticatorHttpService extends HttpService {
    ...
    public checkUsernameAvailability = (username: string, success: (response: Response) => void, error: (response: Response) => void) => {
        this.get(AuthenticatorHttpService.checkUsernameUrl, success, error);
    }
}
(j) We can use the get method that we just created to implement a method for checking the availability of a username.

Hello is anybody out there?

  • WebUi
    • Source
      • components
        • authenticator
          • authenticator.component.pug
          • authenticator.component.ts

Now that we have the ability to communicate with the server it's time to actually do it. As far as checking for the availability of a username we are going to want to do that when a user has entered the username that they want and has shifted the focus from the username input. We do this by binding a method to the blur event of our inputs (k). For now in order to test the communication we will perform the http request if the input that is blurred is the username input (l). In the event handler we will just log the server response to the console.

authenticator.component.pug

mixin formControlGroup(label, inputType, key)
    div.form-control-group
        label #{label}
        div.input-group(...)
            input(..., (blur)=`onBlurInput('${key}')`)
(k) Binding of the event handler onBlurInput to the blur event on our inputs.

authenticator.component.ts (2)

...
import { Response } from "@angular/http";
...
export class AuthenticatorComponent implements OnInit {
    ...
    public onBlurInput = (key: string) => {
        if (key === this.controlUsernameKey) {
            this.checkUsernameAvailability();
        }
    }
    ...
    private checkUsernameAvailability = () => {
        this._authenticatorHttpService.checkUsernameAvailability(this.controlUsername.value,
            (response: Response) => {
                console.log(response.json());
            },
            (response: Response) => {
                console.error(response.json());
            });
    }
    ...
}
(l) Adding of the event handler and helper function to our component to handle the http request to the server.

By focusing the username input and then removing the focus we should see the request to the server in the network tab of our browser's developer tools. From (m) we see the request has been made and just as importantly it has been made to the correct url. The response is status code 404 which makes sense since we have not yet added the end point to our application.

Image showing that the request was made to the server and was made to the
            correct url.
(m) Image showing that the request was made to the server and was made to the correct url.

I'm only including the error shown in (n) at this time just to remind us that we need to be careful when attempting to convert the response body to json.

Error that resulted from attempting to convert the response body to
            a json object when the server did not return a response object.
(n) Error that resulted from attempting to convert the response body to a json object when the server did not return a response object.

In the next section we will setup the end point on our application to respond to this request.

A conversation takes two

  • WebUi
    • Controllers
      • AccountController.cs

After we create a file called 'AccountController.cs' in our newly created 'Controllers' folder we can add the code in (o) to it. The effect of this code is to create an end point with the url of /account/check-username-availability and to simply return a simple json object.

AccountController.cs

using Microsoft.AspNetCore.Mvc;

namespace WebUi.Controllers
{
    [Route("account")]
    public class AccountController : Controller
    {
        [HttpGet]
        [Route("check-username-availability")]
        public IActionResult CheckUsernameAvailability()
        {
            return Ok(new
            {
                Message = "Hello World!"
            });
        }
    }
}
(o) Creation of the check username availability end point on our application.

With this addition when we make the same request to the server we see that the status code is now 200 (p).

Image showing that we are able to successfully communicate with the server now.
(p) Image showing that we are able to successfully communicate with the server now.

And we do in fact have the response from the server logged to the console as expected.

Image showing that we are able to receive the response from the server and covert it to json.
(q) Image showing that we are able to receive the response from the server and covert it to json.
Exciton Interactive LLC
Advertisement