Visual Studio Home Visual Studio

Get started with Ionic 2 apps in Visual Studio

Updated: 1/16/2017

Ionic is a popular front-end JavaScript framework for developing cross-platform mobile apps using Apache Cordova. The Ionic Framework gives Cordova applications a native look and feel, and automatically adjusts that look across platforms. You can use Visual Studio 2015 to easily create and debug cross-platform Ionic apps. The Get started with Visual Studio Tools for Apache Cordova guide showed how to create a simple Weather app using jQuery and jQuery Mobile. In this article, you'll learn how to configure a Visual Studio 2015 development environment for Ionic 2, and create the Ionic 2 version of the Weather App as shown below:

Finished Weather application

Requirements

To manage, code, run and debug Ionic 2 applications using Visual Studio, you must install the following:

Installing the Ionic 2 templates

  1. If you haven't already, install Visual Studio 2015.

    Under the covers, Ionic apps are Apache Cordova apps, you'll need a functional Cordova development environment before you can use with Ionic.

    Verify that you can create and run the default Cordova Blank App. In Visual Studio, open the File menu, select New, then Project. In the new project dialog, expand the JavaScript templates section, select Apache Cordova Apps then pick the Blank App (Apache Cordova) template. Give the new project a name and location then click the OK button. Press F5 to build and run the new project in the Ripple Emulator. The Chrome browser should open and render the app's content. If you have any issues running the application, see these troubleshooting steps.

  2. Install the Ionic template in Visual Studio by selecting the Tools menu, then Extensions and Updates.... In the Extensions and Updates dialog box, select Online. In the search box located in the upper-right corner of the dialog, type Ionic 2. Select the Ionic 2 RC Templates option in the list. Click the Download button to start the installation.

    Getting the templates

    The template files will download, then Visual Studio will automatically launch the installation process. When prompted, click the Install button to begin the installation.

    Installing the templates

Note: The Ionic templates may not appear in Visual Studio until it's restarted. Close, then re-open Visual Studio before continuing.

Creating an Ionic Project in Visual Studio

  1. Create a new Ionic project. Open the File menu, select New, then Project. In the new project dialog, expand the TypeScript templates section, and then select Apache Cordova Apps. Ionic offers three default app styles: Blank, Sidemenu, and Tabs; this is a simple project, so select the Blank template.

    Give the new project a Name and Location, and then click the OK button. This app will later become our Weather App project, so you should probably call it Weather App or Weather App Ionic 2 to save time.

    New Project dialog

  2. Check the new Ionic project's readme file for any additional tools that must be installed to use the template.

    Dependencies node

    At the time of this writing, you'll need to install the following Visual Studio extensions:

    • NPM Task Runner
    • TypeScript 2.0.6 editor
    • Microsoft ASP.NET and Web Tools Preview 2

    The requirements may change as updates are made to the templates. To install the required extensions, open the Tools menu, and select Extensions and Updates. Use search to locate and install the required extensions.

  3. Ionic is a heavy framework, it requires a lot of tools and libraries to operate. Once Visual Studio creates the new project, it kicks off a process to download and install the required components using the Node Package Manager (npm). Wait a few minutes as Ionic's npm packages are installed.

    Note This process will take several minutes depending on your system and internet connection speeds.

    To check progress, open Solution Explorer and look for the Dependencies node. You should see Restoring..., if you don't, right-click on the Dependencies node in Solution Manager then select Restore Packages.

    Dependencies node

    Note: After Visual Studio finishes installing dependencies, the Dependencies node may show not installed. This is a known issue; the Ionic project will be OK at this point.

    Dependencies node

    You can monitor the package installation process through Visual Studio's Output window. Open the View menu, then select Output or use the keyboard shortcut Ctrl-W + O:

    Task Runner

  4. At this point, you have a complete Ionic application project ready to go. To test and debug the application in Visual Studio, select a target platform in the Standard Toolbar, select a target device, then pres F5 to run the application on the selected target. Developing iOS applications requires some extra configuration; refer to the Visual Studio Tools for Apache Cordova: iOS Guide for additional information.

    Ionic application running on Android

Enabling Live Reload in Ripple

Ionic uses Node Package Manager (npm) scripts to compile your application's TypeScript and SASS files into JavaScript and CSS, then copies the output into the project's www folder. By default, Visual Studio uses the ionic:build command in the project's package.json to build the project. Visual Studio executes this script every time you launch the Ionic application.

While developing your app using the Ripple emulator, you may want to setup your project to live reload changes into the browser as you work on your files. To do this:

  1. Open the Task Runner Explorer: In Visual Studio, open the View menu, select Other Windows then Task Runner Explorer. You can also use the keyboard shortcut: Ctrl + Alt + Backspace.

  2. Right click the ionic:build task and choose the Bindings -> Project Open menu to setup the watch task to run when you load your project.

  3. Run your Ionic app in the Ripple browser.

