Overview

The starter project comes with a suggested starting point for a framework. Its features will be covered in this section.

It is important to note that this starter project is not actually a framework. See Framework vs. Starter Project for the distinction between the two.

The base namespace used is JustinCredible.SampleApp. To switch this to your own namespace, you can perform a project find and replace.

Build Schemes

The project is set up so that builds can be made for different environments. This is useful for easily switching between development, staging, or production environments, for example.

You can switch between build schemes using the gulp config --scheme scheme_name command.

See Development Tips: Build Schemes for more details.

Boot Sequence

Cordova uses the src attribute of the content element from config.xml to determine the initial page to load. This is set to index.html by default.

The index.html file has static references to all of the CSS and JavaScript files to load. In addition to the main application bundle (www/js/bundle.js), the page also references the first-level boot loader: www/js/boot1.js. This file is responsible for executing any code before Cordova's JS API, Ionic, or Angular have been initialized. It then kicks off the second-level boot loader by invoking JustinCredible.SampleApp.Boot2.main().

The second-level boot loader is located at src/Framework/Boot2.ts (which is compiled into www/js/bundle.js). The second-level boot loader is responsible for initializing Ionic after the Cordova JavaScript ready event occurs. This is where you would configure Angular services and modules.

The second-level boot loader automatically registers filters, directives, controllers, and services using a helper (src/Framework/BootHelper.ts). It does this by examining the applicable namespaces (e.g., JustinCredible.SampleApp.Filters, JustinCredible.SampleApp.Services, etc.). More details are provided in the applicable sections. The second-level boot loader also takes care of setting up the Angular routes using the src/Application/RouteConfig.ts file.

After initializing and configuring Angular, the second level boot loader then delegates to the start() method of the application service located in src/Application/Application.ts.

The application service is where the bulk of your low-level application-specific code should be located (i.e., device events, global event handlers, push notification handlers, etc.). It takes care of pushing the user to the initial view.

Views

A view or screen in your application will consist of an HTML Angular template, an Angular controller, a view model, and optionally, CSS styling.

A view is registered in the src/Application/RouteConfig.ts file by specifying the path to its template, the ID of its controller, and its URL and state name:

// A shared view used between categories, assigned a number via the route URL (categoryNumber).
$stateProvider.state("app.category", {
    url: "/category/:categoryNumber",
    views: {
        "root-view": {
            templateUrl: "Views/Category/Category.html",
            controller: Controllers.CategoryController.ID
        }
    }
});

In most simple Angular examples, you'll find that templates, controllers, and view models are often located in separate directories. While this is nice for a simple application, in larger applications it is a burden to track down each of the files. To avoid this, this starter project groups the files by feature rather than function.

For example, you'll find all of the files applicable to the category view located at src/Views/Category:

Controllers

Every controller in your application should extend the provided BaseController class and specify the type of view model for the view:

namespace JustinCredible.SampleApp.Controllers {

    export class CategoryController extends BaseController<ViewModels.CategoryViewModel> {

        //#region Injection

        public static ID = "CategoryController";

        public static get $inject(): string[] {
            return [
                "$scope",
                "$stateParams"
                Services.Utilities.ID
            ];
        }

        constructor(
            $scope: ng.IScope,
            private $stateParams: ICategoryStateParams,
            private Utilities: Services.Utilities) {
            super($scope, ViewModels.CategoryViewModel);
        }

        //#endregion

        //#region BaseController Events

        protected view_beforeEnter(event?: ng.IAngularEvent, eventArgs?: Interfaces.ViewEventArguments): void {
            super.view_beforeEnter(event, eventArgs);

            // Set the category number into the view model using the value as provided
            // in the view route (via the $stateParameters).
            let label = this.Utilities.format("Category: {0}", this.$stateParams.categoryNumber);
            this.viewModel.categoryLabel = label;
        }

        //#endregion
    }
}

Note

If your view does not have a view model, you can use the provided EmptyViewModel class.

The services to be injected into the controller are controlled via the static $inject property. These IDs are used to locate the services to be injected into the constructor.

The constructor receives these parameters and can save off the services to the local instance. TypeScript allows the private keyword in the constructor to indicate that the parameters should be accessible as private instance variables.

Finally, the base events avaiable from the BaseController can be overridden. These include several events exposed by Ionic:

View Model

The specified view model is accessible in the template via the viewModel property:

