Welcome to the NativeScript & Angular 2 getting started guide 📚. In this hands-on tutorial, you’ll build a cross-platform iOS and Android app from scratch.
IMPORTANT: NativeScript’s Angular integration is in beta. Refer to this guide’s appendix for a list of known issues that may affect you while going through this tutorial.
NativeScript is a framework for building native iOS and Android apps using JavaScript and CSS. NativeScript renders UIs with the native platform’s rendering engine—no WebViews—resulting in native-like performance and UX.
Angular JS is one of the most popular open source JavaScript frameworks for application development. The latest version of Angular, Angular 2, makes it possible to use Angular outside of a web browser, and developers at Telerik—the company that created and maintains NativeScript—have been working closely with developers at Google to make Angular 2 in NativeScript a reality.
The result is a software architecture that allows you to build mobile apps using the same framework—and in some cases the same code—that you use to build Angular 2 web apps, with the performance you’d expect from native code. Let’s look at how it all works by building an app.
This guide will walk you through building Groceries, a groceries management app that does the following things:
If you follow along to the end, here's what the finished app will look like on iOS:
And here's what the app will look like on Android:
This guide assumes that you have some basic knowledge of JavaScript, CSS, and your development machine’s terminal. More specifically:
This guide will not assume you have any knowledge of Angular 2 or TypeScript. When background Angular 2 or TypeScript expertise will help you understand a concept, this guide will link you to the appropriate places in the Angular or TypeScript documentation.
With that out of the way, let’s get started!
In this chapter you're going to start with the basics, including installing the NativeScript CLI, starting a new project, and getting your first app up and running.
The NativeScript CLI has a few system requirements you must have in place before building NativeScript apps. As a first step, start by going through the instructions for your operating system:
TIP:
- Completing the installation instructions can be tricky if you’re new to mobile development. If you get stuck, or you have questions at any point while going through this guide, the NativeScript Community Slack channel is a great place to ask questions.
- If you’re overwhelmed by these requirements, you may also be interested in using NativeScript as part of the Telerik Platform. The Telerik Platform provides robust tooling for NativeScript apps, including an IDE that performs iOS and Android builds in the cloud, removing the need to complete these system requirements.
After completing the setup you should have two commands available from your terminal: tns
—which is short for Telerik NativeScript—and nativescript
. The two commands are equivalent, so we'll stick with the shorter tns
.
You can verify the installation was successful by running tns
in your terminal. You should see something like this:
$ tns
# NativeScript
┌─────────┬─────────────────────────────────────────────────────────────────────┐
│ Usage │ Synopsis │
│ General │ $ tns <Command> [Command Parameters] [--command <Options>] │
│ Alias │ $ nativescript <Command> [Command Parameters] [--command <Options>] │
└─────────┴─────────────────────────────────────────────────────────────────────┘
With the NativeScript CLI installed, it's time to start building your app. Normally, you would use the tns create
command to create an empty NativeScript application. For this guide however, we've scaffolded out a boilerplate project to act as a starting point for Groceries.
Navigate to a folder where you want to keep your app code:
cd the-folder-you-want-groceries-to-be-in
Next, assuming you have git installed, clone the Groceries repo from GitHub:
git clone https://github.com/NativeScript/sample-Groceries.git
After that, change to the newly cloned repo's folder:
cd sample-Groceries
Finally, switch to the “angular-start” branch for this guide's starting point:
git checkout angular-start
TIP:
- The “angular-end” branch has the final state of this guide's tutorial. Feel free to refer to the branch on GitHub if you get stuck.
- After completing this guide, if you’d like to start a new NativeScript and Angular 2 app—one that doesn’t clone Groceries—you can use the NativeScript CLI’s
tns create
command as such:tns create my-app-name --template tns-template-hello-world-ng
Your app is now set up, but before you run it, you need to initialize a platform-specific native project for each platform you intend to target.
If you're on a Mac, start by adding the iOS platform:
tns platform add ios
Next, add the Android platform with the same platform add
command:
tns platform add android
IMPORTANT: You can add platforms only for SDKs that you already have installed on your development machine. If you get errors running
tns platform add
, refer back to the section on setting up your development environment.
The platform add
command adds a folder called platforms
to your project, and copies all of the required native SDKs into this folder. When you build the application, the NativeScript CLI will copy your application code into the platforms
folder so that a native binary can be created.
With the platform initialization complete, you can run your app in an emulator or on devices.
If you're on a Mac, start by running the app in an iOS simulator with the following command:
tns run ios --emulator
If all went well, you should see something like this:
Next, run your app on an Android emulator with the following command:
tns run android --emulator
WARNING:
- You must have at least one Android AVD (Android Virtual Device) configured for this command to work. If you get an error, try setting up an AVD and then run the command again.
- If you're using Genymotion, launch your Genymotion virtual device, and then run
tns run android
.
If all went well, you should see your app running in an Android emulator:
Here are a few other tips for running NativeScript apps.
TIP:
- To run on a USB-connected Android or iOS device, use the same
run
command without the--emulator
flag—i.e.tns run android
andtns run ios
.- The
tns device
command lists all USB-connected iOS devices, USB-connected Android devices, and Genymotion virtual devices thattns run
can deploy to. Note thattns device
does not list iOS simulators.
At this point, you have the NativeScript CLI downloaded and installed, as well as the iOS and Android dependencies that you need to run your app. Now you need a good workflow that lets you make changes and see results fast. For that we’ll use the tns livesync
command.

If you have an Android emulator running, start an Android livesync watcher by executing the following command:
tns livesync android --emulator --watch
The tns livesync
command updates your app by transferring the updated source code to the device or simulator. By adding the --watch
flag, the livesync
command additionally watches the files in your NativeScript project. Whenever one of those files changes, the command detects the update, and patches your app with the updated code.
TIP: You can learn about how this is possible by reading more about how NativeScript works.
To see livesync in action let’s make a small update to your app. Open your project’s app/app.component.ts
file in your text editor of choice and change
to
. Save the file and you should see the app relaunch and the updated text displayed.
If you’re on a Mac building for iOS, the workflow is currently a bit different, as the tns livesync ios
command is not yet supported. (See the known issues for details.) To see the updated text, type Ctrl+C
to kill your previous tns run ios
command, and then re-execute tns run ios --emulator
to launch the app with your changes.
Regardless of whether you’re running on iOS or Android, or whether you’re using tns livesync
or tns run
, the NativeScript CLI shows the output of console.log()
statements as your app executes, as well as stack traces when things go wrong. So if your app crashes at any time during this guide, look to the terminal for a detailed report of the problem.
The iOS and Android logs can be a bit noisy, so you might have to scroll up a bit to find the actual problem. For example if I try to call foo.bar()
when foo
does not exist, here's the information I get on iOS:
/app/path/to/file.js:14:8: JS ERROR ReferenceError: Can't find variable: foo
1 0xe3dc0 NativeScript::FFICallback<NativeScript::ObjCMethodCallback>::ffiClosureCallback(ffi_cif*, void*, void**, void*)
And here's the same information in the Android logs:
E/TNS.Native( 2063): ReferenceError: foo is not defined
E/TNS.Native( 2063): File: "/data/data/org.nativescript.groceries/files/app/./views/login/login.js, line: 13, column: 4
TIP: When you're trying to debug a problem, try adding
console.log()
statements in your JavaScript code—exactly as you would in a browser-based application.WARNING: Not all changes can be livesync’d in a NativeScript app. For instance, livesync cannot patch native configuration file changes (
Info.plist
,AndroidManifest.xml
, and so forth), new plugin installations, and any other change that requires a full compilation of the application. In those cases, you’ll want to useCtrl+C
to stop livesync, and rerun the application using thetns run android
commands. Not to worry though, when situations that require a full compilation come up in this guide, these instructions will be explicitly listed.
Now that you've created an app, configured your environment, and set up your app to run on iOS and Android, you're ready to start digging into the files that make up a NativeScript app.
Before you start coding the Groceries app it's important to understand a NativeScript app's folder structure. It'll help you understand where to place new files, as well as a bit of what's going on with NativeScript under the hood.
Go ahead and open your app's sample-Groceries
folder in your text editor of choice and let's dig in.
To keep things simple, let's start by looking at the outer structure of the Groceries app:
.
└── sample-Groceries
├── app
│ └── ...
├── hooks
│ └── ...
├── node_modules
│ ├── angular2
│ ├── nativescript-angular
│ ├── tns-core-modules
│ └── ...
├── platforms
│ ├── android
│ └── ios
├── package.json
├── references.d.ts
└── tsconfig.json
Here's what these various files and folders do:
camera.takePicture()
). We'll look at some examples in chapter 4. The source code for these modules lives at https://github.com/NativeScript/nativescript.android
folder you'll find things like your project's AndroidManifest.xml
and .apk executable files. Similarly, the ios
folder contains the Groceries' Xcode project and .ipa executables. Note, users on Windows and Linux machines will not have an ios
folder."experimentalDecorators"
and "emitDecoratorMetadata"
flags are essential to making NativeScript and Angular 2 work, so don’t remove those. You can refer to the official TypeScript wiki for detailed documentation on what you can do in a tsconfig.json
file.The NativeScript CLI manages the platforms
folder for you as you develop and run your app; therefore, it's a best practice to treat the platforms
folder as generated code. The Groceries app includes the platforms
folder in its .gitignore
to exclude its files from source control.
Next, let's dig into the app
folder, as that's where you'll be spending the majority of your time.
.
└── sample-Groceries
├── app
│ ├── App_Resources
│ │ ├── Android
│ │ └── iOS
│ ├── pages
│ │ ├── login
│ │ │ ├── login.html
│ │ │ └── ...
│ │ └── ...
│ ├── shared
│ │ └── ...
│ ├── utils
│ │ └── ...
│ ├── app.css
│ ├── app.component.ts
│ ├── main.ts
│ └── ...
└── ...
Here's what these various files and folders do:
platforms
folder when you execute tns run
.config.ts
file used to share configuration variables like API keys. We’ll discuss the shared
folder, as well as code sharing between native apps and web apps, in detail in section 3.2.To get a sense of a NativeScript app actually starts up, let’s explore the first few files.
The first few files you run in a NativeScript app look almost identical to the first few files you run in an Angular 2 web app. Let’s start with main.ts
as that’s the first file executed. Open your app.main.ts
file; you should see the code below:
import {nativeScriptBootstrap} from "nativescript-angular/application";
import {AppComponent} from "./app.component";
nativeScriptBootstrap(AppComponent);
Here you’re using the TypeScript import
command to bring in a function—nativescriptBootstrap()
—and a TypeScript class—AppComponent
—each of which are defined in separate files. The nativescriptBootstrap()
function comes from the “nativescript-angular” npm module, which you may recall contains the code needed to integrate NativeScript and Angular 2. Whereas Angular 2’s own bootstrap()
function starts an Angular 2 browser app, NativeScript’s bootstrap function starts an Angular 2 native app.
TIP: If you’re curious about what
nativescriptBootstrap()
function actually has to do to startup native iOS and Android apps, remember that all this code is open source for you to explore at any time. ThenativescriptBootstrap()
function specifically is defined in anapplication.ts
file in the NativeScript/nativescript-angular repository on GitHub.
The bootstrap function, regardless of whether it’s for the web or for native apps, needs to know which Angular component to start the application with. In this case, you’re passing control to a AppComponent
component defined in app.component.ts
.
TIP: In NativeScript we follow Angular 2’s own convention of naming component files with a
.component.ts
suffix.
Next, open your app’s app/app.component.ts
file; you should see the code below:
import {Component} from "angular2/core";
@Component({
selector: "my-app",
template: ""
})
export class AppComponent {}
This file contains an Angular 2 component, which is the primary building block of Angular 2 applications, including NativeScript apps. Let’s break down what’s going on in this file.
First, you again use TypeScript’s import
command to bring in externally defined functionality—in this case, the Component
class from Angular 2 itself. In Angular 2 a component manages a view, or a piece of the user interface that the user sees. A component can be used to define an individual UI element, or an entire page, and eventually we’ll add a bunch of logic to these components and use them to build an entire app. But for now this component is simple for the purpose of demonstration.
TIP: Why TypeScript? It's strongly recommended that you use TypeScript in your Angular 2 NativeScript app, as it's a first class citizen in both NativeScript and Angular 2. In fact, both NativeScript and Angular 2 were built with TypeScript. The NativeScript CLI makes compiling your TypeScript files seamless, as each time you livesync, run or build the app, the files are recompiled from TypeScript to JavaScript. In some IDEs, such as Visual Studio Code, you might choose to hide the compiled files. A tutorial on how to do this can be found here.
Notice the interesting way that the Component
class is used—with the syntax @Component
. This is a TypeScript decorator, which allows you to annotate a TypeScript class or method with additional information. For now, you can think of it as a way of adding some metadata configuration to the currently empty AppComponent
class. Specifically, the @Component
decorator’s template
property tells NativeScript how to render this component on the screen. In fact, the
syntax is why you saw “hello NativeScript” when you ran this app earlier.
However, this syntax may look a bit odd if you come from a web development background. On the web, the
HTML element doesn’t have a text
attribute, so why do we see it here? Let’s dive into this by looking at how NativeScript UI elements work.
NOTE: Curious about the
@Component
decorator’sselector
property? The property defines how a component can be used within another component’s template. For instance a component that defines itsselector
withselector: "foo-bar"
can be used by another component astemplate: "<foo-bar></foo-bar>"
. NativeScript is smart enough to use your first Angular 2 component automatically; therefore, theselector
property of this first component is irrelevant. We’ll use theselector
property later in this guide though.
The primary difference between building an Angular 2 app for the web and an Angular 2 app with NativeScript is in the UI elements that you use. NativeScript apps do not use a browser and do not have a DOM; therefore, elements like