After following the above steps, you'll now be able to make edits to the files in your src folder and your changes will automatically show up in the Ripple emulator. The status of the watch task will appear in a separate window in the Task Runner Explorer.

Task Runner - Watch

Creating the Weather App

Now, lets do something with this app you created. You'll take the blank application project and turn it into the Weather App shown earlier. In the following sections, you'll start by adding a Current Conditions page, then wire in a Search Box, and finally add an area to display Forecast data. Lets get started.

The app uses the free OpenWeatherMap service to retrieve weather conditions for a location. Before you can use the service, you must setup an account and request an API key for their Current Conditions API. Point your browser to OpenWeatherMap and setup an account. Once you have a valid login, go to the service's API page and subscribe to the Current Weather data service, and then generate an API key.

Note: Make note of the API key as you'll need it later in the app's TypeScript code.

Configuring the Application Project

  1. Start by setting some configuration options for the application. In Visual Studio Solution Explorer, double-click the project's config.xml file. Visual Studio will open a custom editor similar to the one shown in the following figure:

    Modifying the application's config.xml file

    At a minimum, set the project's Display Name and Package Name values; you may want to update the Author and Description fields as well.

  2. By default, the application displays weather data based on the device's current location, so we'll need to use the Cordova Geolocation plugin to add that capability to our application. You could use the browser's built-in geolocation capabilities, but Cordova and Ionic both provide special capabilities that make using geolocation in an Ionic app a little easier.

    Switch to the editor's Plugins tab and select the Geolocation plugin in the list. Click the Add button to add the plugin to the project.

    Adding the Cordova Geolocation plugin

    Press the keyboard's Ctrl-S key to save your changes, then close the editor file.

  3. Open the project's src\index.html file and change the application's Title. This isn't a required step, but if you're later testing the app in the browser, the app title will be correct.

    <title>Weather App Ionic</title>
    

Installing the Ionic CLI

Visual Studio and TACO deliver a robust development and debugging environment for Ionic 2 applications. However, there are a couple of Ionic application management tasks that require the use of the Ionic CLI.

  1. If you haven't already, install the Node JavaScript runtime from nodejs.org. You'll want the latest LTS version.

  2. Next, install Apache Cordova and the Ionic CLI by opening a terminal window and executing the following command:

    npm install -g cordova ionic
    

    Validate that the installation worked by executing cordova and, separately, ionic in the terminal window. You should see help pages display; if you receive error messages, you'll have some additional work to do.

Generating a Weather Provider

Now, lets start working on the application's code. By convention, Ionic applications segregate data sources from other types of application code using a special Providers component type. Since the weather data is coming from an external source, and we don't want the application code to call the service directly, we'll make a Weather provider and put all of the application's weather access code there.

  1. Open a terminal window and navigate to the project's Ionic project folder. In the examples shown earlier, the folder is c:\dev\WeatherAppIonic2\WeatherAppIonic2. The nested folders were created because the option to Create directory for solution was selected when creating the project.

    When the terminal window is pointing to the right folder, execute the following command:

    ionic g provider Weather
    

    The Ionic CLI will create a new folder in the project: src\providers\weather and populate the folder with a file called weather.ts.

  2. Now that you have the Weather provider, you have to tell Ionic about it. Open the project's src\app\app.module.ts file and add the following line to the imports section at the top of the file:

    import { Weather } from '../providers/weather';
    
  3. Next, add a reference to the Weather provider in the file's providers array:

    providers: [Weather, {provide: ErrorHandler, useClass: IonicErrorHandler}]
    

    Save your changes before continuing.

Coding the Weather Provider