<ion-view view-title="Category {{viewModel.categoryNumber}}">

View Events

To access an event on your controller, you should make your event protected:

protected button1_click(event: ng.IAngularEvent): void {
   ...
}

and access it using the controller property:

<button ng-click="controller.button1_click($event)">Click Me!</button>

Directives

Angular directives are located in the src/Directives directory. There are two types of directives in this starter project: standard and element instance.

Standard Directive

A standard directive is simply a class that has a link function.

An example of a standard directive can be found in src/Directives/OnLoadDirective.ts. This directive can be used by placing an on-load attribute on an image element, and will cause a function to be fired when the image has loaded. For example:

<img src="..." on-load="controller.image1_load()">

Element Instance Directive

The optional BaseElementDirective class provides a recommendation on how to build element directives. This base class provides initialize and render methods that should be overridden in your implementation.

This base class is useful for building element directives that need to maintain state, fire events, or otherwise act as accessible instances from your controller.

An example directive that can be used to show an icon with text is located at /src/Directives/Icon-Panel/IconPanelDirective.ts, and is used from the category controller.

Filters

Angular filters are located in the src/Filters directory. To be registered as a filter automatically, a class should exist in the JustinCredible.SampleApp.Filters namespace, and it should contain a unique static ID property and a single static function named filter.

namespace JustinCredible.SampleApp.Filters {

    export class ThousandsFilter {

        public static ID = "Thousands";

        public static filter(input: number): string {
           ...
        }
    }
}    

Services

Angular services are located in the src/Services directory. To be registered as a service automatically, a class should exist in the JustinCredible.SampleApps.Service namespace and contain a unique static ID property.

namespace JustinCredible.SampleApp.Services {

    /**
     * Provides a common set of helper/utility methods.
     */
    export class Utilities {

        //#region Injection

        public static ID = "Utilities";

        public static get $inject(): string[] {
            return [
                MyService.ID,
                Preferences.ID
            ];
        }

        constructor(
            private MyService: MyService,
            private Preferences: Preferences) {
        }

        //#endregion

        public someMethod(): void {
             this.MyService.doSomething();
        }
    }
}    

Note

If the class contains a static getFactory method, it will be registered as a factory instead of a service.

Provided Services

The following services are provided with this sample project.

Configuration

Contains configuration values, including a reference to the build variables from the www/js/build-vars.js file.

FileUtilities

A set of helper methods for working with Cordova's file plugin.

HttpInterceptor

A special factory for intercepting all HTTP requests. It is responsible for showing a progress indicator (via the NProgress library) for asynchronous requests or a full-screen spinner for blocking requests.

It also takes care of expanding URLs starting with a tilde, defining the URL by prepending config.xml's ApiUrl property to it (e.g., ~/Products/123 would be expanded to http://your-server.com/path/Products/123).

It is also responsible for adding headers (such as API keys) or otherwise modifying requests before they go out.

Logger

Used to handle logging requests of various levels (e.g., info, warn, error, etc.).

The provided implementation delegates to the applicable console methods and stores logs in memory (which can be viewed via the in-app developer tools), but a production implementation could send logs to your servers.

MockHttpApis

Used to provide mock implementations for HTTP request data. This is useful for demos, development, or testing.

Mock API mode is enabled via the developer tools view.

See Development Tips: Mock HTTP APIs for more details.

MockPlatformApis

Used to provide mock implementations of APIs that are native to certain platforms. This allows you to mock up Cordova plugin APIs that may not be available in the browser during development.

This is mainly used by the plugins service.

See Development Tips: Mock Platform APIs for more details.

Plugins

Used as a facade to access native Cordova plugins. If a plugin is not available on the given platform, it will delegate to MockPlatFormApis to obtain a mock implementation.

Preferences

Used to store user preferences that should persist when the application has closed. The default backing store is the web view's local storage (which is sandboxed and specific to your application instance).

UIHelper

Contains several helper methods for user interface-related tasks. These include alert, confirm, and prompt dialogs as well as a PIN dialog.

It also includes a generic API to show your own custom dialogs.

See Dialogs for more information.

Utilities

Contains several helper methods for string manipulation, determining device information, type introspection, and more.

PIN Entry

The sample project includes a PIN entry dialog that the user can enable via the Settings view.

If the application is in the background for more than ten minutes, the user-specified PIN must be entered to use it.

Developer Tools

