title | author | ms.prod | ms.date | ms.author |
---|---|---|---|---|
Getting started with Ionic 2 apps in Visual Studio |
jmatthiesen |
visual-studio-dev14 |
01/16/2017 |
jomatthi |
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:
To manage, code, run and debug Ionic 2 applications using Visual Studio, you must install the following:
- Visual Studio 2015
- Visual Studio Tools For Apache Cordova
- Visual Studio Ionic 2 Templates (described below)
- Ionic Template dependencies (described below)
-
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.
-
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.
The template files will download, then Visual Studio will automatically launch the installation process. When prompted, click the Install button to begin the installation.
Note: The Ionic templates may not appear in Visual Studio until it's restarted. Close, then re-open Visual Studio before continuing.
-
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.
-
Check the new Ionic project's readme file for any additional tools that must be installed to use the template.
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.
-
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.
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.
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:
-
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 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:
-
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.
-
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.
-
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.
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.
-
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: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.
-
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.
Press the keyboard's Ctrl-S key to save your changes, then close the editor file.
-
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>
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.
-
If you haven't already, install the Node JavaScript runtime from nodejs.org. You'll want the latest LTS version.
-
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.
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.
-
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 calledweather.ts
. -
Now that you have the
Weather
provider, you have to tell Ionic about it. Open the project'ssrc\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';
-
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.
In this section, we'll add code the Weather Provider to connect with the external weather API and deliver data to the application.
-
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.
-
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';
-
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. -
After the
weather
class 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 usesmakeDataURL
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 thegetCurrent
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.
-
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
andsrc\app\app.html
files. At start up, the Ionic framework replaces the contents of the project'ssrc\index.html
file's<ion-app></ion-app>
tag with thesrc\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 thesrc\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 thec_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 toc_item
, then the template code that follows displays thename
andvalue
properties of thec_item
object. You'll see how thec_items
array is populated soon. -
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';
-
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.
-
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> = [];
-
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 ) { }
-
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 thegetLocalWeather
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 callsshowCurrent
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:
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.
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.
-
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'ssearchInput
variable which you will see in a second. -
Open the project's
src\pages\home\home.ts
file and add the following variable declaration to the top of theHomePage
class (before theconstructor
)://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.
-
Finally, still in the project's
src\pages\home\home.ts
file, add the following function to theHomePage
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. TheKeyboard.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.
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.
-
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 theWeather
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
withforecast
in the API URL, otherwise the code is exactly the same. -
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
andforecast
. Next, the<div>
with the[ngSwitch]="displayMode"
attribute switches in separate<ion-list>
components based on the value assigned to the page'sdisplayMode
variable. -
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 theHomePage
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'];
-
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
tocurrent
. Add the code to theresume
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(); });
-
Now that the page displays two segments, the
refreshPage
function needs to know which part of the page to refresh. Replace the currentrefreshPage
function with the following code:refreshPage() { //Which page are we looking at now? if (this.displayMode === this.currentMode) { this.showCurrent(); } else { this.showForecast(); } }
-
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 tothis.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(); }
-
Finally, add the
showForecast
function to theHomePage
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:
Tap the forecast tab, and you'll see the list of forecast periods shown in the following figure:
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.
-
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 ofweather-detail
folders containing multiple files:weather-detail.ts
,weather-detail.html
, andweather-detail.scss
. -
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 followingimport
statement to the top of the file:import { WeatherDetailPage } from '../pages/weather-detail/weather-detail';
-
Next, add a reference to the
WeatherDetailPage
component to thedeclarations
andentryComponents
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 { }
-
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
andvalue
values. -
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 theforecast
object sent through theNavParams
component
-
-
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'ssrc\pages\home\home.ts
and add the followingimport
statement to the top of the file:import { WeatherDetailPage } from '../weather-detail/weather-detail';
-
Still in
src\pages\home\home.ts
, add theviewForecast
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:
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.
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?
Did you close, then restart Visual Studio? After the installation, the templates aren't available until Visual Studio restarts.
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.
If you are unable to install the Web Tools:
-
Run the following command from the folder containing the download:
DotNetCore.1.0.0-VS2015Tools.Preview2.exe SKIP_VSU_CHECK=1
-
Create a new Ionic 2 project from the templates that you downloaded previously.
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.
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).
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.
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) {
If you see the following unhandled exception when targeting Win/WinPhone 8.1, follow the steps to call platformOverrides.js to fix this issue.
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.
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.
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 *">
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:
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.
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.
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.