#12 We Need to Talk to the Server
Friday, December 22, 2017
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.
Parts
- Part 30: User Database
- Part 29: Canonical Url
- Part 28: Razor Page Handlers
- Part 27: Send Screen Finished
- Part 26: Stroke Dashoffset
- Part 25: Send Screen
- Part 24: Forgot Link Behaviour
- Part 23: Animating Controls (cont.)
- Part 22: Animating Controls
- Part 21: Hiding Control Groups
- Part 20: ModelState Errors (cont.)
- Part 19: ModelState Errors
- Part 18: Animating Info Pages (cont.)
- Part 17: Animating Info Pages
- Part 16: Keyboard Navigation
- Part 15: Accessing the DOM
- Part 14: All About the Username
- Part 13: CSRF Attacks
- Part 12: Http Requests
- Part 11: HMR
- Part 10: Color Inputs And Buttons
- Part 9: Animating Sub-Actions
- Part 8: Form Validation (cont.)
- Part 7: Form Validation
- Part 6: Form Group
- Part 5: Authenticator Validators
- Part 4: Authenticator Inputs
- Part 3: First Angular Component
- Part 2: Webpack
- Part 1: Beginning
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
-
authenticator
-
services
- http.service.ts
-
components
-
Source
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) {
}
}
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);
}
}
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, ...
) {
...
}
}
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.
Inject angular's http service
-
WebUi
-
Source
-
components
-
authenticator
- authenticator-http.service.ts
-
authenticator
-
components
-
Source
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 {
...
}
Of course when one problem is fixed another one takes its place. In the next section we will take care of this new problem.
Can't inject it if we don't have a provider
-
WebUi
-
Source
-
app
- account-authenticator.site.ts
-
app
-
Source
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 { }
...
Let's trim rxjs down
-
WebUi
-
Source
-
app
- vendor.ts
-
app
-
Source
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";
...
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.
Need a method to talk to the outside world
-
WebUi
-
Source
-
components
-
authenticator
- authenticator-http.service.ts
-
authenticator
-
services
- http.service.ts
-
components
-
Source
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);
});
}
}
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);
}
}
Hello is anybody out there?
-
WebUi
-
Source
-
components
-
authenticator
- authenticator.component.pug
- authenticator.component.ts
-
authenticator
-
components
-
Source
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}')`)
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());
});
}
...
}
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.
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.
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
-
Controllers
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!"
});
}
}
}
With this addition when we make the same request to the server we see that the status code is now 200 (p).
And we do in fact have the response from the server logged to the console as expected.