Wednesday, March 30, 2016

Code Reuse in Angular 2 Native Mobile Apps with NativeScript

Please welcome Nathan Walker as a guest-author on the Angular blog. Read on to find out more about building cross-platform apps with Angular 2 and NativeScript.

The zen of multiple platforms. Chrome, Android and iPhone all running the same code.

You may have felt the tremors reverberating from the bowels of Twitter or elsewhere about writing pure Native mobile apps in JavaScript. This is not hybrid, Cordova or webviews. These are truly native, 60fps mobile applications written with JavaScript. But that leaves one wondering: If there is no webview, how can I re-use my application code between the web and my mobile app?

The answer is you need a JavaScript framework smart enough to do it, and a mobile runtime powerful enough to support it.

In this article, I'm going to show you how to create a single application with Angular 2 that can be rendered on the web, or rendered in a native mobile application with NativeScript. Here's what you can expect to learn:

How to build a Native mobile app from your existing web app codebase.

How NativeScript can fit perfectly in the mix with your Angular 2 web app.

How to utilize all of our existing web codebase with minimal to zero disruption.

How to configure Angular's Component to use the right view template on the right platform.

About a powerful feature in Angular 2: Decorators.

The strategy presented is used in the angular2-seed-advanced project (exemplified in image above). It exists for you to learn from, use directly for one of your projects as well as gather community feedback on potential integration improvements. In addition to NativeScript, it also supports ngrx/store for RxJS powered state management (Redux inspired), ng2-translate for i18n, lodash for reduction of boilerplate and more coming soon.

Disclaimer: As always, there are multiple ways to achieve the same goal. Alternative strategies are welcome and/or improvements to the one presented.

One notable and very exciting benefit is you will gain the ability to create truly Native components that are highly performant and feel natural on both Android or iOS.

Angular 2's powerful and extensible architecture makes this possible and the ability to integrate the two technologies is achieved via nativescript-angular, a custom renderer for Angular 2 which makes it possible for Angular 2 templates to render native iOS and Android controls. More background and an excellent overview of this integration is presented here by TJ Vantoll.

Q: Do I need to create a separate NativeScript XML template for each HTML template?

Q: Isn't that pretty disruptive though?

Decorators add the ability to augment a class and its members as the class is defined, through a declarative syntax. To help illustrate, let's look at a Decorator you get for free by the Angular 2 framework:

@Component is a Decorator that declaratively augments the SampleComponent class by defining specific metadata about the class; in this case: a selector for it's use in the browser's DOM and a templateUrl to define a view. Because Angular 2 was built with extensibility in mind, you are not constrained by only the Decorators the framework provides, but rather you can extend these Decorators to create your own.

In keeping with our goal to be as non-disruptive as possible with our NativeScript integration into our existing web app codebase, we are going to create our own custom Decorator, leveraging the power and elegance of Angular 2's ComponentDecorator by extending to augment with new capabilities allowing our NativeScript integration to be seamless. This will allow us to share code between our web app that runs in the browser, and your mobile app which will run on Android and iOS.

To do so, we are going to create a useful utility which will make creating our custom Decorator easier. This utility can be found in the angular2-seed-advanced project in addition to more elaborate use cases for it. Let's start by looking at how this utility works so you can use it in your own apps.

1. Create a Decorator Utility

Decorators have been proposed to become an offical part of ES7. In the meantime, we will need the reflect-metadata shim loaded in our web app or included with your build setup (webpack, gulp, etc.). This shim provides the api to interact with our Decorator's metadata. In particular, it will load a global Reflect object we will use and define as const _reflect.

You may be wondering why getMetadata is there since it doesn't look like it does anything and you are correct. It doesn't...yet. The details of annotating our Component via Reflect's api is tucked away in annotateComponent. For those wanting to learn more about Reflect, I recommended reading this. Additionally, the incredibly illustrious and well-versed Pascal Precht at thoughtram has written a fantastic in-depth article here which will help provide more background information on Decorators.

2. Create a Custom Component Decorator using our Utility

Here we are exporting a function named BaseComponent which will become the name of our custom Decorator. It accepts our Component's metadata and passes that into our Utility's annotateComponent method.

This will render a Label UI control native to the host platform, be it Android or iOS. You might recognize the [text]="statement" binding. Yep, that's just standard Angular 2 bindings.

Our *native* `Label` UI on both Android and iOS.

Note: Please take note to not use self-closing elements like <Label [text]="statement" />.
This is related to the Parse5DomAdapter noted here.

Ok, now let's make our custom BaseComponentDecorator do something interesting.

Use the NativeScript XML view when running the mobile app

Queue in our Decorator Utility. The goal is to teach our Component's to know which {N} templateUrl to use without disrupting our web development flow. A new service will be introduced, ViewBrokerService, which we will create momentarily. Since we will be using our handy utility for all of our custom decorators, let's make our adjustment there:

If our component defines a templateUrl, we are going to shuttle it through ViewBrokerService.TEMPLATE_URL which will handle "brokering" the right view template for the right platform.

getMetadata now serves a purpose to keep our augmented capabilities tidy and isolated away from the details of the Reflect api. This allows us to focus on the special sauce that will make our custom Decorator tick with less distraction.

Create ViewBrokerService

This slim service provides a single static method, TEMPLATE_URL, which will be used to provide the proper path to a template based on a static runtime configuration setting, handled by CoreConfigService; created in a moment.

The path returned for your {N} templates can be any location in your codebase.
Here's a condensed view of the sample directory structure used throughout:

In this example, all NativeScript templates are contained in a nativescript.framework folder nested under a frameworks folder.
The path expands underneath the nativescript.framework folder to match the exact same path our Component's templateUrl defined, which was:

templateUrl: './components/sample.component.html'

With help from our Decorator, the ViewBrokerService will expand our Component's templateUrl to become:

Now when our NativeScript app runs, the templateUrl will be swapped out with the {N} view and Voila!
You are now using all the code from your web application in your native mobile app! A truly amazing feat.

The 2 unique considerations are:

Create {N} XML template for each HTML template.

Create a separate bootstrap file for your NativeScript app which sets a static configuration option used by your ViewBrokerService.

There's always one other thing

Say you want to utilize the same Component method for both your web view and {N} view. Depending on the specific control in use, you may need some conditional logic to determine what the user clicked on in the web vs. native mobile.

To {N}finity and Beyond!

You will notice that with the advanced seed, the NativeScript app is in a separate directory nativescript aside from the main web src directory. The web src is actually copied into the nativescript directory when the NativeScript app is run with these instructions. This is done for several reasons, but to list the most important:

Removes the need to process {N} specific modules in the main web build which uses gulp.

Keep an eye on the advanced seed for improvements to potentially move the nativescript directory inside the src directory alongside the main web source (to remove the necessity of copying the src). The build will be modified soon so only the specific code relevant for either platform web or native mobile would be built upon command.