Angular Controllers using TypeScript


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

So far our Angular app is not all that useful but in this post we are going to create the controller for the store listing. A controller is almost like a ViewModel if you are used to MVVM patterns.

Whenever we add something to our angular app we typically need to do 4 things.

  1. Change the HTML markup in some way
  2. Create a new angular piece (a controller or a service or a filter etc.)
  3. Register it with an angular module and add any dependencies
  4. Include the new script in our index.html

The naming convention for controllers is that it should be UpperCamelCased and end in Ctrl or Controller so we’ll call our controller StoreListCtrl.

For the purposes of this series I am going to call the angular controller ...Ctrl while letting the ts (typescript) controllers class end in ...Controller so that it is easier to see how it all fits together.

Update the HTML

We have separated out all the HTML for the store listing to storeListView.html in the templates folder so that is where we'll be adding angular markup to databind to our controller.

  1. Bind the view to the controller by adding an ng-controller directive to the top div.  This way everything inside that div will be bound to the controller.  We'll be using the "controller as" syntax to make it super clear which controller we are binding to (in case we decide to use nested controllers)
    <div ng-controller="StoreListCtrl as vm">
    
  2. Replace the store listing <h1> so that it binds to the title
    <h1 class="page-header">{{::vm.title}} <small>a-z</small></h1>
    

    {{}} is used to bind to the controller and the :: here means that we’ll do one-time binding so once it’s bound it won’t update

  3. Replace div/row with the list of hard-coded stores with the following
    <div class="row">
       <div ng-repeat="store in vm.stores" class="col-sm-3 col-xs-6">
          <img class="img-responsive img-thumbnail portfolio-item" src="assets/img/image{{store.StoreId}}.jpg" />
          <h4>{{store.Name}}</h4>
       </div>
    </div>
    

    The ng-repeat here is like a for loop that will loop through the list of stores and output a div with an image and inside each iteration we bind to the store.StoreId and store.Name.

If you refresh the page in the browser you will see {{::vm.title}} in big letters so something is obviously wrong. Press F12 to get the dev tools and refresh the page again and you should get something along the lines of “Argument ‘StoreListCtrl’ is not a function, got undefined." Which as it turns out is pretty obvious since we haven’t written the controller yet.

Create the Controller

The Controller for our store listing is very simple. It exposes a title (set to “store listing”) and a list of stores, we’ll call it stores.