and
simply do not work.
No worries though, as NativeScript provides an extensive suite of UI elements, each of which are implemented with native iOS and Android controls. For instance, the
control our previous example used is actually rendered as a UILabel
on iOS and an android.widget.TextView
on Android. The great thing about using NativeScript though, is that this native details are transparent to use as a developer. You type
and let NativeScript handle the rendering details.
Let’s return back to building Groceries. The first screen of Groceries is intended to be a login screen, so let’s replace the current
with something that resembles a typical login screen in a mobile app.
app.component.ts
Open app/app.component.ts
and replace the existing @Component
with the following code:
@Component({
selector: "my-app",
template: `
<TextField hint="Email Address" id="email" keyboardType="email"
autocorrect="false" autocapitalizationType="none"></TextField>
<TextField hint="Password" id="password" secure="true"></TextField>
<Button text="Sign in"></Button>
<Button text="Sign up for Groceries"></Button>
`
})
NOTE: Notice the back-tick character (`) used with the
template
property. This character is used to define an ES2015 template literal, which TypeScript supports, and which allows you to write multi-line strings without using messy string concatenation.
This code adds two new NativeScript UI elements: a text field and a button. Much like HTML elements, NativeScript UI elements provide attributes to let you configure their behavior and appearance. The code you just added uses the following attributes:
<TextField>
hint
: Shows placeholder text that tells the user what to type.keyboardType
: The type of keyboard to present to the user for input. keyboardType="email"
shows a keyboard optimized for entering email addresses. NativeScript currently supports five types of keyboards for text fields.autocorrect
: A boolean attribute that determines whether the mobile operating system should autocorrect user input. In the case of email address text fields, the autocorrect behavior is undesirable.autocapitalizationType
: Determines how the operating system should autocapitalize user input. autocapitalizationType="none"
turns autocapitalization off altogether. NativeScript supports four autocapitalization types on text fields.secure
: A boolean attribute that determines whether the text field’s text should be masked, which is commonly done on password fields.<Button>
text
: Controls the text displayed within the button.After your app updates with this change, you may expect to see a polished login screen, but instead you will see a single <Button>
element on the screen:
What went wrong? In NativeScript whenever you use more than one UI element, you need to tell NativeScript how to arrange those elements on the screen. Since you’re not doing that currently, NativeScript is incorrectly assuming you want the last element—the <Button>
—to take up the whole screen. To arrange these elements, let’s move onto the NativeScript feature for aligning elements on the screen: NativeScript layouts.
TIP: The NativeScript docs include a full list of the UI components and attributes with which you can build your apps. You can even build your own, custom UI components.
NativeScript provides several different layout containers that allow you to place UI elements precisely where you want them to appear.

in HTML markup.For your login screen, all you need is a simple <StackLayout>
for stacking the UI elements on top of each other. In later sections, you'll use some of the more advanced layouts.
Open app/app.component.ts
, and add a <StackLayout>
element within your component’s template
property. The full component should now look like this:
@Component({
selector: "my-app",
template: `
<StackLayout>
<TextField hint="Email Address" id="email" keyboardType="email"
autocorrect="false" autocapitalizationType="none"></TextField>
<TextField hint="Password" id="password" secure="true"></TextField>
<Button text="Sign in"></Button>
<Button text="Sign up for Groceries"></Button>
</StackLayout>
`
})
The stack layout is a UI element, and as such, it has attributes just like the <TextField>
and <Button>
elements you used in the previous section. After your app updates with this change, you'll see that your login page’s UI elements stack up:
Although the UI elements are in the correct order, they could use some spacing and color to make the app look a bit nicer. To do that let's look at another NativeScript feature: CSS.
TIP:
- Refer to the NativeScript docs for a more detailed look at how NativeScript layouts work and the various things you can do to configure them.
- Check out Jen Looper's article on demystifying NativeScript layouts for a more thorough look at NativeScript layouts in action.
NativeScript uses a subset of CSS to change the visual appearance of your app. You can use three mechanisms to add CSS properties to UI components: application-wide CSS, component-specific CSS, and an inline style
attribute. In this section we’ll cover application-wide, or global CSS, and in the next section we’ll look at how to apply CSS rules to individual components.
TIP: Although inline styles are great for quick testing—e.g.
<StackLayout style="background-color: green;">
—you should avoid them in general because thestyle
attributes tend to clutter up your templates, especially if you need to apply multiple rules.
Open your app/app.css
file and paste in the following code:
Page {
background-color: white;
font-size: 15;
}
TextField {
padding: 10;
font-size: 13;
}
If you've done any web development before, the syntax should feel familiar here. You select two UI components by their tag names (Page and TextField), and then apply a handful of CSS rules as name/value pairs. NativeScript does not support all CSS properties because it is not possible to replicate some of them in native apps without causing performance issues. A full list of the CSS properties that are supported are listed in the NativeScript docs.
NOTE: In NativeScript, a single
<Page>
UI element wraps thetemplate
of every page-level Angular component, which you’ll learn about when we introduce routing later in this guide. For now, just know that a<Page>
exists as the parent of yourAppComponent
’s template, and that you can target that<Page>
with CSS as you would with any other UI element.
Although often you want CSS rules to apply equally to your iOS and Android app, occasionally it makes sense to apply a CSS rule to only one platform. For example, iOS text fields frequently have borders around them, but Android text fields do not. Let's look at how to make platform-specific style changes in NativeScript.
Add the following as the first line of your app's app/app.css
file:
@import url("~/platform.css");
WARNING: NativeScript is consistent with browser implementations in that
@import
statements must precede all other CSS rules in a file.
Next, open your app’s app/platform.ios.css
file and paste in the following code:
TextField {
border-width: 1;
border-color: black;
}
NOTE: We’ll leave the
platform.android.css
file empty as we have no Android-specific changes to make yet.
NativeScript supports CSS's @import
statement for importing one CSS file into another. So this new line of code imports the CSS rules from platform.css
into app.css
. But, you might have noticed that Groceries does not have a file named platform.css
—only app/platform.android.css
and app/platform.ios.css
exist. What's going on here?
When you execute tns run
, or tns livesync
, the NativeScript CLI takes the code from your app
folder and places it in the native projects located in the platforms/ios
and platforms/android
folders. Here the naming convention comes in: while moving files, the CLI intelligently selects .android.*
and .ios.*
files. To give a specific example, the CLI moves platform.ios.css
into platforms/ios
and renames it to platform.css
; similarly, the CLI moves platform.android.css
into platforms/android
, and again renames it to platform.css
. This convention provides a convenient way to branch your code to handle iOS and Android separately, and it's supported for any type of file in NativeScript—not just CSS files. You'll see a few more examples of this convention later in this guide.
With these changes in place, you'll notice that the app has a bit more spacing, and also that the text fields have borders on iOS but that Android:
Despite our changes the app still looks pretty ugly, and that’s because we’re going to apply another batch of styles at the component level. Let’s look at how that works.
Much like on the web, sometimes in your NativeScript apps you want to write CSS rules that apply to your entire application, and sometimes you want to write CSS rules that apply to a specific portion of the interface. In the previous section you saw how to use NativeScript’s app.css
file to write global rules, and in this section you’ll learn how to use a component’s styleUrls
property to apply rules that are scoped to individual components.
Open your app/app.component.ts
file and add a styleUrls
property, so that that full @Component
declaration now looks like this:
@Component({
selector: "my-app",
template: `
<StackLayout>
<TextField hint="Email Address" id="email" keyboardType="email"
autocorrect="false" autocapitalizationType="none"></TextField>
<TextField hint="Password" id="password" secure="true"></TextField>
<Button text="Sign in"></Button>
<Button text="Sign up for Groceries"></Button>
</StackLayout>
`,
styleUrls: ["pages/login/login-common.css", "pages/login/login.css"]
})
Next, open your app’s app/pages/login/login-common.css
file and paste in the following code:
StackLayout {
margin-left: 30;
margin-right: 30;
padding-bottom: 15;
background-color: white;
}
Image {
margin-top: 5;
margin-bottom: 20;
}
Button, TextField {
margin-left: 16;
margin-right: 16;
margin-bottom: 10;
}
#submit-button {
background-color: #CB1D00;
color: white;
margin-top: 20;
}
In Angular 2, styleUrls
points at an array of stylesheets that should be used to style a component. In this case, you’re telling Angular to use two stylesheets, login-common.css
, and login.css
, which is actually implemented as login.ios.css
and login.android.css
using the same naming convention we introduced in the previous section.
Why three files? Much like you divided your global files into app.css
, platform.ios.css
, and platform.android.css
, this structure gives you a similar ability to place common login styling in login-common.css
, iOS-specific login styling login.ios.css
, and Android-specific login styling in login.android.css
.
The great thing about placing CSS rules at the component level is you can use concise CSS selectors such as Button
and TextField
, and not worry about those rules applying to all buttons and text fields in your application, as Angular 2 ensures those rules remain scoped to your component.
Before we see what your app looks like now, there’s one small change you need to make. Notice that the last selector used in login-common.css
is #submit-button
. Much like using CSS on the web, in NativeScript you can both id
and class
attributes to target specific user interface element, but at the moment there’s no UI element in your app with an id
of "submit-button"
. Let’s change that.
id
attribute
Open your app/app.component.ts
file, find <Button text="Sign in"></Button>
in your component’s template
, and replace it with the code below:
<Button text="Sign in" id="submit-button"></Button>
With this last id
change in place your app is starting to look a little nicer:
As you can see, in NativeScript you have a lot of options for how you can apply CSS rules. You can apply rules globally either for both platforms in app.css
, for iOS in platform.ios.css
, or for Android in platform.android.css
. And you can also apply rules at the component level, while maintaining the same flexibility to target different platforms if required.
To continue polishing the visuals of this login screen, let’s look at how we can add an image of this app’s logo.
In NativeScript you use the <Image>
UI element and its src
attribute to add images to your pages. The src
attribute lets you specify your image in three ways. The first (and simplest) way is to point at the URL of an image:
<Image src="https://www.nativescript.org/images/default-source/landingpages/logo.png"></Image>
The second way is to point at an image that lives within your app's app
folder. For example if you have an image at app/images/logo.png
, you can use it with:
<Image src="~/images/logo.png"></Image>
The third way, and the one Groceries uses, is to use platform-specific image resources. Let's add an image to the login screen and then discuss exactly what's happening.
In app.component.ts
, add the <Image>
below as the first child of the existing <StackLayout>
tag:
<Image src="res://logo_login" stretch="none" horizontalAlignment="center"></Image>
The res://
syntax tells NativeScript to use a platform-specific resource, in this case an image. Platform-specific resources go in your app's app/App_Resources
folder. If you look there you'll find a few different image files, several of which are named logo_login.png
.
Although more complex than putting an image directly in the app
folder, using platform-specific images gives you more control over image display on different device dimensions. For example iOS lets you provide three different image files for devices with different pixel densities. As such you'll find logos named logo_login.png
<EMAIL_ADDRESS>, and<EMAIL_ADDRESS>in your App_Resources/iOS
folder. For Android you'll find similar image files in App_Resources/Android/drawable-hdpi
(for "high" dpi, or high dots-per-inch), App_Resources/Android/drawable-mdpi
(for medium-dpi), and App_Resources/Android/drawable-ldpi
(for low-dpi).
Once these files are in place the NativeScript framework knows how to pick the correct file; all you have to do is reference the image using res://
and its base file name—i.e. res://logo_login
. Here's what your login screen should look like on iOS and Android:
At this point your UI looks better visually, but the app still doesn't actually do anything. Let's look at how you can use JavaScript to add some functionality.
TIP: The community-written NativeScript Image Builder can help you generate images in the appropriate resolutions for iOS and Android.
In this chapter you’ll learn how to add JavaScript logic to your app, how to create services that talk to backend endpoints, and how to architect an app that uses multiple pages. There’s a lot to cover, so let’s start by discussing how to handle events and data binding.
Most user interfaces are driven by events. In NativeScript apps, those events are usually some user action, such as tapping, swiping, or rotating—and NativeScript abstracts the iOS- and Android-specific code for handling such events into a handful of easy-to-use APIs. Let’s start with the most common event you’ll use in a NativeScript app: tap
.
tap
event handler
Open app/app.component.ts
, find the existing sign in button within your component’s template
(<Button text="Sign in"></Button>
), and replace it with the following code:
<Button text="Sign in" id="submit-button" (tap)="submit()"></Button>
Next, in the same file, replace the current AppComponent
declaration with the one shown below:
export class AppComponent {
submit() {
console.log("hello");
}
}
The (eventName)="functionName()"
syntax is part of Angular 2’s event binding system, which lets you bind an event that occurs on a UI element to a function in your component’s class. In this case, the (tap)="submit()"
syntax tells Angular to run the AppComponent
class’s submit()
function whenever the user taps the sign in button.
To verify this binding works tap the “Sign In” button in your app; you should see “hello” logged in your terminal or command prompt as such:
TIP:
- In NativeScript you can find a list of events available in the appropriate UI element’s API documentation. For instance, the button element’s API documentation lists its
tap
event.- The Angular 2 docs have a helpful cheat sheet that includes the various syntaxes available when building templates. Don’t worry too much about knowing how all these work at the moment; we’ll progressively introduce the most common syntaxes in this guide.
With the tap
event in place, you now have a way of tying the UI elements in your template to your TypeScript code. To make a login page actually work though, we need to introduce one other way of connecting a template to code: data binding.
Angular 2 provides several ways to bind data in your TypeScript code to UI controls, and through the magic of NativeScript those same methods are available in your iOS and Android apps.
The first of these is a way to bind UI attributes to properties defined in your TypeScript class. Let’s look at how it works.
In app/app.component.ts
replace the current AppComponent
declaration with the one shown below, which adds a new email
property, and changes the submit()
method to display its value:
export class AppComponent {
email =<EMAIL_ADDRESS>submit() {
alert("You’re using: " + this.email);
}
}
Next, find the first <TextField>
in your component’s template
and replace it with the code below, which adds a new text
attribute:
<TextField hint="Email Address" id="email" keyboardType="email" [text]="email"
autocorrect="false" autocapitalizationType="none"></TextField>
The key thing to note here is the [text]="email"
attribute on the <TextField>
. This is Angular 2’s syntax for attribute binding, and it allows you to bind the value of an attribute to a property in your TypeScript class. In this case, the text
attribute of the <TextField>
—which is roughly equivalent to a DOM <input>
’s value
attribute—is bound to the AppComponent
’s email
attribute. Therefore the app now has an email address prefilled when it loads:
NOTE: It’s very easy to confuse Angular 2’s event binding syntax
(eventName)="functionName()"
with its attribute binding syntax[attributeName]="propertyName"
. Don’t worry though; if you get them backwards, the problem is usually easy to debug, as the functionality you’re attempting to add just won’t work.
This attribute binding approach works really well when you need the data binding to be one way—that is, when you need TypeScript properties, and changes to those properties done in TypeScript code, to appear in the user interface. But in the case of user interface controls that accept user input, such as the text field in this example, usually you need data binding to work two way—that is, you additionally need changes the user makes to data via form controls to be reflected in your TypeScript code.
To show that the current example’s data binding is only one way, head back to your app, change the email text field’s value (type a few extra letters or something like that), and then tap the “Sign In” button. Because your submit()
function alerts the current value of your component’s email
property—alert("You’re using: " + this.email)
—you might expect to see the updated value in the alert. Instead, however, you see the original value. Notice how the typed text and the alert value don’t match in the screenshot below.
To fix this, you need to switch to Angular 2’s two-way data binding syntax.
In app/app.component.ts
, find the find the first <TextField>
, and replace it with the <TextField>
below, which introduces a new [(ngModel)]
attribute:
<TextField hint="Email Address" id="email" keyboardType="email" [(ngModel)]="email"
autocorrect="false" autocapitalizationType="none"></TextField>
At first glance the [(ngModel)]
syntax looks more than a little odd, as it’s essentially a combination of the event and attribute binding syntax that you used in earlier examples. In the case of this example, [(ngModel)]="email"
is shorthand for [text]="email" (emailChange)="email=$event"
, which binds the email element’s text
attribute to an email
property and adds a change
event handler that updates the email
property’s value whenever the user makes a change.
Don’t worry too much about the details here while we’re still getting started. In your head you can think of [(ngModel)]
as the way to implement two-way data binding when you need it on form controls. To show that it works, if you again modify your app’s email address and click the “Sign In” button, you’ll see the updated value in the alert as expected:
Before we move on, let’s make one additional change to show what else you can do with Angular 2’s data binding APIs. The Groceries app uses the same UI for the “Sign In” form and the “Sign Up” form. Therefore, when the user clicks “Sign Up”, we need to update the text of the buttons (and eventually the functionality that occurs when you tap them). Let’s see how to make that happen.
Open app/app.component.ts
and replace the two existing buttons with the code below:
<Button [text]="isLoggingIn ? 'Sign in' : 'Sign up'" id="submit-button" (tap)="submit()"></Button>
<Button [text]="isLoggingIn ? 'Sign up' : 'Back to login'" (tap)="toggleDisplay()"></Button>
Next, replace the existing AppComponent
declaration with the code below, which adds a new isLoggingIn
property, and a new toggleDisplay()
method:
export class AppComponent {
email =<EMAIL_ADDRESS>isLoggingIn = true;
submit() {
alert("You’re using: " + this.email);
}
toggleDisplay() {
this.isLoggingIn = !this.isLoggingIn;
}
}
The previous examples switches each button’s’ text
attribute from a simple string—e.g. <Button text="Sign Up">
—to an attribute binding that is dependent on a value defined in the AppComponent
class—e.g. <Button [text]="isLoggingIn ? 'Sign in' : 'Sign up'">"
. Now, when the value of the isLoggingIn
attributes changes after the user clicks the bottom button, Angular is smart enough to update the text of the button automatically. The result looks like this:
At this point, you have a basic login screen setup with two-way data binding—which isn’t bad for 20 some lines of code of TypeScript. (Think about how much code you’d have to write in Android Studio and Xcode to accomplish the same task.) To this point though you’ve been placing all of your logic in a single TypeScript file, which doesn’t scale all that well for real-world applications.
Before we tie this app to a backend and make this login screen fully functional, let’s take a step back and setup a structure that can scale.
There are many reasons to segment any application into modular units, and you can read about the various benefits on Wikipedia. Modularizing NativeScript apps, in addition, has one unique benefit: the ability to share the code you write between Angular-2-built web apps, and Angular-2-built native apps.
Even if you have no plans to create an Angular 2 web app, separating out your code is still advantageous for a number of other reasons—testability, ease of maintenance, and so forth—but if you do have plans to build an Angular 2 web app, having a chunk of functionality that you can reuse in your native and web apps can be an invaluable time saver.
To see how this works in action, let’s edit some files in the shared
folder and set them up to be imported.
Let’s start by creating a simple model object to store user data. Open app/shared/user/user.ts
and paste in the following code:
export class User {
email: string;
password: string;
}
This code defines a simple TypeScript class that does nothing more than define two properties—email
and password
. Note the use of TypeScript’s export
keyword, as we’ll see why that’s important momentarily.
Next, open app/app.component.ts
, and add the following import
to the top of the file:
import {User} from "./shared/user/user";
Here you import the User
class that you just defined. Note the parallel between the export
command used in the previous example and the import
command used here. The reason the User
class is available to import is because it was explicitly exported. You’ll see other examples of import
and export
as you go through this guide.
Next, replace the existing AppComponent
definition with the one below, which uses the User
class you just imported.
export class AppComponent {
user: User;
isLoggingIn = true;
constructor() {
this.user = new User();
}
submit() {
alert("You’re using: " + this.user.email);
}
toggleDisplay() {
this.isLoggingIn = !this.isLoggingIn;
}
}
Instead of storing data on the AppComponent
directly, you’re now using the User
model object, which is reusable outside of this page and even outside of this application. You instantiate an instance of the User
class in a new constructor
function, which Angular 2 invokes when it bootstraps your application.
Your next step is to use this new model object in your template. To do that, replace the two existing <TextField>
s with the code shown below, which updates the [(ngModel)]
bindings to point at the new User
object:
<TextField hint="Email Address" id="email" keyboardType="email" [(ngModel)]="user.email"
autocorrect="false" autocapitalizationType="none"></TextField>
<TextField hint="Password" id="password" secure="true" [(ngModel)]="user.password"></TextField>
As one final change, because this template is getting to be a bit complex, let’s move it into a separate file. Open app/pages/login/login.html
and paste in the following code:
<StackLayout>
<Image src="res://logo_login" stretch="none" horizontalAlignment="center"></Image>
<TextField hint="Email Address" id="email" keyboardType="email" [(ngModel)]="user.email"
autocorrect="false" autocapitalizationType="none"></TextField>
<TextField hint="Password" id="password" secure="true" [(ngModel)]="user.password"></TextField>
<Button [text]="isLoggingIn ? 'Sign in' : 'Sign up'" id="submit-button" (tap)="submit()"></Button>
<Button [text]="isLoggingIn ? 'Sign up' : 'Back to login'" (tap)="toggleDisplay()"></Button>
</StackLayout>
Finally, in app/app.component.ts
, replace the existing template
property with the new templateUrl
property shown below:
templateUrl: "pages/login/login.html"
In case you got lost during this section, here’s a copy-and-paste friendly of the app/app.component.ts
file you should have at this point:
import {Component} from "angular2/core";
import {User} from "./shared/user/user";
@Component({
selector: "my-app",
templateUrl: "pages/login/login.html",
styleUrls: ["pages/login/login-common.css", "pages/login/login.css"]
})
export class AppComponent {
user: User;
isLoggingIn = true;
constructor() {
this.user = new User();
}
submit() {
alert("You’re using: " + this.user.email);
}
toggleDisplay() {
this.isLoggingIn = !this.isLoggingIn;
}
}
TIP: With Angular 2 components you have the ability to specify templates and CSS styling in two places—directly within the component, or in external files. For simple components feel free to choose either approach based on your personal preference, but once your templates/styles get to ~10 lines of code, consider using external files exclusively, as mixing non-trivial XML, CSS, and JavaScript code makes your component code less readable.
With this setup you now have a User
class that you can share across pages in your app and even across applications. But a model object that’s four simple lines of code isn’t all that exciting. Where this approach really pays off is when you’re able to share your business logic, and the code that hits your backend systems. In Angular 2 those classes are known as services. Let’s look at them next.
A login screen isn’t all that useful if it doesn’t actually log users into anything. Therefore, our next task is to allow users to create and log into accounts. We’ll build this functionality as an Angular 2 service, which is Angular 2’s mechanism for reusable classes that operate on data.
For the purposes of this tutorial we prebuilt a handful of backend endpoints using Telerik Backend Services, and we’ll be using those endpoints to make this app functional. Let’s see how they work.
NOTE: You don't have to use Telerik Backend Services to power your app’s backend; you can use any HTTP API in a NativeScript app. Telerik Backend Services is convenient for us to use for this tutorial because it lets us spin up HTTP endpoints quickly.
There’s are several new concepts to introduce with Angular services, so we’re going to start by stubbing out a new register()
method, and then come back to the implementation later in this section. With that in mind, open app/shared/user/user.service.ts
and paste in the following code:
import {Injectable} from "angular2/core";
import {User} from "./user";
@Injectable()
export class UserService {
register(user: User) {
alert("About to register: " + user.email);
}
}
This creates a basic Angular service with a single method that takes an instance of the User
object you created in the previous section. The one new thing is the @Injectable
decorator. This decorator denotes this class as a candidate for Angular’s dependency injection mechanism. For now just think of adding the @Injectable
as a required convention for all services that you write.
Next, add the following line to the top of app/app.component.ts
, which imports the service you just defined:
import {UserService} from "./shared/user/user.service";
After that, add a new providers
property to the existing @Component
decorator. The full @Component
declaration should now look like this:
@Component({
selector: "my-app",
providers: [UserService],
templateUrl: "pages/login/login.html",
styleUrls: ["pages/login/login-common.css", "pages/login/login.css"],
})
The providers
array is a simple list of all the Angular 2 services that you need to use in your component. At the moment you only have one service, so your providers
array only has one entry.
Next, replace AppComponent
’s existing constructor
with the code below:
constructor(private _userService: UserService) {
this.user = new User();
}
We’ll discuss what this constructor
syntax is doing momentarily, as it can be confusing if you’ve never worked with Angular 2 before, but first let’s make the final change to get this example running. Find the existing submit()
function in AppComponent
, and replace it with the following three functions:
submit() {
if (this.isLoggingIn) {
this.login();
} else {
this.signUp();
}
}
login() {
// TODO: Define
}
signUp() {
this._userService.register(this.user);
}
Now, in your app, tap the “Sign Up” button, type an email address, and tap the “Sign Up” button again. If all went well, you should see the alert below:
How does this code work? Let’s return to this bit of code:
constructor(private _userService: UserService) {
this.user = new User();
}
This is Angular 2’s dependency injection implementation in action. Because you registered UserService
as a provider in this component’s providers
array, when Angular sees this syntax it creates an instance of the UserService
class, and passes that instance into the component’s constructor.
This begs a bigger question though: why bother with all of this? Why not run this._userService = new UserService()
in the component’s constructor and forget the complexity of @Injectable
and providers
?
The short answer is a dependency-injection-based approach to coding keeps your classes less coupled, and therefore more maintainable and testable as your application evolves over time. For a longer answer, head over to the Angular for a more thorough discussion of the benefits of dependency injection.
Let’s return to our example and make the registration process actually work.
Open app/shared/user/user.service.ts
and paste in the following code:
import {Injectable} from "angular2/core";
import {Http, Headers, Response} from "angular2/http";
import {User} from "./user";
import {Config} from "../config";
import {Observable} from "rxjs/Rx";
import "rxjs/add/operator/do";
import "rxjs/add/operator/map";
@Injectable()
export class UserService {
constructor(private _http: Http) {}
register(user: User) {
var headers = new Headers();
headers.append("Content-Type", "application/json");
return this._http.post(
Config.apiUrl + "Users",
JSON.stringify({
Username: user.email,
Email: user.email,
Password: user.password
}),
{ headers: headers }
)
.catch(this.handleErrors);
}
handleErrors(error: Response) {
console.log(JSON.stringify(error.json()));
return Observable.throw(error);
}
}
Next, in app/app.component.ts
, replace the existing signUp()
function with the following code:
signUp() {
this._userService.register(this.user)
.subscribe(
() => {
alert("Your account was successfully created.");
this.toggleDisplay();
},
() => alert("Unfortunately we were unable to create your account.")
);
}
The first thing to note here is the new constructor
code in user.service.ts
:
constructor(private _http: Http) {}
The UserService
class is using the same dependency injection technique to bring in a service that it needs, in this case the Http class, which is Angular 2’s way of letting you perform HTTP calls. And thanks to NativeScript, those same HTTP APIs work on iOS and Android without any extra work.
TIP: Refer to Angular 2’s docs on Http for specifics on what HTTP APIs are available.
The other new bit of code is the return value of this new register()
method. register()
returns this._http.post()
, which is an RxJS Observable
. You can refer to docs for a full tutorial on how RxJS observables work, but for now just know that the most common thing you’ll need to do with observables is subscribe to them, which is what the new code you added to app.component.ts
does:
this._userService.register(this.user)
.subscribe(
() => {
alert("Your account was successfully created.");
this.toggleDisplay();
},
() => alert("Unfortunately we were unable to create your account.")
);
NOTE: The
() => {}
syntax defines an ES2015 arrow function, which TypeScript supports out of the box. In this example, the arrow functions are the equivalent of writingfunction() {}
.
The two functions you provide are success and failure handlers. If The call to register()
succeeds, your first alert will fire, and if the call to register()
fails, your second alert will fire. Now that your service code is setup and ready to go, let’s create make the final few changes and create an account.
Because the UserService
makes use of the Http service, your final step is registering the Http provider in AppComponent
. Start by opening app/app.component.ts
and adding the following import to the top of the file:
import {HTTP_PROVIDERS} from "angular2/http";
Next, in the same file, replace the current providers
array with the following code:
providers: [UserService, HTTP_PROVIDERS],
At this point you should be ready to create an account to verify this whole setup worked.
After the provider changes have livesync’d, click the “Sign Up” button in your app, type in an email address and password (fake credentials are fine, just make up something you can remember), and then click the orange “Sign Up” button.
If all went well, you should see a confirmation dialog that looks like this:
TIP: After creating your account, you may wish to hardcore your credentials in your
AppComponent
’sconstructor
to make development faster for the rest of this guide.constructor(private _userService: UserService) { this.user = new User(); this.user.email =<EMAIL_ADDRESS>this.user.password = "password"; }
Your app now has a fully functional registration process, but users can’t do anything with the accounts they create. Our next step is to allow the users to login and navigate to a new list page. And to do that we need to introduce the concept of routing.
Most of the Angular 2 concepts you’ve used in the last few sections are the same regardless of whether you’re building for the web or native apps. You are even able to share model objects and services directly between the two environments.
However, routing is different, as there are some fundamental differences between how the concept of routing works on the web and in apps. Routing on the web revolves around the concept of a URL, but in a native app there is no browser. Likewise, native apps have concepts that aren’t present in the browser, such as Android’s hardware back button, or iOS’s swipe left-to-right gesture to go back.
Because of that, routing in NativeScript needs to work a bit differently. The main difference is that you must designate certain Angular components in your app as “pages”, which in Groceries are stored in the pages
folder. Let’s look at the code needed to make this work, and then step by and discuss why NativeScript takes the approach that it does.
To this point you’ve been putting your login page code in app.component.ts
. Let’s move that logic into the pages/login
folder to make room for additional pages.
First, open app/app.component.ts
and copy its contents into app/pages/login/login.component.ts
.
Next, in login.component.ts
, change the name of the class from “AppComponent” to “LoginPage, and update the two paths below accordingly:
import {User} from "../../shared/user/user";
import {UserService} from "../../shared/user/user.service";
Now that app.component.ts
is empty, let’s add in the appropriate Angular 2 routing code. Open app/app.component.ts back up and paste in the following code:
import {Component} from "angular2/core";
import {RouteConfig} from "angular2/router";
import {NS_ROUTER_DIRECTIVES, NS_ROUTER_PROVIDERS} from "nativescript-angular/router";
import {LoginPage} from "./pages/login/login.component";
@Component({
selector: "main",
directives: [NS_ROUTER_DIRECTIVES],
providers: [NS_ROUTER_PROVIDERS],
template: "<page-router-outlet></page-router-outlet>"
})
@RouteConfig([
{ path: "/Login", component: LoginPage, name: "Login", useAsDefault: true },
])
export class AppComponent {}
The main new concept here is the @RouteConfig
decorator, which you use to provide a list of all routes that are present in your app. Currently you only have one route, which is why @RouteConfig
contains a single entry.
TIP: If you haven’t played with Angular 2 routing before, you can refer to Angular’s tutorial on the topic for some background.
The other new concept in this example is <page-router-outlet>
, which is your app’s first directive. You can check out Angular’s docs for details on what directives do, but the simplest way to think of them is as something that can affect the markup you put in your template
—in this case <page-router-outlet>
.
Angular 2 provides a <router-outlet>
directive for web apps, and NativeScript extends that directive with its own <page-router-outlet>
directive that handles the unique environment of iOS and Android apps. To see how it works let’s add another page.
Open pages/list/list.component.ts
and paste in the following code, which you’ll use as the start of a simple list page:
import {Component} from "angular2/core";
@Component({
selector: "list",
templateUrl: "pages/list/list.html",
styleUrls: ["pages/list/list-common.css", "pages/list/list.css"]
})
export class ListPage {}
For now, we’ll keep the list page simple so you can see how the routing works. But so that there’s something to see, open pages/list/list.html
and paste the following label:

After that, go back to app/app.component.ts
and paste the following import in at the top of the file:
import {ListPage} from "./pages/list/list.component";
Next, replace the existing @RouteConfig
with the following code so that it includes this new list page:
@RouteConfig([
{ path: "/Login", component: LoginPage, name: "Login", useAsDefault: true },
{ path: "/List", component: ListPage, name: "List" }
])
Angular 2 now knows about the list page, but we still need to navigate the user to that page at the appropriate time. Our next step is to allow users to log into their accounts, and to take navigate them to the new list page after they successfully authenticate.
To do that, start by opening app/shared/user/user.service.ts
and the login()
function below to the existing UserService
class:
login(user: User) {
var headers = new Headers();
headers.append("Content-Type", "application/json");
return this._http.post(
Config.apiUrl + "oauth/token",
JSON.stringify({
username: user.email,
password: user.password,
grant_type: "password"
}),
{ headers: headers }
)
.map(response => response.json())
.do(data => {
Config.token = data.Result.access_token;
})
.catch(this.handleErrors);
}
This code hits one of our existing backend endpoints, and stores off a authentication token that we’ll use later in this guide.
To use this login()
function, return to app/login/login.component.ts
, and add the following import to the top of the file:
import {Router} from "angular2/router";
Next, replace the current constructor()
declaration with the code below, which adds Angular 2’s Router
service:
constructor(private _router: Router, private _userService: UserService) {
Finally, replace the LoginPage
’s existing login()
function with the code below:
login() {
this._userService.login(this.user)
.subscribe(
() => this._router.navigate(["List"]),
(error) => alert("Unfortunately we could not find your account.")
);
}
NOTE:
- You don’t have to add
Router
to yourLoginPage
component’sproviders
array because it’s already included in the parentAppComponent
component’sproviders
list.- Refer to Angular’s documentation for a full list of the API available on the
Router
service.
After this change you can now navigate between the login and list pages in your app:
The power of NativeScript is you have the ability to use the same Angular conventions that you’d use in a web app—@RouteConfig
, Router
, and so forth—yet get an app that fits right in on iOS and Android. Notice how on Android the hardware back button works as expected, and how your iOS app uses built-in iOS animations and conventions such as the back button.
And we’re just getting started. NativeScript provides a number of other ways to tie into native device functionality out of the box through NativeScript modules. Let’s look at how they work.
In this chapter you'll learn about NativeScript modules, which are the JavaScript modules in your app's node_modules/tns-core-modules
folder. Whether you've realized it or not, you've already used several NativeScript modules, as all of the NativeScript UI elements are actually implemented with JavaScript code.
If you dig into node_modules/tns-core-modules
you can get an idea of how these modules work. Start by finding the node_modules/tns-core-modules/camera
folder, which includes the implementation of the camera module. It includes:
package.json
file that sets the name of the module;camera.android.js
);camera.ios.js
);camera-common.js
)NOTE: You can refer to the Node.js documentation on folders as modules for more detailed information on how NativeScript organizes its modules.
The *.ios.*
and *.android.*
naming convention should look familiar, as it’s the exact same convention we used to include Android- and iOS-specific styling in chapter 2.3. NativeScript uses this same convention to implement its modules on iOS and Android. Now that you know where these modules are, let's take a closer look at what else they can do for your app, starting with a closer looks at what you can do with NativeScript’s UI elements.
So far, you’ve only used NativeScript UI elements by including them in an Angular 2 component’s template
, but you can also programmaticly create and access UI elements, and each UI element has a set of properties and methods you can use to customize your app. To see how this works lets access the <Page>
UI element and make some changes to it.
Open app/pages/login/login.component.ts
, and add the following two imports to the top of the file:
import {topmost} from "ui/frame";
import {Page} from "ui/page";
Next, alter the same file’s existing "angular2/core"
import to include the OnInit
interface:
import {Component, OnInit} from "angular2/core";
OnInit
is a TypeScript class interface. To see how it works, make the following change to the declaration of your LoginPage
class:
export class LoginPage implements OnInit {
If you’re using an editor that supports TypeScript, you should see an error that says something like “Class ‘LoginPage’ incorrectly implements interface ‘OnInit’”. When you implement a TypeScript class interface, you’re telling the TypeScript compiler that you will implement any of the methods that the interface requires. In the case of OnInit
, Angular 2 requires you to implement a single ngOnInit()
method. To implement it, add the following code within the LoginPage
class:
ngOnInit() {
this.page = <Page>topmost().currentPage;
this.page.actionBarHidden = true;
this.page.backgroundImage = this.page.ios ? "res://bg_login.jpg" : "res://bg_login";
}
NOTE: The
this.page.ios
in this function can go away after NativeScript 2.0 is released. The check is there because of this bug.
ngOnInit
is one of several component lifecycle hooks that Angular 2 provides. As its name implies, ngOnInit
gets invoked when Angular initializes this component.
We’ll discuss what the code within ngOnInit()
does momentarily, but finally, to make these changes compile and run, add the following property to the LoginPage
class. (You can put it right under the user: User;
line.)
page: Page;
Now that you have this code in place, let’s discuss what happens in these three lines:
this.page = <Page>topmost().currentPage;
this.page.actionBarHidden = true;
this.page.backgroundImage = this.page.ios ? "res://bg_login.jpg" : "res://bg_login";
This code uses the topmost()
function from the NativeScript frame module, and the Page
class from the NativeScript page module. The topmost()
function gives you access to the app’s current page with its currentPage
property, and you use that reference to set that page’s actionBarHidden
and backgroundImage
properties.
Although you can peruse the NativeScript API documentation for a full list of these properties and what they do, if you’re using a TypeScript-friendly IDE, you can get a full list of these properties at any point.
If you run your app you should see that the action bar—the bar beneath the status bar that had previously displayed on Android—is now hidden, and the page uses a gorgeous new background image:
Let’s look at a few other NativeScript modules you can use to help improve the look of this app.
The ability to run robust and performant animations is the one of the biggest reasons people choose to build native mobile apps, and NativeScript makes running these animations simple. The NativeScript animation modules provides a series of JavaScript APIs that let you perform a wide variety of animations to elements on the screen, including the following:
Let’s add a simple animation so you can see how they work.
Open app/pages/login/login.html
and add an id
to the existing <StackLayout>
:
<StackLayout id="container">
Next, open app/pages/login/login.component.ts
and add the following line at the top, which imports the Color
class from the NativeScript color module:
import {Color} from "color";
Finally, change the LoginPage
’s toggleDisplay()
function in the same file to use this code:
toggleDisplay() {
this.isLoggingIn = !this.isLoggingIn;
this.page.getViewById("container").animate({
backgroundColor: this.isLoggingIn ? new Color("white") : new Color("#301217"),
duration: 200
});
}
All NativeScript UI elements inherit from a base View
class, which contains a number of useful methods—including the getViewById()
and animate()
methods used in the previous example. The getViewById()
method, as its name implies, allows you to get a reference to a child view by its id
attribute.
One you have a reference to a UI element, you can call any of the methods that element inherits from View
. In this case, you call the <StackLayout id="container">
element’s animate()
method to change its background color over a duration of 200
, or 2/10 of a second. The effect is a subtle color change that helps user differentiate between the “Sign In” and “Sign Up” functionality that your form provides.
NOTE: You may notice that the text color is off with the brown background. Don’t worry about that for now; we’ll address that in chapter 6.
The animation module is a lot of fun to play with, and it’s easy to use too. All you need to do is get a reference to an element using getViewById()
, and then call that element’s animate()
method. You may want to take a few minutes to look through our animation samples and try a few of these animations for yourself in Groceries.
For now, let’s move on to another commonly used NativeScript UI element: the <ListView>
.
The ListView element lets you show a list of things on the screen, which is exactly what you need for showing a list of groceries. Before tying the grocery list to a backend API, let's start by seeing how to show a hardcoded list of items on the screen.
Open app/pages/list/list.html
and replace its contents with the following code:
<GridLayout>
<ListView [items]="groceryList" id="grocery-list" class="small-spacing">
<template #item="item">

</template>
</ListView>
</GridLayout>
We’ll talk about the new syntax in a moment, but first let’s define the class names used in the previous example. Open app/app.css
and paste the following code at the bottom of the file, which defines a few utility class names you can use throughout your app:
.small-spacing {
margin: 5;
}
.medium-spacing {
margin: 10;
}
Next, open app/pages/list/list.component.ts
and replace its contents with the code below:
import {Component, OnInit} from "angular2/core";
@Component({
selector: "list",
templateUrl: "pages/list/list.html",
styleUrls: ["pages/list/list-common.css", "pages/list/list.css"]
})
export class ListPage implements OnInit {
groceryList: Array<Object> = [];
ngOnInit() {
this.groceryList.push({ name: "Apples" });
this.groceryList.push({ name: "Bananas" });
this.groceryList.push({ name: "Oranges" });
}
}
Your ListPage
class now has a single groceryList
property, that you fill with three objects in an ngOnInit
handler. If you run your app and login, you should see the same list of groceries on the screen:
How does this work? Let’s return to this chunk of code:
<ListView [items]="groceryList" id="grocery-list" class="small-spacing">
<template #item="item">

</template>
</ListView>
The <ListView>
UI element requires an items
property that points at an array of data—in this case, the groceryList
array you added to your ListPage
class. The list view element requires a child <template>
element that specifies how to render each item in the items
array.
The #item
syntax is Angular 2’s for syntax for creating local template variables. This gives you the ability to refer to each item in the array as item
within the template. For this template, you render each item in the array with a single
UI element, and because of the [text]="item.name"
binding, those labels contain the text from the name
property of each of the items in groceryList
TypeScript array.
Now that you have a hardcoded list displaying, let’s see how to swap that out with live data.
Open app/shared/grocery/grocery.ts
and paste in the following code:
export class Grocery {
constructor(public id: string, public name: string) {}
}
This creates a simple Grocery
model object that you can use throughout your app. Next, let’s create a simple service that reads grocery lists from our backend. Open app/shared/grocery/grocery-list.service.ts
and paste in the following code:
import {Injectable} from "angular2/core";
import {Http, Headers} from "angular2/http";
import {Config} from "../config";
import {Grocery} from "./grocery";
import {Observable} from "rxjs/Rx";
import "rxjs/add/operator/map";
@Injectable()
export class GroceryListService {
constructor(private _http: Http) {}
load() {
var headers = new Headers();
headers.append("Authorization", "Bearer " + Config.token);
return this._http.get(Config.apiUrl + "Groceries", {
headers: headers
})
.map(res => res.json())
.map(data => {
var groceryList = [];
data.Result.forEach((grocery) => {
groceryList.push(new Grocery(grocery.Id, grocery.Name));
});
return groceryList;
})
.catch(this.handleErrors);
}
handleErrors(error: Response) {
console.log(JSON.stringify(error.json()));
return Observable.throw(error);
}
}
The code here is very similar to the code you used in the UserService
earlier in this guide. You use the Http
service’s get()
method to load JSON data, and RxJS’s map()
function to format the data into an array of Grocery
objects.
To use this service, open app/pages/list/list.component.ts
and add the following two lines to the top of the file:
import {Grocery} from "../../shared/grocery/grocery";
import {GroceryListService} from "../../shared/grocery/grocery-list.service";
Next, change the existing groceryList
declaration to use the newly imported Grocery
class instead of a generic Object
:
groceryList: Array<Grocery> = [];
Then, add the following constructor
function within the ListPage
class:
constructor(private _groceryListService: GroceryListService) {}
Next, because you’re injecting a service into your constructor you must also include it as a provider within your component decorator. To do so, replace the existing @Component
decorator with the code below:
@Component({
selector: "list",
templateUrl: "pages/list/list.html",
styleUrls: ["pages/list/list-common.css", "pages/list/list.css"],
providers: [GroceryListService]
})
Finally, to kick off the call to load()
when this page initializes, replace the existing ngOnInit()
function with the code below:
ngOnInit() {
this._groceryListService.load()
.subscribe(loadedGroceries => {
loadedGroceries.forEach((groceryObject) => {
this.groceryList.unshift(groceryObject);
});
});
}
The full version of your app/pages/list/list.component.ts
file should now look like this:
import {Component, OnInit} from "angular2/core";
import {Grocery} from "../../shared/grocery/grocery";
import {GroceryListService} from "../../shared/grocery/grocery-list.service";
@Component({
selector: "list",
templateUrl: "pages/list/list.html",
styleUrls: ["pages/list/list-common.css", "pages/list/list.css"],
providers: [GroceryListService]
})
export class ListPage implements OnInit {
groceryList: Array<Grocery> = [];
constructor(private _groceryListService: GroceryListService) {}
ngOnInit() {
this._groceryListService.load()
.subscribe(loadedGroceries => {
loadedGroceries.forEach((groceryObject) => {
this.groceryList.unshift(groceryObject);
});
});
}
}
Once you run this code you may expect to see a list of groceries, but instead you’ll see this error in your terminal:
JS: EXCEPTION: No provider for Http! (ListPage -> GroceryListService -> Http)
JS: STACKTRACE:
JS: Error: DI Exception
JS: at NoProviderError.BaseException [as constructor] (/data/data/org.nativescript.groceries/files/app/tns_modules/angular2/src/facade/exceptions.js:16:23)
JS: at NoProviderError.AbstractProviderError [as constructor] (/data/data/org.nativescript.groceries/files/app/tns_modules/angular2/src/core/di/exceptions.js:38:16)
The problem here is your new GroceryListService
uses the Http
service, but that Http
service is never included in your component’s providers
array. You could add HTTP_PROVIDERS
to this array, as you did in login.component.ts
, but it seems a little silly to add HTTP_PROVIDERS
to every page that you build. And as it turns out, Angular 2 providers a simpler way of handling this, by allowing you to add common providers to parent components.
Open app/pages/login/login.component.ts
and remove the following line from the top of the file:
import {HTTP_PROVIDERS} from "angular2/http";
Next, in the same file, remove HTTP_PROVIDERS
from the component decorator’s providers
array. The array should now look this like:
providers: [UserService],
After that, open app/app.component.ts
and add the following import to the top of the file:
import {HTTP_PROVIDERS} from "angular2/http";
Finally, add HTTP_PROVIDERS
to the AppComponent
decorator’s providers
array so that it looks like this:
providers: [HTTP_PROVIDERS, NS_ROUTER_PROVIDERS],
Generally, it’s only a good idea to declare providers in parent components if all of the component’s children actually use that provider. Although you could declare all your providers in AppComponent
, your providers
would become unwieldy as your app grows, and difficult to refactor as your app changes.
If you load the list page with the account you created earlier you’ll see a blank page, as your account is newly created, and therefore your grocery list is empty. If you want to see some data to verify your changes worked, try logging in with the credentials<EMAIL_ADDRESS>and “password”. You should some data that looks something like this:
At this point you have a list of data associated with each account that you display in a list view control, but a grocery list isn’t very useful if you can’t add to the list. Let’s look at how to do that next.
In order to allow users to add to their grocery lists you need to add a few additional UI controls to the list page. While you could layout these elements with a <StackLayout>
, let’s look at how to create a slightly more complex layout using the <GridLayout>
element.
Open app/pages/list/list.html
and replace the contents of the file with the following code:
<GridLayout rows="auto, *">
<GridLayout row="0" columns="*, auto" class="add-bar">
<TextField id="grocery" hint="Enter a grocery item" col="0"></TextField>
<Image src="res://add" col="1"></Image>
</GridLayout>
<ListView [items]="groceryList" id="grocery-list" row="1" class="small-spacing">
<template #item="item">

</template>
</ListView>
</GridLayout>
When your app runs with these changes your UI should now look like this:
To break down how this layout works, let’s start with the outer structure of the markup:
<GridLayout rows="auto, *">
<GridLayout row="0" class="add-bar">...</GridLayout>
<ListView row="1">...</ListView>
</GridLayout>
The outer grid layout’s rows
attribute divides the screen into two rows, the first auto-sized according to its childrens' height, and the other to contain *, or the remaining height of the screen. You place UI elements into these rows using the zero-based row
attribute. You place inner grid layout in the top row with the row="0"
attribute, and the list view in the bottom row with the row="1"
attribute.
Grid layouts can also divide the screen into columns, which is what the inner grid layout does:
<GridLayout columns="*, auto" class="add-bar">
<TextField col="0"></TextField>
<Image col="1"></Image>
</GridLayout>
Here the columns
attribute divides the top of the screen into two columns. The col="0"
attribute puts the text field in the first column, and the col="1"
attribute puts the plus image in the last column. Grid layouts are the most commonly used NativeScript layout, so you may wish to take a minute to play around with the columns
and rows
attributes to figure out how they work.
TIP:
- You can nest any of the NativeScript layouts—not just grid layouts.
- You can pass numbers, percentages, and a variety of other values to create more complex grid layouts. Refer to the grid layout docs for more information.
Now that we have the UI ready, let’s make the add button work.
Open app/pages/list/list.html
and give the existing <TextField>
a new [(ngModel)]
attribute so that it looks like this:
<TextField id="grocery" [(ngModel)]="grocery" hint="Enter a grocery item" col="0"></TextField>
Next, give the same file’s image a new tap
attribute binding, so that the full <Image>
looks like this:
<Image src="res://add" (tap)="add()" col="1"></Image>
With these attributes in place, your next steps are to define the grocery
property and the add()
method. To do that, open app/pages/list/list.component.ts
and add the following property to the ListPage
class (right below the existing groceryList
property):
grocery: string = "";
Next, add the following two inputs to the top of the list.component.ts
file:
import {TextField} from "ui/text-field";
import {topmost} from "ui/frame";
Then, add the following add()
function to the existing ListPage
class:
add() {
if (this.grocery.trim() === "") {
alert("Enter a grocery item");
return;
}
// Dismiss the keyboard
var groceryTextField = <TextField>topmost().currentPage.getViewById("grocery");
groceryTextField.dismissSoftInput();
this._groceryListService.add(this.grocery)
.subscribe(
groceryObject => {
this.groceryList.unshift(groceryObject);
this.grocery = "";
},
() => {
alert({
message: "An error occurred while adding an item to your list.",
okButtonText: "OK"
});
this.grocery = "";
}
)
}
In this function you first ensure the user didn’t submit without typing a grocery. If the user did type something, you dismiss the device’s keyboard with the TextField element’s dismissSoftInput()
method, and then call a new add()
method on the GroceryListService
.
To finish this example you have to define that new add()
method. To do so, open app/shared/grocery/grocery-list.service.ts
and paste the following function into the GroceryService
class:
add(name: string) {
var headers = new Headers();
headers.append("Authorization", "Bearer " + Config.token);
headers.append("Content-Type", "application/json");
return this._http.post(
Config.apiUrl + "Groceries",
JSON.stringify({ Name: name }),
{ headers: headers }
)
.map(res => res.json())
.map(data => {
return new Grocery(data.Result.Id, name);
})
.catch(this.handleErrors);
}
The add()
code should look familiar as you’re again using the Http
service’s post()
method to make an HTTP call to our backend, and then using RxJS’s map()
function to convert the returned data into a Grocery
object. You consume that object in the ListPage
component’s add()
method, which adds the grocery to the page’s list by calling this.groceryList.unshift()
, and then empties that page’s text field by setting this.grocery
equal to ""
.
this._groceryListService.add(this.grocery)
.subscribe(
groceryObject => {
this.groceryList.unshift(groceryObject);
this.grocery = "";
},
() => { ... }
);
The end result looks like this:
At this point you can add a grocery item and it will appear immediately in your list—and, all of this is completely driven by a backend service. Pretty cool, huh?
Let's look at how you can polish this page with a NativeScript module for showing activity indicators.
Currently there's a bit of a delay when you first visit the list page before groceries appear. This delay could confuse a new user, who might think the app is stuck rather than retrieving data from a backend.
In NativeScript apps you can use the ActivityIndicator module to show a spinner icon in your UI while your app is busy performing actions. The ActivityIndicator is a relatively simple UI element as it primarily uses one attribute—busy
. When an ActivityIndicator's busy
attribute is set to true
the ActivityIndicator shows, and when its busy
attribute is set to false
it doesn't. Let's see how the module works by adding an ActivityIndicator to the list page.
Open up app/pages/list/list.html
and paste the following line immediately before the final </GridLayout>
:
<ActivityIndicator [busy]="isLoading" row="1"></ActivityIndicator>
This binds the ActivityIndicator’s busy
attribute to an isLoading
property in the ListPage
component. To define that property, open app/pages/list/list.component.ts
and add the following line of code immediately under grocery: string = ""
:
isLoading = false;
Now that the property exists, your final step is to set this flag to to true
when the grocery data is loading. To do that, change the existing ngOnInit()
function to use the code below:
ngOnInit() {
this.isLoading = true;
this._groceryListService.load()
.subscribe(loadedGroceries => {
loadedGroceries.forEach((groceryObject) => {
this.groceryList.unshift(groceryObject);
});
this.isLoading = false;
});
}
When you first visit the list page, you should now see the following loading indicators:
TIP: You can apply the same
row
orcolumn
attribute to multiple UI controls to have them take up the same space on the screen. The UI control that is defined last will appear on top, which is why the<ActivityIndicator>
appears on top of the<ListView>
in the previous example.<ListView row="1">...</ListView> <ActivityIndicator row="1"></ActivityIndicator>
To finish off this chapter, let’s look at how you can use the animation module to add a final bit of polish to how the list page loads.
In this exercise, you’ll use the animation module to animate the opacity
of the list page’s <ListView>
to add a fade-in effect. Start by opening app/pages/list/list-common.css
and pasting in the following CSS at the top of the file:
ListView {
opacity: 0;
}
Next, open app/pages/list/list-component.ts
and add replace the existing ngOnInit()
function with the following code:
ngOnInit() {
this.isLoading = true;
this._groceryListService.load()
.subscribe(loadedGroceries => {
loadedGroceries.forEach((groceryObject) => {
this.groceryList.unshift(groceryObject);
});
this.isLoading = false;
var groceryList = topmost().currentPage.getViewById("grocery-list");
groceryList.animate({
opacity: 1,
duration: 1000
});
});
}
A few things are happening in the code above.
First, in CSS, you assign an opacity
of 0
to the list page’s <ListView>
. This hides the grocery list completely when the page loads. Next, in TypeScript, after the GroceryListService
’s load()
call completes, you call the list view’s animate()
function. This changes the element's opacity
from 0
(completely hidden) to 1
(completely visible) over one full second.
The result of this code is a nice fade-in animation:
Now that you have functional login and list pages, let’s enhance the app’s functionality as a grocery list management tool. In the next chapters you'll add functionality such as email validation, social sharing, and more. And you’ll use one of NativeScript's most useful features to do so: npm modules.
TIP: There are several modules that come out of the box with your NativeScript installation that we did not have time to cover in this guide—including a location service, a file-system helper, a timer module, a camera module, and a whole lot more. Make sure to peruse the “Modules API” of the docs, or just look around
node_modules/tns-core-modules
to see all of what’s available.
As you build more complex apps, you'll likely run into functionality that is not implemented in the NativeScript modules. But no worries, as NativeScript lets you leverage npm (node package manager) to import npm modules into your apps. Alternately, you can install NativeScript plugins, which are simply npm modules that can access native code and use Android and iOS SDKs, if required.
In this chapter, you'll install and use an external email validator module to verify the format of email addresses as they are entered on the registration screen. Then, you'll add a NativeScript plugin, NativeScript social share, to let users share their grocery lists using their device's native sharing widget.
It would be nice to be able to make sure people are entering well-formatted email addresses into your app on the registration screen. You could write this functionality yourself, but validating email addresses is surprisingly tricky, and it's a lot easier to use one of many npm modules that already provide this validation. For Groceries let's see how to add this email-validator module to test for valid addresses.
Return to your terminal and make sure that you are working in the root directory in your Groceries project folder, a.k.a. here:
sample-Groceries <----------------
├── app
│ └── ...
├── package.json
└── platforms
├── android
└── ios
From the root directory install the email-validator module:
npm install email-validator --save
The install process does a few things in the background. First, because you added the --save
flag, npm records this dependency in your app's package.json
. If you open your package.json
you should see "email-validator"
in your app's "dependencies"
array.
"dependencies": {
"email-validator": "^1.0.4"
}
The npm CLI also creates a node_modules
folder in the root of your app. This folder contains the code for the email-validator module, which is a bit of validation logic in node_modules/email_validator/index.js
.
TIP: By saving your app's npm dependencies in your
package.json
file, you can always regenerate yournode_modules
folder by runningnpm install
. Because of this, it's a common practice to exclude thenode_modules
folder from source control. The Groceries app uses git for source control, and as such includesnode_modules/
in its.gitignore
.
Now that you have the module installed let's look at how to use it.
Open /app/shared/user/user.ts
and replace the existing contents of the file with the code below:
var validator = require("email-validator");
export class User {
email: string;
password: string;
isValidEmail() {
return validator.validate(this.email);
}
}
NOTE: The NativeScript framework's
require()
method is configured to look at the"main"
value in an npm module'spackage.json
file. In the case of this module, the"main"
value is"index.js"
. Therefore, when you runrequire("email-validator")
, you're actually requiring the file atnode_modules/email_validator/index.js
. You could also typerequire("email-validator/index")
to retrieve the same file.
To make use of this validator, open app/pages/login/login.component.ts
and paste the following code at the beginning of the existing submit()
function:
if (!this.user.isValidEmail()) {
alert("Enter a valid email address.");
return;
}
Now, if the user attempts to login or register with an invalid email address, they’ll see an alert that points out the error. However in order to test out this change you’ll need to do one more thing.
As we mentioned in chapter 1.4, although the tns livesync
command is smart enough to reload your app for most changes you make to your app, the command cannot hot reload your app for all changes—most notably, changes to native files in app/App_Resources
, new modules installed with npm install
, and new NativeScript plugins.
For NativeScript to recognize this new email-validator npm module, type Ctrl+C
in your terminal to kill the existing tns livesync
watcher if it’s still running, and then use tns run
to rebuild your application and deploy it to an emulator or device.
tns run ios --emulator
Or
tns run android --emulator
After the app deploys you can again run the livesync
command to setup the watcher again.
tns livesync ios --emulator --watch
Or
tns livesync android --emulator --watch
After your app launches again, if you type an invalid email address and attempt to login, you should see an alert that prevents the submission:
In general npm modules greatly expand the number of things you're able to do in your NativeScript apps. Need date and time formatting? Use moment. Need utility functions for objects and arrays? Use lodash or underscore. This code reuse benefit gets even more powerful when you bring NativeScript plugins into the picture.
WARNING: Not all npm modules work in NativeScript apps. Specifically, modules that depend on Node.js or browser APIs will not work, as those APIs do not exist in NativeScript. The NativeScript wiki contains a list of some of the more popular npm modules that have been verified to work in NativeScript apps.
NativeScript plugins are npm modules that have the added ability to run native code and use iOS and Android frameworks. Because NativeScript plugins are just npm modules, a lot of the techniques you learned in the previous section still apply. The one big difference is in the command you use to install plugins. Let's look at how it works by installing the NativeScript social share plugin.
Return to your terminal, make sure you're still in the root of your app, and run the following command:
tns plugin add nativescript-social-share
The install process does the same thing that the npm install
command does—including retrieving the module from npm, installing the module in node_modules
, and saving the module as a dependency in your app's package.json
—but the tns plugin add
command additionally configures any native code that the plugin needs to use.
For example the NativeScript push plugin uses both iOS and Android SDKs, and the tns plugin add
command takes care of installing those. The NativeScript flashlight plugin needs permissions to use the camera on Android, and the tns plugin add
command takes care of setting that up too.
Now that you've installed the social share plugin, let's look at how to use it.
Open app/pages/list/list.component.ts
and add the following line at the top of the file, which requires the social share module you just installed:
var socialShare = require("nativescript-social-share");
Next you have to build some UI that lets you share a grocery list. To do so, open app/pages/list/list.html
and add the following code at the very top of the file:
<ActionBar title="Groceries">
<ActionItem text="Share" (tap)="share()" android.systemIcon="ic_menu_share_holo_dark" ios.systemIcon="9" ios.position="right"></ActionItem>
</ActionBar>
This code defines an ActionBar, which is a UI component that appears on the top of the screen, and which can optionally include menu items, or <ActionItem>
components.
NOTE: On iOS devices,
<ActionItem>
s are placed from left to right in sequence; you can override that (as the code above does) by providing anios.position
attribute.
Next, to add a bit of styling to this new <ActionBar>
, add the following CSS to the top of your app/app.css
file:
ActionBar {
background-color: black;
color: white;
}
Finally, now that you’ve installed and required the plugin, and setup a UI to use it, your last step is implementing the <ActionItem>
's tap
handler. Open app/pages/list/list.component.ts
again and add the following function to the ListPage
class:
share() {
var list = [];
for (var i = 0, size = this.groceryList.length; i < size ; i++) {
list.push(this.groceryList[i].name);
}
var listString = list.join(", ").trim();
socialShare.shareText(listString);
}
This code takes the grocery data from the grocery list array, converts the data into a comma-separated string, and passes that string to the social share plugin’s shareText()
method.
 If you don’t remember how refer back to chapter 5.0 for instructions.
After you run the app, you'll see a new button at the top of the screen. When you tap it, the native iOS or Android sharing widget will show to let you post your groceries to your social networks, or send them via email, message, or any other method you prefer.
Pretty cool, huh? The ability to use npm modules greatly expands the number of things you're able to do in a NativeScript app. Need to compose emails in your app? Try out the NativeScript email plugin. Need to use the clipboard in your app? Try out the NativeScript clipboard plugin.
If you're looking for NativeScript plugins start by searching both the Telerik NativeScript Plugins Marketplace and our community-curated list of plugins on npm. If you don't find the plugin you need, you can request the plugin on our ideas portal, or you can take a stab at creating the plugin yourself.
Between NativeScript modules, npm modules, and NativeScript plugins, the NativeScript framework provides a lot of functionality you can use to build your next app. However, we've yet to talk about NativeScript's most powerful feature: the ability to directly access iOS and Android APIs in JavaScript. Let's look at how it works.
The beauty of NativeScript is that you can write a native iOS or Android app in JavaScript, XML, and CSS without touching Swift, Objective-C, or Java, if you choose. But what if you want to present a different, more platform-specific UI to your users? Or if you want to access an iOS or Android API that NativeScript doesn't expose through a NativeScript module or plugin?
NativeScript gives you the option to dig into native code as needed, and to do so without leaving JavaScript. To show how this works in action, let's tweak a few of the UI elements in your app using native code.
You may recall from earlier chapters that the hint color on your sign up screen could use a little more contrast. Notice the unappealing black on brown color of the text in the images below (if you can see the text at all).
At the time of this writing, NativeScript doesn’t expose a way to style a text field’s hint color through CSS—although there is an open issue requesting the feature—however, both iOS and Android have ways to accomplish this task, and with NativeScript you have direct access to these native APIs.
Let’s start with iOS. If you run a generic search for “style iOS text field hint text”, the first result is a Stack Overflow post that recommends setting a UITextField
’s attributedPlaceholder
property. Let’s look at how to do that.
Because there are multiple text fields in Groceries, we’ll write the functionality to change hint colors as a utility that lives in your app’s utils
folder. Open app/utils/hint-util.ts
and paste in the following code:
import {Color} from "color";
import {TextField} from "ui/text-field";
declare var NSAttributedString: any;
declare var NSDictionary: any;
declare var NSForegroundColorAttributeName: any;
export function setHintColor(args: { view: TextField, color: Color }) {
var dictionary = new NSDictionary(
[args.color.ios],
[NSForegroundColorAttributeName]
);
args.view.ios.attributedPlaceholder = NSAttributedString.alloc().initWithStringAttributes(
args.view.hint, dictionary);
}
This creates a function called setHintColor()
that accepts a <TextField>
and Color
. We’ll talk about the contents of this function momentarily; first let’s look at how to use it.
Open up app/pages/login/login.component.ts
and add the following two imports to the top of the file:
import {setHintColor} from "../../utils/hint-util";
import {TextField} from "ui/text-field";
After that, add the following function to the file’s LoginPage
class:
setTextFieldColors() {
var email = <TextField>this.page.getViewById("email");
var password = <TextField>this.page.getViewById("password");
var mainTextColor = new Color(this.isLoggingIn ? "black" : "#C4AFB4");
email.color = mainTextColor;
password.color = mainTextColor;
var hintColor = new Color(this.isLoggingIn ? "#ACA6A7" : "#C4AFB4");
setHintColor({ view: email, color: hintColor });
setHintColor({ view: password, color: hintColor });
}
Finally, add a call to the new setTextFieldColors()
in your LoginPage
’s existing toggleDisplay()
method—ideally immediately after the existing this.isLoggingIn = !this.isLoggingIn
line:
this.setTextFieldColors();
After your app refreshes with this change, you should now see a far more readable hint color:
Let’s back up to the contents of the setHintColor()
function so we can discuss what’s going on here.
var dictionary = new NSDictionary(
[args.color.ios],
[NSForegroundColorAttributeName]
);
args.view.ios.attributedPlaceholder = NSAttributedString.alloc().initWithStringAttributes(
args.view.hint, dictionary);
By convention, NativeScript controls make their iOS and Android native implementations available via ios
and android
properties, respectively. In this code that means that args.color.ios
resolves to a UIColor
, and args.view.ios
resolves to a UITextField
. Once you have a reference to these controls you can set native properties on them directly in TypeScript, which this code does with the UITextField
’s attributedPlaceholder
property.
The power with NativeScript is you can perform these customizations in TypeScript—there’s no need to jump into Xcode and write Objective-C or Swift. And this doesn’t apply just to attributes. Notice the global NSDictionary
, NSAttributedString
, and NSForegroundColorAttributeName
attributes. In NativeScript, all iOS and Android APIs are globally available to use—again, directly in TypeScript code.
Admittedly, this code can seem a bit arcane if you’ve never written an iOS app before, but the key here is that you’re never limited by the APIs that NativeScript provides out of the box. Most of the time you’ll be able to solve problems using the NativeScript module APIs, but if you hit a scenario your app needs that NativeScript doesn’t provide a module for, you can always hit the native APIs directly.
TIP:
- NativeScript provides TypeScript declaration files (
.d.ts
files) for all iOS and Android APIs. You can download the files using the links below. One word of warning though: because the declaration files include the entirety of the iOS and Android SDKs, they’re quite large, and can slow TypeScript builds to a crawl because of their sheer size. Nevertheless, the files can be invaluable during development, as they make accessing native APIs a whole lot easier.- For detailed information on how NativeScript makes native APIs globally available, read about “How NativeScript Works” on our blog, and “Accessing Native APIs with JavaScript” on our documentation.
Let’s move onto how to accomplish this same hint color task on Android.
Much like with iOS, if you’ve not a native Android developer, figuring out how to accomplish a task on Android often starts with a web search. If you run a search for “style Android text field hint text”, you’ll end up on a Stack Overflow answer that recommends using a android.widget.TextView
’s setTextHintColor()
method. Let’s alter our code to do that.
Open app/utils/hint-util.ts
and replace the existing contents of the file with the following code:
import {Color} from "color";
import {TextField} from "ui/text-field";
declare var NSAttributedString: any;
declare var NSDictionary: any;
declare var NSForegroundColorAttributeName: any;
export function setHintColor(args: { view: TextField, color: Color }) {
if (args.view.android) {
args.view.android.setHintTextColor(args.color.android);
}
if (args.view.ios) {
var dictionary = new NSDictionary(
[args.color.ios],
[NSForegroundColorAttributeName]
);
args.view.ios.attributedPlaceholder = NSAttributedString.alloc().initWithStringAttributes(
args.view.hint, dictionary);
}
}
Remember from the previous section that NativeScript makes native objects available via a android
property. In this case args.view.android
refers to a TextView
, and therefore has the setHintTextColor()
method that the Stack Overflow post said to call.
One other thing to notice is the if checks that you added around each of the native calls. Your TypeScript code runs across both platforms, and iOS APIs are not available on Android (and vice versa). Testing for the existence of the native object properties is a common way to fork your code in NativeScript to avoid errors. And with this change in place, your hint colors on Android are now far more legible.
Let’s look at one last way we can improve the look of this app with native code.
At the time of this writing, NativeScript does not expose a way to make translucent status bars—aka status bars that you can see through. There is an open issue requesting this feature, but as with anything else when building with NativeScript, you don’t have to be limited by what NativeScript provides out of the box. Let’s look at how you can use that power to make your status bars look a little nicer.
Sometimes accomplishing tasks with native code is simple, as it was with setting hint text on Android, and sometimes it takes a little more work. Because setting a status bar’s appearance is slightly more involved, the code has been prepopulated in app/utils/status-bar-util.ts
. There are a few comments the link to detailed information on the techniques used, if you’re curious about how it all works.
Because this code changes the appearance of the status bar, we’ll want to call this method as soon as possible, so that the status bar doesn’t awkwardly update after the app has already loaded. Therefore to use this new utility, open app/main.ts
and replace the contents of the file with the following code, which calls a new setStatusBarColors()
before the app is bootstrapped.
import {nativeScriptBootstrap} from "nativescript-angular/application";
import {AppComponent} from "./app.component";
import {setStatusBarColors} from "./utils/status-bar-util";
setStatusBarColors();
nativeScriptBootstrap(AppComponent);
Finally, there are a few last CSS tweaks you to make to account for the now translucent status bars. On iOS a translucent status bar continues to take up space open, so you need to adjust the content of the page to sit on top of the status bar’s location. To do so, open app/platform.ios.css
and paste in the following code:
Page {
margin-top: -20;
}
Next, open app/pages/list/list.ios.css
and paste in the following code, which moves the add bar down from underneath the list page’s <ActionBar>
:
.add-bar {
margin-top: 20;
}
On Android a translucent does not take up space, so you need to add a bit of padding to the top of the list page so the status bar and <ActionBar>
don’t sit on top of one another. To do so, open app/pages/list/list.android.css
and paste in the following code:
ActionBar {
padding-top: 10;
}
And with that, your status bar is now translucent and properly spaced on iOS and Android:
And... that's it! You've created a functional, cross-platform, backend-driven app to manage your grocery list. In the process you've created a unique UI for Android and iOS, leveraged NativeScript plugins and npm modules, learned how to log in and register, managed backend services, created a list, and more.
Congratulations! Feel free to share your accomplishment on Twitter or Facebook to impress your friends 😀.
As cool as Groceries is, it’s currently missing one crucial feature for a grocery management app: the ability to delete groceries from the list.
The Groceries backend already supports deleting, but it’s up to you to implement the feature in the app. You do get two hints though. First, this is a function you can use in the GroceryListService
for performing the necessary HTTP call to delete a grocery:
delete(id: string) {
var headers = new Headers();
headers.append("Authorization", "Bearer " + Config.token);
headers.append("Content-Type", "application/json");
return this._http.delete(
Config.apiUrl + "Groceries/" + id,
{ headers: headers }
)
.map(res => res.json())
.catch(this.handleErrors);
}
Second, here’s an image you can use in your template for users to tap to delete items. One note though: the image is a white “X”, so you’ll have to find a way to create a non-white background in order to see the image.
<Image src="res://delete"></Image>
If you get stuck the Groceries app’s “angular-end” branch has a solution you can check.
Sadly our tutorial is now complete, but your journey with NativeScript is just beginning. Let’s look at the next steps you can take to build your next app with NativeScript.
Regardless of what you choose to do next with NativeScript, joining the NativeScript community is a great way to keep up with the latest and greatest in the NativeScript world. Here are some ways you can get involved:
Although you’re welcome to use Groceries as the basis of your own app, if you want to start a new app from scratch, the NativeScript CLI provides a number of built-in starting points. For example, you can start a new “hello world” app by running the tns create
command:
tns create my-app-name --template tns-template-hello-world-ng
If you want to start from a slightly more complete starting point, the tns create
command also has additional values you can use with the --template
option. For instance, the following command creates a NativeScript project with tab navigation already setup:
tns create MyNextGreatApp --template tns-template-tab-navigation
TIP: A number of other templates are available to use with the
--template
option, including TypeScript templates and more. Refer to the full list of official templates at any time.
Regardless of which template you choose to start from, once you have your app up and running, let’s look at some of the resources you can use as you develop.
There are a variety of NativeScript learning materials that go above and beyond what was offered in this tutorial. You may wish to refer to any of the following as you take your app from a prototype to the app stores:
Finally, we’d love if you could take a minute to tell us what you thought about the this guide. Your feedback helps us make this tutorial better.
As NativeScript’s Angular integration is still in beta, there are a few outstanding issues you may hit while completing this guide. Here are the major ones:
tns livesync
command currently does not work on iOS apps. Because of this, for now, you must run tns run ios --emulator
after each change you make to your app in order to see the changes rendered. Follow this problem’s GitHub issue for updates.You can refer to the nativescript-angular repo on GitHub for a full list of issues, and to report any problems you have.