In our last guide, we covered how to install everything we need to get started with upgrading from to Angular. We also covered the basics of rewriting and downgrading components.

Now, let’s learn how to work with services in an project. You’ll learn how to:

  • Rewrite an AngularJS to Angular
  • Convert an observable to a promise
  • Downgrade the service so it still works with our AngularJS code
  • Convert a promise to an observable

Our Starting Point

Take a minute to clone or fork this sample project on GitHub (don’t forget to run npm install in both the public and server folders). Checkout this commit to see our starting point:

git checkout 083ee533d44c05db003413186fbef41f76466976

We’ve got a simple Order System project that we can use to work through ngUpgrade. It’s using component architecture, TypeScript, and Webpack (with builds for both development and production). We’ve also got Angular and ngUpgrade set up and bootstrapped, and the component has been rewritten to Angular.

(If you’re lost on any of that, we cover it all in the comprehensive video course Upgrading AngularJS.)



One quick note: Things change quickly in Angular and RxJS. If you’re using Angular 4.3+ or 5+, you’ll see a couple slight discrepancies here compared to the sample project. The sample project uses Http in services for HTTP calls like GET and POST. We’re going to use the new, much simpler HttpClient that was added as of version 4.3+. For the purposes of this tutorial, the functionality is basically the same, so just follow along here. RxJS also made some changes as of version 5.5 in the way things are imported, so we’ll use that new style here.

Let’s Rewrite an AngularJS Service

When doing an ngUpgrade, it’s smart to pick one route at a time and work from the bottom up. This is because it’s very easy to make Angular code available to AngularJS, and we can take advantage of keeping Angular and AngularJS running side by side without worrying about breaking the app.

Since we did the home route in the last guide, we’re now ready to start on the customers route. We’ll start by rewriting the CustomerService and downgrading it to make it available to our AngularJS components. Then, we’ll take a look at using both observables and promises in the service, so that you can choose for yourself which will work best for you in your migration process.

Add HttpClient to NgModule

Before we rewrite the CustomerService, we have to explicitly import Angular’s HttpClientModule into our NgModule for the app (app.module.ts) in order to make HTTP calls. This is different than in Angular JS, where everything was included by default. In Angular, we need to be explicit about which parts of Angular we want to use. While it may seem inconvient at first, this is great because it helps reduce the footprint of our application by not automatically importing unused code.

So after line 3, we’ll import it like this:

import { HttpClientModule } from '@angular/common/http';

Then, we need to add that module to our imports array after the UpgradeModule on line 12:

//app.module.ts
@NgModule({
    imports: [
        BrowserModule,
        UpgradeModule,
        HttpClientModule
    ],
    declarations: [
        HomeComponent
    ],
    entryComponents: [
        HomeComponent
    ]
})

Now we’re able to use the HttpClientModule throughout our application. We only need to import it once and we can use it for all the rest of our services throughout the application.

Rewrite the Customer Service

Now that we’ve got HttpClientModule added to our Angular app module, we’re ready to rewrite the CustomerService in Angular. We’ll then downgrade it so that we can still use it in our Angular JS components as well as our Angular components.

The first thing we’ll do is rename the customerService.ts file tocustomer.service.ts so that it follows the current naming conventions.

Now, let’s open the file. You’ll see that we’re using an ES2015 class already:

//customer.service.ts
class CustomerService{
    $http: any;
    constructor($http) {
        this.$http = $http;
    }

    getCustomers(){
        return this.$http.get('/api/customers')
            .then((response) => response.);
    }

    getCustomer(id){
        return this.$http.get(`/api/customers/${id}`)
            .then((response) => response.);
    }

    postCustomer(customer){
        return this.$http.post('/api/customers', customer)
            .then(() => );
    }
}

CustomerService.$inject = ['$http'];
export default CustomerService;