In this section, we'll add code the Weather Provider to connect with the external weather API and deliver data to the application.

  1. Open the newly created src\providers\weather.ts file, and modify the @angular/http import at the top of the file so it looks like the following:

    import { Http, Response } from '@angular/http';
    

    The HTTP component is loaded by default in any provider, what you're doing here is adding the Response component we'll use in the provider's code later.

  2. Still at the top of the file, add an import that loads the toPromise module:

    import 'rxjs/add/operator/toPromise';
    

    When you're done, the imports section at the top of the file will look like the following:

    import { Injectable } from '@angular/core';
    import { Http, Response } from '@angular/http';
    import 'rxjs/add/operator/map';
    import 'rxjs/add/operator/toPromise';
    
  3. In the top of the Weather class, add the following variable declarations:

    private weatherEndpoint = 'http://api.openweathermap.org/data/2.5/';
    private weatherKey = '';
    

    Populate the weatherKey variable with the API key you obtained from the weather service earlier.

  4. After the weatherclass constructor, add the following code:

    getCurrent(loc: any): Promise<any> {
      let url: string = this.makeDataURL(loc, 'weather');
      return this.http.get(url)
        .toPromise()
        .then(this.extractData)
        .catch(this.handleError);
    }
    
    private makeDataURL(loc: any, command: string): string {
      //Build a weather service URL using the command string and
      //location data that we have.
      let uri = this.weatherEndpoint + command;
    
      //Do we have a location?
      if (loc.long) {
        //then use the 'grographical coordinates' version of the API
        uri += '?lat=' + loc.lat + '&lon=' + loc.long;
      } else {
        //Otherwise, use the zip code
        uri += '?zip=' + loc.zip;
      }
    
      //Configure output for imperial (English) measurements
      uri += '&units=imperial';
      //Use the following instead for metric
      //uri += '&units=metric';
    
      //Append the API Key to the end of the URI
      uri += '&APPID=' + this.weatherKey;
      //Return the value
      return uri;
    }
    
    //'Borrowed' from //https://angular.io/docs/ts/latest/guide/server-communication.html
    private extractData(res: Response) {
      //Convert the response to JSON format  
      let body = res.json();
      //Return the data (or nothing)
      return body || {};
    }
    
    //'Borrowed' from //https://angular.io/docs/ts/latest/guide/server-communication.html
    private handleError(res: Response | any) {
      console.error('Entering handleError');
      console.dir(res);
      return Promise.reject(res.message || res);
    }
    

    On start up, the application passes the current location (longitude and latitude) to the getCurrent function. When searching for a location using a US Zip Code, the Zip Code is passed instead. getCurrent calls the appropriate API to get the current weather data, but uses makeDataURL to format the request URL correctly depending on whether it has a location or a Zip Code value.

    The extractData function formats the data returned by the API call as JSON. This code could easily be included directly into the getCurrent method, but as you'll see later, this code is used more than once by the application, so it made sense to make it a separate function.

