Angular Services using TypeScript


This is part of a blog series – please read the intro part and install the prerequisites if you want to follow along.

For this next part we will replace our hardcoded stores in StoreListController with a call to the back-end API we just created.  For this we can either use the Angulars $http or $resource.

$resource is pretty neat when you talk to a REST API.  You start by defining a resource (e.g. /api/stores/:id where id is optional).  Then you call one of the actions on the resource to get data. ‘query’ to get a list of stores back, or ‘get’ to get a single store back.  You can also call ‘save’, ‘remove’ or ‘delete’ but since we’re doing just read operations we wont cover those here.

Create a new Common.Services module where our services will live

All our services will live in a separate module (“common.services”) in the folder /app/common/services  so that we can use them from multiple modules in case we decide to have more SPAs on our site.   Using this technique we can also inject dummy services instead of the real ones during testing.

  1. Create the folders /app/common/services
  2. Create a new common.services.ts TypeScript file in the services folder to define the new common.services module
    module app.common.services {
        angular.module("common.services", ["ngResource"]);
    }
    

    This looks very similar to how we created the “mallApp” module, except for the “ngResource” part.  This is how Angular does dependency injection.

    By adding this, any services added to the “common.services” module, can now access the ngResource library.
    ngResource is the angular library that lets us use the magic $resource stuff.

Prepare to use the resource bits in our site

ngResource is not part of the base angular library so before we can use it we need to do a few things

  1. Download the angular-resource library with BowerYou can either type in “angular-resource”:”1.4.6″ to the bower.json file, or if you’re working in VS2015 Update1 you can also right-click on the bower.json file to get the neat UI for managing Bower Packages and install it from there
  2. Include the angular-resource script in index.html
    <script src="lib/angular-resource/angular-resource.js"></script>
    
  3. Download the TypeScript definitions for angular-resource
    tsd install angular-resource --save
    

And with that we’re ready to write some service code.  Before we do though, it’s worth mentioning that there are three options for registering services in Angular, Factory, Service and Provider.  We’ll use the Service way because it turns out to be pretty natural in TypeScript.

Create the DataAccessService

  1. Create a new DataAccessService.ts file under app/common/services
  2. Add an Interface describing the DataAccessService
    module app.common.services {
        interface IDataAccessService {
            getStoreResource(): ng.resource.IResourceClass<IStoreResource>;
        }
    }
    

    The DataAccessService will just give us access to the various API’s we may be using, in this case a resource for the stores API. We could also have methods here for getAdResource() or getProductResource() for example if we had APIs to get ads or products or similar.  We tell it to return a ng.resource.IResourceClass<IStoreResource> so we’ll have to define what that is.

  3. Declare the IStoreResource as a resource of IStores… this is to explain what types of items will be returned from or passed to the resource. This should go above the interface declaration for IDataAccessService
    interface IStoreResource extends ng.resource.IResource<app.domain.IStore> { }
    
  4. Next we’re going to implement the DataAccessService below the IDataAccessService interface
    export class DataAccessService implements IDataAccessService {
       //minification protection
       static $inject = ["$resource"]
       constructor(private $resource: ng.resource.IResourceService) { }
    
       getStoreResource(): ng.resource.IResourceClass<IStoreResource> {
          return this.$resource("/api/stores/:id");
       }
    }
    

    First off we use the export keyword just like with IStore and Store since we are going to use this from our own code.
    We then declare that we’ll be using $resource by injecting it into our constructor.  If you recall from the Store code, this will also create a private field called $resource which is why we can access it later with this.$resource.
    The static $inject part is very important, it protects from minification problems and it has to be static because it needs to be defined as part of the class rather than an instance of the class.

    With all that in place we just need to implement the getStoreResource to return the resource for /api/stores/:id

  5. Finally we need to register it with the common.services module as dataAccessService
    angular.module("common.services").service("dataAccessService", DataAccessService);
    

Use the service in the StoreListController

Lets modify the StoreListController so that it grabs the data from the service rather than using the hard-coded data

class StoreListController implements IStoreListController {
   title: string;
   stores: app.domain.IStore[];

   static $inject = ["dataAccessService"];
   constructor(private dataAccessService : app.common.services.DataAccessService) {
      this.title = "Store Listing";
      this.stores = [];

      var storeResource = dataAccessService.getStoreResource();
      storeResource.query((data: app.domain.IStore[]) => {
         this.stores = data;
      });
   }
}

Just like with $resource we need to inject the dataAccessService to use it.  Once we have it we can get the storeResource and call query on it to get the stores.

If you are a C# developer you should be pretty familiar with the lambda (=>) pattern but basically the lambda delegate is what will be called when the query method finishes.

Now that we are done with the service we need to include the js scripts in index.html so that the page knows about them too.

<!-- services -->
<script src="app/common/services/common.services.js"></script>
<script src="app/common/services/DataAccessService.js"></script>

And then we are ready to browse the index.html page again…

Oh no, everything broke, sad smiley

If you browse the site you are in for an unpleasant surprise. It seems like we’re back where we started with the visible {{::vm.title}}, what the heck happened here?

Press F12 in the browser to bring up the F12 tools, make sure you show the console, and refresh the page.
You’ll see the error [$injector:unpr] Unknown provider: dataAccessServiceProvider …but we injected the dataAccessService, what did we miss?

You guessed it… in order for anything in the “mallApp” module to use anything in the “common.services” module, we need to inject it at the module level.

Move over to app.ts and change the angular.module line to

var main = angular.module("mallApp", ["common.services"]);

Now we’re talking, browse the page again and you should see a beautiful list of stores.

But… there is one problem with the page still, the search box doesn’t work. We’ll get to that next.

Next: Filters

Comments (3)

  1. Billyson Bueno says:

    How do you add an UPDATE?

  2. Kieran John Harvey says:

    Cool thanks;

  3. Amadou Sall says:

    The problem with the way you did it is that you cannot instanciate a Class of app.domain.IStore directive i.e you have to use IStoreResource.get.
    What If you just want to create a new IStore (to call it’s save method) ?

Skip to main content