10 minutes read
In this article we’ll learn about Angular2 Services. Our example App will contain a simple Logon-Form connected to a background Authentication-Service. This Auth-Service utilizes some of the default Angular2 mechanisms like Http, Rx/Observables etc. to provide us the needed infrastructure.
Here’s a working demo for live testing.
As always, the code is located on GitHub.
But before we begin let’s provide a proper definition of a Service in Angular 2.
What is a Service?
A Service is a class annotated with @Injectable that contains some logic which can be used by different parties in the application. An Injectable doesn’t come into existence by using `new` but gets instantiated by Angular’s Dependency Injection mechanism. To get access to a service a client has to declare its dependency on it by providing the service’s ClassName in its providers Array and insert a constructor parameter of the same class. By default Services are Singletons and therefore a client doesn’t have to provide an explicit providers-declaration if it’s parent component has already done that. This leads to the important fact that services declared in Angular’s bootstrap function are globally available throughout the application. However, if, for some reason, we want a separate Singleton for a component sitting deeper in the hierarchy a new providers-declaration would instruct Angular to create additional Singleton of the same service type. In our case only a single LogonService should be available and therefore we use the standard bootstrap function from Angular to set everything up.
We import our main component App and the LogonService. To make the bootstrapping easily expandable we create a new array for our future services APP_SERVICES which currently contains only the LogonService. Finally, we put APP_SERVICES together with other defaults from Angular’s machinery and boot the App. Our LogonService comprises of a single class with some important extensions:
We see the new annotation @Injectable which is originally from Dart (the language Google once wanted to replace JS but failed miserably). Also, the constructor of this class expects yet another Service Http, provided by Angular. In our above configuration we’ve imported several providers from the core infrastructure of Angular. One of them is the Http service. Therefore, a service can easily import other services too. Our LogonService offers a single method doLogon that expects an argument of type ILogonData, which is a simple interface describing account data (name, password). The return value is particularly interesting: it’s an Observable that emits a single IUser instance. Of course, to explain Observables we’d need not one but several (long) articles, so I’ll try to describe only a few significant parts.
An Observable can be thought of as a collection of things whose structure is based on time (and not on space as with classical collections, for example arrays). When you control a “standard” array you oversee a structure that’s defined in space, from the first to the last element. All of them exist at the same time and are ordered one by one. Such arrays can be nicely packaged in memory and have proper addresses, for all of their elements. To get a certain element out an array one has to go over all the preceding elements until the element of interest is reached. Not so with “arrays in time”, Observables. They usually never contain all of “their” elements at the same time nor there’s any mechanism which’ll keep them in order. An Observable is a collection that spans through time and just a moment later a seemingly empty Observable could contain several new elements. This is the most important difference: standard arrays (or better: collections) are pull based while Observables are push based. The standard collections contain elements that we pull out while Observables contain elements “something else” pushes in for us. This “something else” can be anything: a Component, a Service, another Observable, or even a Server that accepts our logon-queries and returns user data.
Using a JSON service to log on
In our case we deal with a freely available (fake) JSON-service that accepts our POST requests and returns a Response which is then forwarded to us via HttpService as an Observable<Response>. We take this data and extract the contained JSON object that ultimately gets mapped to a typesafe IUser instance. This is another powerful aspect of Observables as it allows transformation of data on the fly. The outputs of one Observable can easily become inputs of another one. The use-case is rather simple but it also covers some important aspects regarding services and how we should deal with data coming from “unreliable sources”. First, we should never instantiate services directly as Angular provides us a powerful Dependency Injection framework. Second, we should develop with code-reuse in mind and therefore declare our services as generally available without hard coding (so, the URL in the example should not be there when developing a real service). Third, always prefer Observables as they naturally support push-based services and asynchronous processing of data. Now, our service is ready to run and, as we know, was made globally available via the bootstrap function. The next step is to wire it up in the logon.component‘s constructor:
We expect Angulars’ DI to provide us a proper instance of LogonService. Also, we want our UI to behave like a “real” Logon-UI so we additionally take a FormBuilder and some nice validators to check the user-entered data. Without proper data the clicking on the Logon-button will remain disabled. However, in this article we’re not going any deeper into Forms and Form-Validations as this represents another (important) aspect of WebApps. Also, take into account that we’re using here an old forms module that’ll be eventually removed in later versions (in one of the upcoming RC’s, I suppose). Here’s the warning from the console.
More detailed info on forms and their future developments can be found here.
Logon with Observables
As our LogonService communicates via Observables we have to provide a proper Observer so it can respond with meaningful content.
Imagine our Observer as a device containing three small screens: data, error and completed. If an Observable wants to deliver us data we’re interested in it’d use the first screen. If something weird happened it’ll use the error-screen to warn us. And if there will be no data anymore the completed-screen will be used to announce the ending of Observable’s “lifetime”. We can omit the error and completed screens but we have to provide the data-screen as the Observable will never be able to inform us about new data (our Observable would simply have no PUSH-channel available).
I suppose that most of us know how to program a callback function. Dealing with Observables slightly resembles function callbacks but with the difference that one has to hand over a complete mechanism (the device with three screens from above, for example) so it can later be utilized by the Observable itself. Unlike with callbacks we’re not providing a function reference but a little engine whose buttons will be pushed according to the current situation inside the observable stream. Also, keep the term Observable separated from the Observer. Although the term “Observer” could semantically imply a subject the Observers we use always start as objects whose mechanics are accessible by some Observable that decides if it’s time to send new data, an error, or end the transmission. Of course, inside the Observers we can develop many transformations and other logic that’ll deal with received data but nevertheless: an Observer has to be handed over to the Observable and not the other way around. In my opinion this is the hardest part in grasping the concept of Observables (at least, it was for me once). Not to mention Subjects that are Observers and Observables in one. But, I think we’ve talked long enough about Observables! There are many very good tutorials on this subject (no pun intended) so I’m pretty sure you’ll find your preferred way to learn more about it. And one last thing regarding Observables: they’re lazy. This means that an Observable would’t run until it gets a subscription. That’s the reason why calling of doLogon(data) doesn’t kick off the logon procedure. Instead, it returns an Observable instance we have to subscribe to.
Consuming Observable Streams
The last step in our journey from sending the account data to the actual log-on is the subscription to the observable stream. In the above logon-method we din’t call the doLogon method itself. Instead we’ve declared ourselves as being interested in consuming the (probably) available stream of typed Observable<IUser> instances. We’ve indicated our interest by calling the subscribe operator. This operator expect us to provide our little logon-engine comprising of two functions: data and error. As already talked about, we’re free to omit functions we don’t want to use. In this case we don’t expect our LogonService to disappear anytime soon so it makes no sense to implement a completed-function. Now, the harder part is actually to recognize the LogonService as an engine that’s processing a never-ending stream of logon attempts. Let’s say, we’re offering such a service to a large user base with thousands of accounts. Each and every account would see only itself and be completely oblivious of other logons that eventually appear at the same time. However, on the server side all of these logons would be recognized as a single stream of logon attempts. Therefore, the Observable approach to working with such kind of data is more natural. That’s why we have to subscribe to it because our single logon-attempt is just one of many similar attempts in this stream. It’s a collection of logon-attempts spread throughout time. We cannot expect the doLogon method to immediately return a response. At some point in time there will be a response for us available, but only indirectly by using our little “device” provided at the subscription.
My initial attempt was to show how “an Angular 2 Service” works but I’ve quickly realized that services alone are not that much. There are many more or less similar architectures implemented via countless technologies and frameworks. The point of services in Angular 2 aren’t services themselves. It’s the combination of services, dependency injection and Observables that makes Angular’s services a very powerful and versatile engines. I’m not sure if my theoretical explanations about Observables were that helpful, because it’s very easy to write misleading articles about concepts of this kind, but at least I hope that’ll raise some deeper interest in them.
Have fun! 🙂