Angular 2+ services are equally straightforward. They’re also just classes that we export, but we add the Injectable() annotation. Gone are the days of trying to remember factories, services, providers, and how to create each one. In Angular, a service is a service, and it’s just an exported class with the injectable annotation. Isn’t that a huge relief?

Prepare the Code

The first thing we can do is delete the last two lines in this file. We no longer need the AngularJS $inject array, and instead of using export default, we’re going to add the export keyword before the class declaration:

export CustomerService { //etc.

Now I’m ready to import two things from Angular up at the top of the file. The first is the Injectable() annotation that I mentioned:

import { Injectable } from '@angular/core';

Next we need the HttpClient:

import { HttpClient } from '@angular/common/http';

Great. Now we’re ready to make this an Angular service!

Update the Service Class to Angular

First, let’s add the Injectable() annotation to our CustomerService, just above the class:

@Injectable()

There’s no options object that gets passed into this annotation.

The next thing we need to do is replace all of our references to AngularJS’s $http service with Angular’s HttpClient. We’re going to use the shorthand http for this instead, so the easiest thing to do is a find and replace in this document of $http to http, given that most of the calls will actually largely be the same:

Now we need to change one thing about how our http property is created. Instead of this:

//customer.service.ts
class CustomerService{
    http: any;
    constructor(http) {
        this.http = http;
    }

0;we’re going to delete line six that declares a public property of http of type any. Instead, in our constructor, let’s add the private keyword before http and specify that it’s of type HttpClient:

//customer.service.ts
export class CustomerService{
    constructor(private http: HttpClient) {  }

With Angular’s dependency injection, we’re instantiating a private instance of the HttpClient service on our CustomerService.You can also see that, with the private keyword, we don’t need to set our class instance of http equal to our injected instance (it does this behind the scenes for us).

What we have now is the bare bones of an Angular service, but you’ll now see those red squiggly lines underneath our everywhere we use .then. You can see that the IntelliSense is telling us that property then does not exist on type observable of response:

What’s going on there? Let’s tackle that next.

Promises or Observables?

We’ve got our customer service largely rewritten to be an Angular service, but we’ve got a little bit of a problem with trying to use .then on these http calls. That’s because the HttpClient in Angular returns an observable instead of a promise. We’ve got two choices here:

  1. The practical way: convert these responses to promises and the rest of our application will work the same, or
  2. The fun way: keep these responses as observables and update our components.

With any large scale refactor or upgrade, the goal is always to lose as little up time in your application as possible. My recommended approach is to first convert the calls to promises. That way, you can determine what components and other parts of the application are dependent on the service and its calls. After you’ve done that, you can convert the calls one at a time to observables, and update each component accordingly. So, first, get a service over to Angular and get it working. Then worry about using observables when you feel the time is right. Or, as a friend of mine says, "First migrate, then get fancy."

So let’s first learn how to convert the calls to promises. Don’t worry though – in a bit we’ll do the fun thing and convert a call to an observable.

The toPromise Operator

To convert observables to promises, we first need to import from RxJS, the library that handles observables. After our Angular imports, we just need to add:

import { Observable } from 'rxjs/Observable';

This lets us use various functions for the observable object provided by RxJS.

The toPromise method lets us convert observables to promises. It used to be a separate import in previous versions of RxJS, but has now been rolled into Observable. Importing individual operators is a common pattern in RxJS, but figuring out which operators you need and where they reside in the library can be a little daunting. Be sure to go through the documentation resources that RxJS provides, as well as the Angular documentation on RxJS.

Now we can use the toPromise operator before each .then in our calls. When you do that, you’ll also see an error that says that .data is not a property that exists on the type "object". That’s because the response already returns the data object inside of the HTTP response. All we need to do then is remove the .data. This is different than in the days of the original Http service, where we needed to call a .json function to return the data.

One more thing. Since we have the benefits of TypeScript, let’s add the return type to each of these functions. It’s always best in TypeScript to specify types when possible, even though, technically, it’s not required. So, after each function name, we’ll add :Promise<any>.

The finished functions in the service will look like this:

//customer.service.ts
getCustomers():Promise<any> {
   return this.http.get('/api/customers')
       .toPromise()
        .then(response => response);
}

getCustomer(id):Promise<any> {
    return this.http.get(`/api/customers/${id}`)
        .toPromise()
        .then(response => response);
}

postCustomer(customer):Promise<any> {
    return this.http.post('/api/customers', customer)
       .toPromise()
       .then(data => data);
}

Awesome, we’ve successfully converted the observales in our calls to promises!

Downgrade Customer Service

Now that we’ve converted our observables to promises, we’re ready to downgrade the customer service so that the not-yet-migrated AngularJS components can still use it.

This process is very similar to when we downgraded the home component in the previous guide. The first thing we need to do is import the downgradeInjectable function from the ngUpgrade library, just like we imported downgradeComponent for the home component. So after line two, we’ll add:

import { downgradeInjectable } from '@angular/upgrade/static';

We also need to declare a variable called angular just like we did in our home component. So after line four, we’ll add:

declare var angular: angular.IAngularStatic;

Then at the bottom of our file, we’ll register our service as a downgraded factory. So, after the end of the class, we’ll type:

angular.module('app')
    .factory('customerService', downgradeInjectable(CustomerService));

And voíla! We’ve downgraded the CustomerService to be available to AngularJS. Here’s the finished service:

//customer.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { downgradeInjectable } from '@angular/upgrade/static';
declare var angular: angular.IAngularStatic;

@Injectable()
export class CustomerService {
    constructor(private http: HttpClient) {}

    getCustomers():Promise<any> {
        return this.http.get('/api/customers')
            .toPromise()
            .then(response => response);
    }

    getCustomer(id):Promise<any> {
        return this.http.get(`/api/customers/${id}`)
            .toPromise()
            .then(response => response);
    }

    postCustomer(customer):Promise<any> {
        return this.http.post('/api/customers', customer)
            .toPromise()
            .then((data) => data);
    }
}

angular.module('app')
    .factory('customerService', downgradeInjectable(CustomerService));

the Service to the Angular Module

Our customer service has been rewritten to an Angular service and downgraded to be available to AngularJS. Now we need to remove our reference in our AngularJS module and add it to our Angular module.

Remove from the AngularJS Module

First, let’s open up our AngularJS module (app.module.ajs.ts). You can remove line 22:

import CustomerService from './customers/customerService';

…as well as line 41:

.service('customerService', CustomerService)

That’s it!

Move to Angular Module

Now let’s add our service to our NgModule in app.module.ts so that our Angular code can access it. The first thing we need to do is import the service after line seven:

import { CustomerService } from './customers/customer.service';

Now to register our customer service in our application, we need to add an array of providers to our NgModule after our entryComponents array and add our CustomerService there:

//app.module.ts
providers: [
        CustomerService
    ]

The providers array is where we’ll register all of our services in the application. And now we’ve got our customer service registered in our NgModule and ready to go!

A Quick Note on AOT Compiling

This method of downgrading — registering the downgraded service in the service file and removing it from the AngularJS module file — works perfectly well for development or if you plan on quickly rewriting your application before you deploy. However, the Angular AOT compiler for production won’t work with this method. Instead, it wants all of our downgraded registrations in the AngularJS module. Here in this sample project, we don’t need to worry about that, but I’d be remiss if I didn’t mention it for use in the real world.

The downgrade is identical, but instead you’d:

  • Import downgradeInjectable in app.module.ajs.ts (you’ve already got angular in there so you don’t need to declare it).
  • Change the import of CustomerService to import { CustomerService } from './customers/customer.service'; since we switched to a named export.
  • Change the service registration to the exact same factory registration shown above.

Let’s Make Sure This Works

We’d better make sure our application is still running. Let’s start our Express API, then run our Webpack development server. Open a terminal and run these commands to start Express:

cd server
npm start

Then open another terminal and run these commands to start Webpack:

cd public
npm run dev

You should see everything compile and bundle correctly.

Now, open a browser and head over to localhost:9000. Let’s navigate to our customers route and see if the service is working:

We can double-check that we’re using the rewritten Angular service by going to the sources tab in the Chrome developer tools, navigating down to the customers folder, and clicking on the CustomerService source:

Yep, there it is – our rewritten service!

That’s pretty cool, because we’ve updated the service to Angular, but it’s being used in both the customers component and the customer table component, both of which are still in AngularJS!

GetCustomers As Observable

Now that we’ve got the CustomerService downgraded and working, let’s have some fun and use that getCustomers call as an observable. That way we can start taking advantage of all the new features of observables. This is going to be a little bit tricky, because we’re using the call in both the customers component and the orders component, neither of which have been rewritten to Angular yet. Don’t worry – I’ll show you step-by-step how to do this.

Back in the customer service code, the first thing that we need to do is change the return type on line 16 to Observable<any>. Of course now, TypeScript is complaining to us because we’re converting toPromise, so we just need to delete both the toPromise and then functions. Super easy! It looks like this now:

getCustomers():Observable<any> {
      return this.http.get('/api/customers');
}

Now we need to update our customers component to use an observable instead of a promise. We’ll do that next.

Using an Observable in the Customers Component

Our getCustomers call is now returning on observable. Let’s update our customers component (customers.ts) to use an observable instead of a promise. The customers component is still an AngularJS component and that’s fine, we don’t need to mess with it yet, but let’s use a little TypeScript to help us out. Let’s import our CustomerService at the very top of our file:

import { CustomerService } from './customer.service';

Now that we’ve imported the CustomerService, we can specify the type of our injected CustomerService in our controller function definition:

//customers.ts
function customersComponentController(customerService: CustomerService){

We now have the advantage of TypeScript complaining about our .then just like it did in our CustomerService. It knows that the getCustomers call is supposed to return an observable and that .then doesn’t exist on an observable.

The way we use an observable in a component, whether it’s an AngularJS or Angular component, is to subscribe to it. Before we can subscribe to this observable, we need to import Observable just like we did in the service. So, above our CustomerService import, we’ll add:

import { Observable } from 'rxjs/observable';

This will let us use all the basic functions on observable, including subscribe. So, now on line 18 inside of our $onInit function, we can just change the then to subscribe, and everything else can stay the same.

Let’s go look at the browser and see if this actually worked. If you head over to the customers route, you should see that everything is working the same. However, if we go over to the Orders tab, we see a big problem: no data and TypeError: Cannot read property 'fullName' of undefined in the console. What’s going on here?

It turns out the orders component also uses the getCustomers call, but it’s still trying to use it as a promise. Let’s fix that.

Fix the Orders Component

When we rewrote our getCustomers call to be an observable instead of a promise, we accidentally broke our orders component (orders/orders.ts), which is still in AngularJS. That’s because in our $onInit function, we’re using $q.all to wait for two promises to return before we assign any of the data to our view model:

vm.$onInit = function() {
        let promises = [orderService.getOrders(), customerService.getCustomers()];
        return $q.all(promises).then((data) => {
            vm.orders = data[0];
            vm.customers = data[1];
            vm.orders.forEach(function (order) {
                var customer = _.find(vm.customers, function (customer) {
                    return order.customerId === customer.id;
                });
                order.customerName = customer.fullName;
            });
        });
    };

This was a really common pattern in AngularJS.

The most straightforward solution to this problem would be to just rewrite the orders component to Angular, and also rewrite the order service. But, in the real world, that’s just not always possible right away. Remember, in any large-scale refactoring, the first priority is to make sure we minimize down time and be able to have a continuously deliverable application that we can always deploy to production.

So, despite the fact that the orders component is pretty simple, let’s imagine that this is actually a very complicated piece of our application, or a very mission-critical piece of our application, and we just don’t have time to rewrite it. In that case, we have two choices: we can either convert our getCustomers call to a promise in the orders component, or we can convert the getOrders promise to an observable.

To convert getCustomers to a promise in the component, we’d just do exactly the same thing we did earlier in the service – import Observable from RxJS and add the toPromise operator after getCustomers. It’s that easy, and it’s a super handy trick if you just can’t don’t have time to refactor this component to use observables quite yet. However, it’s not completely desirable, as our long-range goal is to completely get rid of promises and switch entirely to observables. So, Iet’s learn how to convert our getOrders call to an observable here.

Convert getCustomers to a Promise

Let’s convert the getOrders to an observable. The first thing we’re going to do is import our CustomerService at the top of the file just like we did in the customer component:

import { CustomerService } from '../customers/customer.service';

Then we can specify the type of our injected CustomerService in our controller function definition:

//orders.ts
function ordersComponentController(orderService, customerService: CustomerService, $q) {

In order to convert the getOrders call to observable, we’re going to use two static methods on observable called fromPromise and forkJoin. The fromPromise method lets us convert a promise to an observable, and forkJoin lets us subscribe to multiple observables. So, you might have guessed by now that the first thing we need to do is import those two methods at the top of our file:

import { fromPromise } from 'rxjs/observable/fromPromise';
import { forkJoin } from 'rxjs/observable/forkJoin';.

Now we can do some work in our $onInit function. Above line 21, let’s to declare a variable called ordersData and use the fromPromise method:

let ordersData = fromPromise(orderService.getOrders());

Now let re-write $q.all to use forkJoin instead. So, first we’ll just replace return $q.all with forkJoin. We need to pass in an array, so let’s move the promises array and add ordersData to the front of it and then just get rid of the promises declaration. Lastly, let’s change .then to .subscribe just as with a single observable. Here’s our finished $onInit function:

vm.$onInit = function() {
        let ordersData = fromPromise(orderService.getOrders());
        forkJoin([ordersData, customerService.getCustomers()]).subscribe((data) => {
            vm.orders = data[0];
            vm.customers = data[1];
            vm.orders.forEach(function (order) {
                var customer = _.find(vm.customers, function (customer) {
                    return order.customerId === customer.id;
                });
                order.customerName = customer.fullName;
            });
        });
    };

Let’s recap what we’ve done here. First, we called fromPromise and converted our getOrders call from a promise to an observable. Then, we used forkJoin to subscribe to both the ordersData and the getCustomers call. Just like with $q.all, the subscribe for forkJoin will return an array of our data in the order that we’ve listed them. So, data[0] will be our order, and data[1] will be our customers.

Let’s do one more thing to clean this up. We can remove the $q dependency from line 16 in our $inject array and line 167 in our function definition.

Is it working?

Let’s go look at the browser one more time and make sure this worked. You should see that our application compiles and loads correctly, so check out the orders tab:

There it is! Our data is loading correctly! This is great, because now you’ve seen how to translate back and forth between promises and observables, which is really useful when you’re working on a large application where you can’t just convert everything to observables all at once as you’re upgrading.

Where to Go Next

From here, use this guide and the last one to convert the customersTable component and the products route. You’ll need to learn a few new with Angular’s template syntax, but otherwise you’ll have everything you need.



If you liked this guide, I’ve got 200+ detailed videos, quiz questions, and more for you in my comprehensive course Upgrading AngularJS. I created it for everyday, normal developers and it’s the best ngUpgrade resource on the planet. Head over and sign up for our email list to get a free Upgrade Roadmap Checklist so you don’t lose track of your upgrade prep. And, while you’re there, check out our full demo. You’re gonna love it!

See you next time, Scotchers!



Source link
thanks you RSS link
( https://scotch.io/tutorials/migrate-your-angularjs-services-to-angular-with-ngupgrade)

LEAVE A REPLY

Please enter your comment!
Please enter your name here