Building the Current Conditions Page

  1. Open the project's src\pages\home\home.html file, and replace the file's contents with the following:

    <ion-header>
      <ion-toolbar>
        <ion-buttons right>
          <button ion-button (click)="refreshPage()">
            <ion-icon name="refresh"></ion-icon>
          </button>
        </ion-buttons>
        <ion-title>
          Weather App (Ionic 2)
        </ion-title>
      </ion-toolbar>
    </ion-header>
    
    <ion-content padding> 
      <ion-list no-lines>
        <!--show this if there are no items in the list-->
        <ion-item [hidden]="c_items.length > 0">
          <p><strong>Weather data is not available</strong></p>
        </ion-item>
        <!--Display the current conditions data we have -->
        <ion-item *ngFor="let c_item of c_items">
          <p><strong>{{c_item.name}}:</strong> {{c_item.value}}</p>
        </ion-item>
      </ion-list>
    </ion-content>
    
    <ion-footer>
      <ion-toolbar>
        <ion-title>Visual Studio Tools for Apache Cordova</ion-title>
      </ion-toolbar>
    </ion-footer>
    

    What you see in the file is references to ionic components as well as some Angular code as well. This markup is a subset of the page's HTML, the rest of it is found in the project's src\index.html and src\app\app.html files. At start up, the Ionic framework replaces the contents of the project's src\index.html file's <ion-app></ion-app> tag with the src\app\app.html file's <ion-nav [root]="rootPage"></ion-nav> then subsequently passes HTML content in and out of that tag as the user uses the application. In this case, when the app starts up, the content of the src\pages\home\home.html page is parsed and rendered.

    The header contains a toolbar displaying the application title and a refresh button the user can tap to refresh the weather data displayed on the page.

    The ion-content tag defines the part of the content that comprises the dynamic content of the page. In this example, it displays an <ion-list> of items; basically a listview. In this case, it renders the contents of the c_items array as a list using an Angular directive: *ngFor="let c_item of c_items" which loops through each element in the array. As the code loops, it assigns each array element to c_item, then the template code that follows displays the name and value properties of the c_item object. You'll see how the c_items array is populated soon.

  2. Open the project's src\pages\home\home.ts file. At the top of the file, add imports for the Geolocation plugin and the Weather provider:

    import { Geolocation, Keyboard } from 'ionic-native';
    import { Weather } from '../../providers/weather';
    
  3. Also at the top of the file, modify the ionic-angular import so it looks like the following:

    import { AlertController, LoadingController, NavController, Platform } from 'ionic-angular';
    

    This loads some additional components used by the page's code:

    • AlertController manages the display of alert dialogs.
    • LoadingController manages the display of a loading spinner.
    • NavController manages navigation between pages.
    • Platform provides platform-related utilities.
  4. Inside the HomePage class, add the following variable declarations:

    degreeStr: string = ' degrees (F)';
    //an empty object (for now) to store our location data passed to the API
    currentLoc: any = {};
    //current weather items array
    c_items: Array<any> = [];
    
  5. Add the following as parameters passed to the HomePage class constructor:

    public alertController: AlertController,
    public loadingCtrl: LoadingController,
    public platform: Platform,
    public weather: Weather,
    

    When you're done, it should look like the following:

    constructor(
      public alertController: AlertController,
      public loadingCtrl: LoadingController,
      public nav: NavController,
      public platform: Platform,
      public weather: Weather
    ) {
    
    }
    
  6. Add the following code to the body of the HomePage class:

    ionViewDidLoad() {
      //Once the main view loads
      //and after the platform is ready...
      this.platform.ready().then(() => {
        //Setup a resume event listener
        document.addEventListener('resume', () => {
          //Get the local weather when the app resumes
          this.getLocalWeather();
        });
        //Populate the form with the current location data
        this.getLocalWeather();
      });
    }
    
    refreshPage() {  
      this.showCurrent();     
    }
    
    getLocalWeather() {
      let locOptions = { 'maximumAge': 3000, 'timeout': 5000, 'enableHighAccuracy': true };
      Geolocation.getCurrentPosition(locOptions).then(pos => {
        //Store our location object for later use
        this.currentLoc = { 'lat': pos.coords.latitude, 'long': pos.coords.longitude };
        //and ask for the weather for the current location
        this.showCurrent();
      }).catch(e => {
        console.error('Unable to determine current location');
        if (e) {
          console.log('%s: %s', e.code, e.message);
          console.dir(e);
        }
      })
    }
    
    showCurrent() {
      //clear out the previous array contents
      this.c_items = [];
      //Create the loading indicator
      let loader = this.loadingCtrl.create({
        content: "Retrieving current conditions..."
      });
      //Show the loading indicator
      loader.present();
      this.weather.getCurrent(this.currentLoc).then(
        data => {
          //Hide the loading indicator
          loader.dismiss();
          //Now, populate the array with data from the weather service
          if (data) {
            //We have data, so lets do something with it
            this.c_items = this.formatWeatherData(data);
          } else {
            //This really should never happen
            console.error('Error retrieving weather data: Data object is empty');
          }
        },
        error => {
          //Hide the loading indicator
          loader.dismiss();
          console.error('Error retrieving weather data');
          console.dir(error);
          this.showAlert(error);
        }
      );
    }
    
    private formatWeatherData(data): any {
      //create a blank array to hold our results
      let tmpArray = [];
      //Add the weather data values to the array
      if (data.name) {
        //Location name will only be available for current conditions
        tmpArray.push({ 'name': 'Location', 'value': data.name });
      }
      tmpArray.push({ 'name': 'Temperature', 'value': data.main.temp + this.degreeStr });
      tmpArray.push({ 'name': 'Low', 'value': data.main.temp_min + this.degreeStr });
      tmpArray.push({ 'name': 'High', 'value': data.main.temp_max + this.degreeStr });
      tmpArray.push({ 'name': 'Humidity', 'value': data.main.humidity + '%' });
      tmpArray.push({ 'name': 'Pressure', 'value': data.main.pressure + ' hPa' });
      tmpArray.push({ 'name': 'Wind', 'value': data.wind.speed + ' mph' });
      //Do we have visibility data?
      if (data.visibility) {
        tmpArray.push({ 'name': 'Visibility', 'value': data.visibility + ' meters' });
      }
      //do we have sunrise/sunset data?
      if (data.sys.sunrise) {
        var sunriseDate = new Date(data.sys.sunrise * 1000);
        tmpArray.push({ 'name': 'Sunrise', 'value': sunriseDate.toLocaleTimeString() });
      }
      if (data.sys.sunset) {
        var sunsetDate = new Date(data.sys.sunset * 1000);
        tmpArray.push({ 'name': 'Sunset', 'value': sunsetDate.toLocaleTimeString() });
      }
      //Do we have a coordinates object? only if we passed it in on startup
      if (data.coord) {
        //Then grab long and lat
        tmpArray.push({ 'name': 'Latitude', 'value': data.coord.lat });
        tmpArray.push({ 'name': 'Longitude', 'value': data.coord.lon });
      }
      //Return the new array to the calling function
      return tmpArray;
    }
    
    showAlert(message: string) {
      let alert = this.alertController.create({
        title: 'Error',
        subTitle: 'Source: Weather Service',
        message: message,
        buttons: [{ text: 'Sorry' }]
      });
      alert.present();
    }
    

    There's a lot of code there; take a few minutes and study it. Here's what it does:

    • ionViewDidLoad - Handles an event that fires when the page completes loading. In this case, the function calls the getLocalWeather function to start the process of getting weather data for the device's current location. It uses capabilities of the Platform component to make sure it doesn't do anything until it's certain that the native application container and the Ionic framework have completed initialization.
    • refreshPage - Executes when the application user taps the Refresh button in the toolbar. Here, it refreshes the current weather data on the page. This function gets more responsibilities later.
    • getLocalWeather - Determines the device's current location (using the Cordova Geolocation plugin), then calls showCurrent to get the current weather data.
    • showCurrent - Displays the loading indicator, then calls the Weather provider to get weather data for the specified location (either a geolocation, or, later, a Zip Code).
    • formatWeatherData - The results the application gets from the Weather API differ depending on whether the application asks for current conditions or the forecast. We're not asking for forecast data (yet) but, but will later. This function builds a weather data object from the results and has special code that builds the appropriate object based on whether it's current conditions or a forecast.
    • showAlert - Displays an error dialog if anything breaks.