There are many ideas on the internet around how to structure your folders and files. Either just flat, or by type (all controllers in one directory and all the views in one like in ASP.NET MVC), or by feature area (everything related to stores in one directory). I like the latter since it is easier to keep track of everything as the app grows.

  1. Create a new folder under Scripts/app called stores
  2. Create a new typescript file StoreListController.ts under the stores directory
  3. In StoreListController.ts, let’s define the interface for our controller (and encapsulate it in a ts module)
    module app.stores {
        interface IStoreListController {
            title: string;
            stores: any[];
        }
    }
    

    We strongly type title as string so no one can accidentally assign a number to it without getting an error, but notice that I set stores as a list of any. We’ll change that later, but I wanted to do that to show that it is completely ok (semantically) to use untyped properties as well since any and all javascript is allowed in typescript.

    Note: the interface is purely a TypeScript dev time helper. If you look at the resulting js file you will notice that it contains no traces of the interface.

  4. Implement the interface in a StoreListController. This code should go inside the module, right after the interface...Now we’re going to get to the parts of typescript that C# people really enjoy. Classes and implements and constructors. Yummy!
    class StoreListController implements IStoreListController {
            title: string;
            stores: any[];
    
            constructor() {
                this.title = "Store Listing";
                this.stores = [
                    {
                        "StoreId": 1,
                        "Name": "Phone accessories R us",
                        "Location": "1A",
                        "WeekDayOpeningHours": "10 am - 8 pm",
                        "WeekEndOpeningHours": "10 am - 4 pm",
                        "Description": "We have cool phone accessories in all colors",
                        "StoreUrl": "http://www.phoneaccessories.com",
                        "Email": "info@phoneaccessories.com"
                    },
                    {
                        "StoreId": 2,
                        "Name": "Organize yourself",
                        "Location": "4A",
                        "WeekDayOpeningHours": "10 am - 8 pm",
                        "WeekEndOpeningHours": "10 am - 4 pm",
                        "Description": "Everything for your organizational needs",
                        "StoreUrl": "http://www.organize.com",
                        "Email": "info@organize.com"
                    },
                    {
                        "StoreId": 3,
                        "Name": "Fabulous Books",
                        "Location": "7A",
                        "WeekDayOpeningHours": "10 am - 8 pm",
                        "WeekEndOpeningHours": "10 am - 4 pm",
                        "Description": "The best bookstore in the mid west",
                        "StoreUrl": "http://www.fabbooks.com",
                        "Email": "info@fabbooks.com"
                    }];
            }
        }
    

    The transpiler will warn you if you assign the wrong type value to the title variable or if you miss to implement anything that you promised in your interface.

  5.  Right under the class definition, register the controller with the myApp module so that myApp knows about it
    angular.module("mallApp").controller("StoreListCtrl", StoreListController);
    

    by adding it here we don't have to clutter the app.ts file with registrations so the StoreListController is pretty self-contained. "StoreListCtrl" will be the name used when we refer to it with the ng-controller directive, and StoreListController is the class that defines the controller.

  6. Let’s fix up the “any” thing, it makes me a bit nervous.
    We'll do this by defining a new interface IStore that will describe the anatomy of a Store object. This will be used all over the place so we’ll put it in a folder called domain where we put all the “models”Create a folder under app, called domain
  7. Create a new TypeScript file called Store.ts with the following contents
    module app.domain {
        export interface IStore {
            StoreId: number;
            Name: string;
            Location: string;
            WeekDayOpeningHours: string;
            WeekEndOpeningHours: string;
            Description: string;
            StoreUrl: string;
            Email: string;
        }
    
        export class Store implements IStore {
            constructor(
                public StoreId: number,
                public Name: string,
                public Location: string,
                public WeekDayOpeningHours: string,
                public WeekEndOpeningHours: string,
                public Description: string,
                public StoreUrl: string,
                public Email: string) {
            }
        }
    }
    

    Notice the new keyword export, this is so that we can use the Store class and the IStore interface even outside the app.domain ts module. Again the resulting js contains no traces of the interface or the type information, both of these are purely for development purposes.

    The other thing that is interesting about this is that when we implement the Store class, we don’t actually create all the fields again, instead we drop them directly as parameters to the constructor and then we don’t do anything with them in the constructor. This is a neat little shortcut in typescript. When you add params to the constructor like this with public or private accessors, typescript automatically generates field variables for you so you don’t have to re-write that code. You can see that in action if you look at the resulting js.

  8. Go in and replace the any in StoreListController by app.domain.IStore both in the interface and in the class
  9. To add another store to the list you can add the following code at the bottom of the constructor
    var store = new app.domain.Store(4, "New store", "2A", "some hours", "some other hours", "some description", "www.url.com", "some@email.com");
    this.stores.push(store);
    

Include the new script

If you browsed the page now you would still get the same error because index.html knows nothing about neither StoreListController.js or Store.js so we need to include those scripts

<!-- domain classes -->
<script src="app/domain/store.js"></script>
<!-- controllers -->
<script src="app/stores/StoreListController.js"></script>

Browse to index.html

If you browse the site you should now see a listing of 4 hard-coded stores. Your project should look pretty similar to the controllers project in http://github.com/TessFerrandez/TheMall, with one exception, I have added a NavigationController for the menu, I will leave it up to you to implement that on your own.

Next: Creating the ASP.NET Web API

Comments (0)

Skip to main content