In a debug build, the in-app developer tools view will be accessible from the Settings view. In a non-debug build, the tools can be enabled by tapping the application icon in the About view ten times.

This view is a good location for items that are used during development. By default, it allows the user to toggle mock HTTP mode, test various plugins, view logs logged by the Logger service, and view device information.

Dialogs

Ionic provides the $ionicModal service, which can be used to show modal dialogs. This sample project includes the BaseDialogController base class and a UIHelper method showDialog(), which are used to simplify usage and normalize dialog behavior.

Two example dialogs are included with this sample project, located at src/Views/Dialogs.

The showDialog method wraps Ionic's modal implementation. It should be invoked with the ID of the controller for the dialog and optional dialog options. It returns a promise that is resolved once the dialog has been closed.

this.UIHelper.showDialog(PinEntryController.ID, options)
    .then((result: Models.PinEntryDialogResultModel) => {

    // Dialog closed with result object.
});

To create a dialog, you first need to create a template with the modal class and the ng-controller attribute to specify the ID of your dialog's controller:

<div class="modal" ng-controller="PinEntryController">

Then you'll need to create a controller that extends BaseDialogController. If you examine the base class, you'll see that it requires three templated types:

export class BaseDialogController<V, D, R> extends BaseController<V> { ... }

For example, the PIN entry dialog works with PinEntryViewModel (V). It receives PinEntryDialogModel as its input (D), and when it closes it returns PinEntryDialogResultModel (R):

export class PinEntryController
    extends BaseDialogController<ViewModels.PinEntryViewModel, Models.PinEntryDialogModel, Models.PinEntryDialogResultModel> {
    ...
}   

Note

Each of these types is optional, and not all dialogs require all three types. For any types you do not wish to specify, you can pass any or ViewModels.EmptyViewModel.

If you examine the sample dialogs, you'll see that the base class provides two events that are fired when the dialog opens and closes (dialog_shown and dialog_hidden, respectively).

There are are two base helper methods provided. The first, getData(), is used to obtain the model object used to open the dialog (templated type D). The second, close(), is used to close the dialog. You can optionally pass an object of type R to the close method, which will be returned to the opener via the promise result.

Popovers

Ionic provides the $ionicPopover service, which can be used to show a non-modal, popover UI element. This sample project includes the BasePopoverController base class and a UIHelper method createPopover(), which are used to simplify usage and normalize popover behavior.

An example popover is used on the Develper Tools > Logs view, and its code is located at src/Views/Settings/Logs-List/Log-Filter-Menu.

The createPopover() method wraps Ionic's popover creation API. It should be invoked with the ID of the controller for the popover and optional popover options. It returns a promise that is resolved once the popover has been created.

this.UIHelper.createPopover(LogFilterMenuController.ID, options)
    .then((popover: ionic.popover.IonicPopoverController) => {

    // Once the popover has been created, you can save a reference so you can show it later.
    this._myPopover = popover;

    // Also, you can listen for any events that you may emit from the popover's controller.
    this._myPopover.scope.$on("customEvent", _.bind(this.popover_customEvent, this));

    // Optionally, you can pass in data using an event (you'd listen for this event in your popover's controller).
    this._myPopover.scope.$broadcast("setData", { your: "data" });
});


// Later in some other method, you show or hiden the popover.
this._myPopover.show();
this._myPopover.hide();

To build a popover, you first need to create a template with the ion-popover-view directive and the ng-controller attribute to specify the ID of your popover's controller:

<ion-popover-view ng-controller="LogFilterMenuController">

Then you'll need to create a controller that extends BasePopoverController. If you examine the base class, you'll see that it requires a single templated type, which is the type of view model that will be used in the controller.

export class LogFilterMenuController
    extends BasePopoverController<ViewModels.LogFilterMenuViewModel> {
    ...
}

Note

If you don't need a view model you can pass any or ViewModels.EmptyViewModel.

If you examine the sample popover, you'll see that the base class provides two events that are fired when the popover is shown and is hidden (popover_shown and popover_hidden, respectively).

There is also a hide() helper method, so that the popover can be closed programmatically.

Unlike dialogs, there is no mechanism to pass data into, or out of, popovers. Instead, you should use events. To pass data in, you would broadcast to your popover by using this._myPopover.scope.$broadcast("event_name", data) before you show it. To pass data or events out, you'd use this.scope.$emit("event_name", data) from an event handler in your popover's controller.