Run the application in the Ripple Emulator or on a physical device connected to the computer. If you run the app in the Ripple Emulator, you'll need to tweak the simulated device's coordinates using the Geolocation panel. When the application loads, you should see the current weather data for the current location as shown in the figure below:

Weather App: Current Weather conditions

It's not the complete app UI, but at least you can see the weather results. In the next section, we'll add the Zip Code Search box so you can get weather data for a specific location.

Note: If you get a blank screen when the app starts, make sure you've added the Cordova Geolocation plugin to the project.

Adding Zip Code Search

It's useful to have weather data for the current location, but what if you're traveling somewhere and want to know what the weather will be like when you get there? In this section, you'll add a search box users can employ to get weather data for a specific US Zip Code.

Note: Using Zip Code as search criteria won't work outside of the US; if you would like to use City Name instead, you can make minor tweaks to the weather API call to change how the API searches. Take a look at the API docs for more information.

  1. Open the project's src\pages\home\home.html file. Inside the beginning of the <ion-content> section, right before the <ion-list> tag, add the following markup:

     <form (ngSubmit)="setZipCode()">
        <ion-item>
          <ion-label>Type your zip code:</ion-label>
          <ion-input type="text" [(ngModel)]="searchInput" name="ZipCode"></ion-input>
        </ion-item>
          <button ion-button icon-left block>
            <ion-icon name="search"></ion-icon>
            Find Weather
          </button>
      </form>
    

    This adds the search field at the top of the page, and directs that the setZipCode function is executed when the user taps the Find Weather button. The reference to ngModel tells Ionic (actually Angular) to map the value in the search field to the application's searchInput variable which you will see in a second.

  2. Open the project's src\pages\home\home.ts file and add the following variable declaration to the top of the HomePage class (before the constructor):

    //Mapped to the search field
    searchInput: string = '';
    

    This defines the variable that will be populated automatically with whatever string the user adds to the search field.

  3. Finally, still in the project's src\pages\home\home.ts file, add the following function to the HomePage class:

    setZipCode() {
      //whenever the user enters a zip code, replace the current location
      //with the entered value, then show current weather for the selected
      //location.
      //Hide the keyboard if it's open, just in case
      Keyboard.close();
      //Populate the currentLoc variable with the city name
      this.currentLoc = { 'zip': this.searchInput };
      //Clear the Zip Code input field
      this.searchInput = '';
      //get the weather for the specified city
      this.showCurrent();
    }
    

    This function responds to the user tapping the Find Weather button and creates a location object then calls showCurrent to retrieve weather data for the specified Zip Code. The Keyboard.close() code uses the Ionic Keyboard plugin to hide the keyboard before initiating the search. If the code didn't do this, it's possible that the keyboard would stay open, blocking part of the page, as the page updates with the new weather data.

When you run the application, you should now see a search field at the top of the page. Enter a US Zip Code and tap the button to get weather data for the specified location.

Weather App: Current Weather conditions

Adding a Weather Forecast Page

The weather API the app uses has an API for retrieving forecast data, so lets display that in the application as well. You can add another page to the app, then use a menu or another navigation method to switch between views, but the search field complicates that approach. Instead, we'll use an Ionic Segment component to split the existing page into two parts: one for current conditions, and another for the forecast.

  1. Start by updating the Weather provider to support getting the forecast from the weather service. Open the project's src\providers\weather.ts file and add the following code to the Weather class.

    getForecast(loc: any): Promise<any> {
      let url: string = this.makeDataURL(loc, 'forecast');
      return this.http.get(url)
        .toPromise()
        .then(this.extractData)
        .catch(this.handleError);
    }
    

    To get the forecast, all the provider has to do is replace weather with forecast in the API URL, otherwise the code is exactly the same.

  2. Next, open the project's src\pages\home\home.html file. Remove the current <ion-list> implementation:

    <ion-list no-lines>
      <!--show this if there are no items in the list-->
      <ion-item [hidden]="c_items.length > 0">
        <p><strong>Weather data is not available</strong></p>
      </ion-item>
      <!--Display the current conditions data we have -->
      <ion-item *ngFor="let c_item of c_items">
         <p><strong>{{c_item.name}}:</strong> {{c_item.value}}</p>
      </ion-item>
    </ion-list>
    

    and replace it with the following:

    <ion-segment [(ngModel)]="displayMode">
      <ion-segment-button value="current" (ionSelect)="showCurrent()">
        Current
      </ion-segment-button>
      <ion-segment-button value="forecast" (ionSelect)="showForecast()">
        Forecast
      </ion-segment-button>
    </ion-segment>
    <div [ngSwitch]="displayMode">
      <ion-list no-lines *ngSwitchCase="'current'">
        <!--show this if there are no items in the list-->
        <ion-item [hidden]="c_items.length > 0">
          <p><strong>Weather data is not available</strong></p>
        </ion-item>
        <!--Display the current conditions data we have -->
        <ion-item *ngFor="let c_item of c_items">
          <p><strong>{{c_item.name}}:</strong> {{c_item.value}}</p>
        </ion-item>
      </ion-list>
      <ion-list inset *ngSwitchCase="'forecast'">
        <ion-item text-wrap>
          {{ f_items.length ? 'Tap an item to view the forecast for the selected period.': 'Forecast data is not available at this time.' }}    
        </ion-item>            
        <button detail-push ion-item *ngFor="let item of f_items" (click)="viewForecast(item)">
          <ion-icon name="time" item-left></ion-icon>
            {{item.period}}
        </button>
      </ion-list>
    </div>
    

    This adds a segment to the page using the <ion-segment> component, and defines two segments: current and forecast. Next, the <div> with the [ngSwitch]="displayMode" attribute switches in separate <ion-list> components based on the value assigned to the page's displayMode variable.

  3. Now we'll add the TypeScript code that make all of this work. Open the project's src\pages\home\home.ts file and add the following variable declarations to the top of the HomePage class.

    //This is used to set the Ionic Segment to the first segment
    currentMode: string = 'current'; 
    // used to control which segment is displayed
    displayMode: string = this.currentMode;
    //holds forecast data, a separate row for each period
    f_items: Array<any> = [];
    //array of day strings used when displaying forecast data
    days: Array<string> = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
    
  4. When the page loads, we want the app to default to displaying the current conditions, so add the following code to the onViewDidLoad function:

    //Switch to the Current segment
    this.displayMode = this.currentMode
    

    This sets this.displayMode to current. Add the code to the resume event listener, the resulting code should look like this:

    document.addEventListener('resume', () => {
      //Get the local weather when the app resumes
      //Switch to the Current segment
      this.displayMode = this.currentMode
      //then update it with local weather conditions
      this.getLocalWeather();
    });
    
  5. Now that the page displays two segments, the refreshPage function needs to know which part of the page to refresh. Replace the current refreshPage function with the following code:

    refreshPage() {
      //Which page are we looking at now?
      if (this.displayMode === this.currentMode) {
        this.showCurrent();
      } else {
        this.showForecast();
      }
    }
    
  6. The setZipCode function defaults to loading current conditions when a Zip Code is entered, so it must switch to the Current tab before getting results. Add the following lines to the function before the call to this.showCurrent():

    //Switch to the 'current' tab
    this.displayMode = this.currentMode;
    

    The complete function should look like this:

    setZipCode() {
      //whenever the user enters a zip code, replace the current location
      //with the entered value, then show current weather for the selected
      //location.
      //Hide the keyboard if it's open, just in case
      Keyboard.close();
      //Populate the currentLoc variable with the city name
      this.currentLoc = { 'zip': this.searchInput };
      //Clear the Zip Code input field
      this.searchInput = '';
      //Switch to the 'current' tab
      this.displayMode = this.currentMode;
      //get the weather for the specified city
      this.showCurrent();
    }
    
  7. Finally, add the showForecast function to the HomePage class:

    showForecast() {
      //clear out the previous array contents
      this.f_items = [];
      //Create the loading indicator
      let loader = this.loadingCtrl.create({
        content: "Retrieving forecast..."
      });
      //Show the loading indicator
      loader.present();
      this.weather.getForecast(this.currentLoc).then(
        data => {
          //Hide the loading indicator
          loader.dismiss();
          //Now, populate the array with data from the weather service
          //Do we have some data?
          if (data) {
            //Then lets build the results array we need
            //Process each forecast period in the array
            for (let period of data.list) {
              //Create a 'record' consisting of a time period's results
              let weatherValues: any = this.formatWeatherData(period);
              //Append this, along with the time period information, into the forecast
              //items array.          
              //Get the forecast date as a date object                     
              let d = new Date(period.dt_txt);
              //Determe the day of the week
              let day = this.days[d.getDay()];
              //And the time
              let tm = d.toLocaleTimeString();
              //Create a new object in the results array for this period          
              this.f_items.push({ 'period': day + ' at ' + tm, 'values': weatherValues });
            }
          } else {
            //This really should never happen
            console.error('Error displaying weather data: Data object is empty');
          }
        },
        error => {
          //Hide the loading indicator
          loader.dismiss();
          console.error("Error retrieving weather data");
          console.dir(error);
          this.showAlert(error);
        }
      );
    }   
    

    This function gets the forecast data, then formats it for display on two pages. First, it builds an array of time periods that represent the different forecast periods. Then, for each period, it appends an object containing the forecast data for that period. The page's forecast segment displays the list of forecast periods, then, when you tap on an entry, the application will eventually (you'll see how in the next section) open a separate page to display the details.

At this point, if you run the application, you should see the following:

Finished Weather application

Tap the forecast tab, and you'll see the list of forecast periods shown in the following figure:

Finished Weather application

When you tap on a forecast item, nothing happens. That's because we need a page to display the data, and you haven't added one yet.

Adding a Weather Details Page

  1. Open a terminal window, navigate to the Ionic project folder WeatherAppIonic2\WeatherAppIonic2, then issue the following command:

    ionic g page WeatherDetail
    

    This command generates a new page in the application. In Solution Manager under src\pages you should see a new set of weather-detail folders containing multiple files: weather-detail.ts, weather-detail.html, and weather-detail.scss.

  2. As when you added a provider to the application, when you add a page, you need to update the app's configuration to use it. Open the project's src\app\app.module.ts file, and add the following import statement to the top of the file:

    import { WeatherDetailPage } from '../pages/weather-detail/weather-detail';
    
  3. Next, add a reference to the WeatherDetailPage component to the declarations and entryComponents arrays as shown below:

    @NgModule({
      declarations: [
        MyApp,
        HomePage,
        WeatherDetailPage
      ],
      imports: [
        IonicModule.forRoot(MyApp)
      ],
      bootstrap: [IonicApp],
      entryComponents: [
        MyApp,
        HomePage,
        WeatherDetailPage    
      ],
      providers: [Weather, { provide: ErrorHandler, useClass: IonicErrorHandler }]
    })
    export class AppModule { }
    
  4. Open the project's src\pages\weather-detail\weather-detail.html and replace its contents with the following markup:

    <ion-header>
      <ion-navbar>
        <ion-title>Forecast</ion-title>
      </ion-navbar>
    </ion-header>
    
    <ion-content padding>
      <ion-list>
        <!--show this if there are no items in the list-->
        <ion-item [hidden]="forecast.values.length > 0">
          <p><strong>Weather data is not available</strong></p>
        </ion-item>
        <ion-item [hidden]="forecast.values.length < 1">
          Period: {{forecast.period}}
        </ion-item>
        <ion-item *ngFor="let item of forecast.values">
          <p><strong>{{item.name}}:</strong> {{item.value}}</p>
        </ion-item>
      </ion-list>
    </ion-content>
    
    <ion-footer>
      <ion-toolbar>
        <ion-title>Visual Studio Tools for Apache Cordova</ion-title>
      </ion-toolbar>
    </ion-footer>
    

    This creates a page that displays the selected forcast item as a list of name and value values.

  5. Open the project's src\pages\weather-detail\weather-detail.ts file and replace its contents with the following code:

    import { Component } from '@angular/core';
    import { NavController, NavParams } from 'ionic-angular';
    
    @Component({
      selector: 'page-weather-detail',
      templateUrl: 'weather-detail.html'
    })
    export class WeatherDetailPage {
    
      forecast: any;
    
      constructor(public navCtrl: NavController, public navParams: NavParams) {
        //Pull the selected forecast off of navParams
        this.forecast = this.navParams.get('forecast');
      }
    
      ionViewDidLoad() {
        console.log('Weather Detail page loaded');
      }
    }
    

    Notice that there's no real code in the page, that's because the HTML template takes care of rendering the data and Ionic takes care of page navigation automatically. The three important changes are:

    • The addition of a NavParams component to the page, it's responsible for passing data between pages.
    • The addition of a forecast variable:

      forecast: any;
      

      This variable is storage for the forecast object passed to the page when it opens. It holds data for the current forcast period. The HTML page references this variable as the source for the data it renders on the page.

    • The addition of a single line of code to the constructor:

      this.forecast = this.navParams.get('forecast');
      

      This assigns the local this.forecast object to the data passed in the forecast object sent through the NavParams component

  6. Now we need to tell the forecast segment how to open the forecast details page when the user taps on an item in the list. Open the project's src\pages\home\home.ts and add the following import statement to the top of the file:

    import { WeatherDetailPage } from '../weather-detail/weather-detail';
    
  7. Still in src\pages\home\home.ts, add the viewForecast function to the class:

    viewForecast(item) {
      //When the user selects one of the Forecast periods,
      //open up the details page for the selected period.
      this.nav.push(WeatherDetailPage, { 'forecast': item });
    }
    

    The page's HTML already has a reference to this function:

    <button detail-push ion-item *ngFor="let item of f_items" (click)="viewForecast(item)">
      <ion-icon name="time" item-left></ion-icon>
      {{item.period}}
    </button>
    

When you run the application now, you should be able to open the forecast by tapping on one of the forecast periods in the list:

Weather App Forecast page

Page navigation is handled by Ionic and Angular, they take care of adding the back arrow in the left corner of the toolbar and closing the page when it's tapped.

That's it! You've completed the Ionic Weather App.

Troubleshooting: Let's fix it

A few errors are fairly common in the starter templates when debugging in Visual Studio.

I installed the Ionic templates, but they don't appear as an option in Visual Studio

Did you receive an error message that says: 'Promise is not defined'?

Did you receive an error message indicating that some node modules won't restore?

Did you receive a WWAHost runtime error?

Did you receive an error message asking you to install a new app?

Did you receive an 'Unhandled exception' running on Windows?

Did you receive an error messaging indicating that the appxrecipe file is missing?

Did you receive an error message saying that the Content Security Policy is missing?

Did you receive a certificate error on Windows?

Visual Studio Emulator for Android won't run?

Having trouble hitting breakpoints in your .ts files?

Other issues?


I installed the Ionic templates, but they don't appear as an option in Visual Studio

Did you close, then restart Visual Studio? After the installation, the templates aren't available until Visual Studio restarts.

Did you receive an error message indicating that some node modules won't restore?

The installed version of Node.js may not support Promises. To fix this, you must install the latest Visual Studio 2015 update (Update 3 or later) and Microsoft ASP.NET and Web Tools. Check for any notifications or warning icons in the upper-right corner of the Visual Studio IDE and install these components, if instructed.

Update your credentials

If you are unable to install the Web Tools:

  1. Download the latest version of the Web Tools here.

  2. Run the following command from the folder containing the download:

    DotNetCore.1.0.0-VS2015Tools.Preview2.exe SKIP_VSU_CHECK=1
    
  3. Create a new Ionic 2 project from the templates that you downloaded previously.

Did you receive an error message indicating that some node modules won't restore?

If you can't restore some of the Node.js modules, such as ionic-angular, make sure that you have installed the Update 3 or later of Visual Studio and Microsoft ASP.NET and Web Tools (for more instructions, see the previous issue).

If that doesn't resolve the issue, open a command line and go to the project folder, then run this command:

npm install

and then this command:

npm rebuild node-sass

Then create a new Ionic 2 project from the templates you downloaded previously.

Did you receive a WWAHost runtime error?

When debugging on a Windows 8.1 dev machine, you may get a WWAHost runtime error when navigating between pages in Ionic apps. You can work around this by:

  • Closing DOM Explorer before navigating pages, or

  • Upgrading to Windows 10 on your dev machine (the platform issue is fixed in Windows 10).

Did you receive an error message asking you to install a new app?

When you are using the AngularJS routing module (Ionic templates use this module) on Windows, you may need to include a call to aHrefSanitizationWhitelist. This will correct errors that occur when loading partial pages.

If you see the dialog box shown here, you have likely run into this issue.

partial pages don't load

Typically, you include the code fix in app.js or wherever you are calling your module configuration code (inside angular.module.config):

$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|file|ghttps?|ms-appx|ms-appx-web|x-wmapp0):/);

$compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|file|ms-appx|ms-appx-web|x-wmapp0):|data:image\//);

If you use the preceding code, then add a reference to $compileProvider in the following line of code.

.config(function ($stateProvider, $urlRouterProvider) {

so that it looks like this:

.config(function ($compileProvider, $stateProvider, $urlRouterProvider) {

Did you receive an 'Unhandled exception' running on Windows?

If you see the following unhandled exception when targeting Win/WinPhone 8.1, follow the earlier steps to call platformOverrides.js to fix this issue.

unhandled exception

If you see the same error when targeting Windows 10, make sure you set Windows 10.0 as the target in the Windows tab of the configuration designer.

Did you receive an error messaging indicating that the appxrecipe file is missing?

If you see this error when targeting Windows 10, make sure you set Windows 10.0 as the target in the Windows tab of the configuration editor (config.xml). Then delete the platforms\windows folder and rebuild the project.

Did you receive an error message saying that the Content Security Policy is missing?

Cordova projects automatically add the Whitelist plugin to every project, so you must add the following meta element to the project's index.html file:

<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *">

Did you receive a certificate error on Windows?

Make sure your user credentials in Visual Studio are up to date. Check for any notifications or warning icons in the upper-right corner of the Visual Studio IDE:

Update your credentials

You may need to re-enter your credentials. If the notifications indicate that you need to update Cordova tooling, please click on the notifications and follow the provided instructions.

Visual Studio Emulator for Android won't run?

The VS Emulator for Android requires Hyper-V and is not supported when running on a VM. For more info, see this information.

If you have previously run the VS Emulator for Android successfully but now the emulator won't run, try deleting the emulator VM instance in the Hyper-V Manager. For more info, see Troubleshooting.

Having trouble hitting breakpoints in your .ts files?

There is a known issue debugging Ionic 2 in VSCode. Otherwise, this indicates a problem with your sourcemaps. When running your app, look for your .ts files under Script Documents in Solution Explorer. They should look similar to the illustration below. You can right-click on the .ts file and choose Properties to view the current path used by the sourceMaps.

SourceMaps

Did you find this article helpful?

Thanks!

We're sorry to hear that! Feel free to email our team with your question.


Want FASTER answers? Our team monitors a special StackOverflow tag here.