Building Angular 2 Web and Native Apps from a Single Codebase

So, you need to build a web site. Great! If you’re an Angular developer, you immediately dash to your favorite IDE and start scaffolding out your site. But wait! Maybe you also need to build a mobile app to accompany your beautiful new site. Now what?

It’s become clear that Angular 2 is not just a framework that you use to spin up web sites. It’s evolved into a platform that, with its decoupled DOM, fosters cross-platform code reuse. Now developers can repurpose some of the same code that they used to build their web sites and use it to build downloadable mobile apps and even desktop apps. This opens up a whole new world of awesome for the Angular developer.

In this article, I’d like to share with you lessons that I learned by doing this in the real world while making PocketRave.me with the Angular 2 Advanced Seed. This seed project is designed to help you create an app simultaneously for the web and for mobile, sharing code all along. We’ll look at how to use it. So let’s get started!

Getting Started with the Angular 2 Advanced Seed

To build for multiple platforms at once, you need a secret weapon. We will use Nathan Walker’s Angular 2 Advanced Seed. This project began as a fork of Minko Gechev’s Angular Seed, which available for download here. Minko’s seed code is one of the most popular Angular starting templates available, and the Nathan’s advanced seed builds upon it.

advanced-demo

The Advanced Seed in action

Because the advanced seed is very full featured – including i18n multilingual support, unit testing, analytics, and more – you might choose to strip out all the bits and pieces you don’t need. I started by cloning the repo and looking through the package.json files, removing the angulartics and ng2-translate libraries, before installing the dependencies with npm i . Then I scoured the codebase to remove analytics and i18n file dependencies, including the entire i18n folder.

However, to make this process easier for you, I have created a very stripped-down version of this Advanced Seed, which you can find here. I call this seed ‘AMPS’ – for the Angular Multi-Platform Seed. Follow the instructions in the README and visit the documentation hub to start up a sample app on the various platforms you are targeting.

I’d like to just note here that this work stands on the shoulders of the giants who preceded it: Nathan Walker and Minko Gechev. I’m very appreciative of their work!

The ‘AMPS’ project builds on the standard NativeScript ‘hello world’ app which is a ‘tap challenge’:

tap_challenge

Don’t let the simple default stop you from creating something much more awesome, though! PocketRave, for example, built from the Advanced Seed, is much nicer!

pocketrave

The app lets you build a “PocketRave” drawing on your mobile app and view it with the soundtrack of your choice on the web. Your art is turned into a kaleidoscope via CSS transforms and songified via a SoundCloud integration:

pocketrave2

PocketRave also has hardware integration to create a wifi-enabled light show, but that’s a story for another day…

How does it work?

The basic premise of this method of developing for web, mobile, and desktop (if you so wish) while sharing code stems from the advent of Angular 2. When the architectural decision was made to decouple the DOM, developers were given the power to use the same code to build for use cases outside the browser such as a NativeScript app, which produces an installable native mobile app, and/or a desktop app running via Electron.

Once you have cloned the AMPS project and followed the instructions to install it as outlined in the README, open the codebase in an IDE of your choice and take a look at it.

You’ll notice the /dist folder from where code is served after the build process, as well as the standard /node_modules folder. The folders we’ll be particularly involved in are /src and /nativescript . You’ll find code specific to a NativeScript mobile app in the /nativescript folder, such as /App_Resources which contains mobile-specific images and files such as the Info.plist file for iOS.

folder_structure

What can I share?

Code-sharing is enabled in this seed by a combination of symlinks and managed naming conventions. During the installation process, a symlink between the nativescript/app/app folder and src/client/app/ folder is created. You’ll find that any code changes that you make will be reflected in both places for use by the NativeScript app and the web app.

code_sharing

In general, you will be working under the /frameworks folder. For example, I’ve created a /src/client/app/frameworks/sample folder in the AMPS project, and most of my code will go into that folder. In PocketRave, this code is kept in /src/client/app/frameworks/pocketrave.

Out of the box, the seed is set up for optimal code sharing. You will be able to share your routes, your standard TypeScript Component file, and your CSS if needed. However, you will want to differentiate your web site’s HTML markup from your mobile app’s XML markup, as I’ll discuss below. Overall, you will be sharing up to about 75% of your code between the designated platforms.

This architecture allows you to deliver your NativeScript app to the app stores when it’s time, from within the NativeScript folder. There is another symlink in the /assets folder so that you can share any visual assets between the two platforms if needed.

The system of naming conventions in this seed also aids in controlling the code that you share – and don’t share – between platforms. Plain HTML code cannot be shared with a NativeScript codebase, as NativeScript has no DOM to consume that code, so the front end usually will need to be altered for both of these platforms.

The presentation layer of a home page for the web might look like this, with standard semantic-markup and html elements like <div>s and <ul>s:


<header id="header">
        <div class="content">
            <h1>Welcome to PocketRave!</h1>
            <p>Create your own personal sound and light show!<br> Download the mobile app to get started</p>
            <ul class="actions">
                <li><a href="https://itunes.apple.com/us/app/pocketrave-lights-music-art/id1137458667" class="button special">Download for iOS</a></li>
                <li><a href="https://play.google.com/store/apps/details?id=com.ladeezfirstmedia.pocketrave" class="button special">Download for Android</a></li>
            </ul>
        </div>
        <div class="image phone">
            <div class="inner"><img src="../assets/screen.png" alt=""></div>
        </div>
</header>

A presentation tier for a NativeScript app, however, is built differently, using XML markup:

<ActionBar title="PocketRave">
    <ActionItem (tap)="help()" ios.position="right">
        <Button [text]="'fa-question-circle' | fonticon" class="purple fa action-btn"></Button>
    </ActionItem>
</ActionBar>
<StackLayout> 
    <Button (tap)="create()" text="Create a new PocketRave!" class="blue"></Button>  
    <WrapLayout horizontalAlignment="center">
        <StackLayout class="innerCard" width="40%" *ngFor="let rave of (raves$ | async)">
            <StackLayout horizontalAlignment="center">
                <Image [src]="rave.image" height="50"></Image>
                <Label horizontalAlignment="center" textWrap="true" [text]="rave.title"></Label>
            </StackLayout>
        </StackLayout>
    </WrapLayout>
</StackLayout>

How these very different files are handled comes down to the seed’s system of naming conventions and the way the build process is configured to handle files with particular names. Any files that exist only for the web are named as simple .css and .html files (such as the files in PocketRave’s /about folder that are not used on the native mobile app). The /home folder, on the other hand, has implementations for both the web and the native app, so any NativeScript-specific files are designated as such with the addition of tns (for Telerik NativeScript) in their naming convention. Thus home.component.tns.html and home.component.tns.css are the NativeScript home page’s markup and styles. Leveraging the naming conventions of this app allows you to effectively separate the front end in your codebase.

markup_separation

What can I not share?

Sharing may be caring, but some code will just not run on a platform on which it’s not intended to run. It’s easy to leverage naming conventions to fork the front end, but as I mentioned above, we need to share the Angular Component class file (for example about.component.ts). This can present challenges once your app gets complex.

For example, in PocketRave’s Component class below, one immediately notices some funny business in ngOnInit(). What’s all that about Config.IS_MOBILE.NATIVE() and a reference to this.frame?

Well, out-of-the-box the AMPS project supports several methods of protecting your code from non-compatible, platform-specific integrations. One of these methods is the use of some settings in the /utils/config.ts file that is used to specify various platforms (mobile native, web, desktop). Using these flags with conditionals is one method to avoid sharing incompatible code. In this example, it tells the NativeScript app on iOS not to show the default back button on the home page’s ActionBar – never a problem for the web, but a pain on iOS.

import {OnInit, Inject} from '@angular/core';
import {BaseComponent} from '../../../core/decorators/base.component';
import {LogService} from '../../../core/services/log.service';
import {DIALOGS, FRAME} from '../../../core/tokens';
import {FirebaseService} from '../../../pocketrave/services/firebase.service';
import {Observable} from 'rxjs/Observable';
import {Config} from '../../../core/utils/config';
import {Router} from '@angular/router';

@BaseComponent({
  moduleId: module.id,
  selector: 'sd-home',
  templateUrl: 'home.component.html',
  styleUrls: ['home.component.css']
})
export class HomeComponent implements OnInit {
  public raves$: Observable<any>;
  constructor(public firebase: FirebaseService,
              private logger: LogService,
              private _router: Router,
              @Inject(DIALOGS) private dialogs: any,
              @Inject(FRAME) private frame: any
              ) {}

 ngOnInit() {
    this.raves$ = <any>this.firebase.getRaves();
    if (Config.IS_MOBILE_NATIVE()) {
      if (this.frame.topmost().ios) {
          this.frame.topmost().ios.controller.visibleViewController.navigationItem.setHidesBackButtonAnimated(true, false);
      }
    }
  }

A full-featured method to protect your code

A more scalable way to “shield” your platform-specific code is to leverage Angular’s concept of the Opaque Token. According to this article, in Angular 2, we can either use string or type tokens to make dependencies available to the injector. However, when using string tokens, there’s a possibility of running into naming collisions, as the same name might exist for a different provider, especially when using third party libraries. Opaque Tokens solve this problem.

While standard tokens are often simple string primitives, Opaque Tokens are different. They are actual object instances. Using Opaque Tokens allows us to create string-based tokens without running into any collisions. While they were designed to allow developers to protect their code from name collisions, we can use them in this code-shared environment to stop the web and mobile platforms from colliding when certain libraries are not supported, for example, on the web.

Let’s give Opaque Tokens a try!

In the /frameworks/core folder, you’ll find a file called tokens.ts which lists some tokens and stores them in a TOKENS_SHARED constant. For PocketRave, I needed to use many plugins on the NativeScript app that have nothing to do with the web integration, so I listed everything I would use in this file:

import {OpaqueToken} from '@angular/core';

export const FIREBASE: OpaqueToken = new OpaqueToken('firebase');
export const FILE_SYSTEM: OpaqueToken = new OpaqueToken('file-system');
export const ENUMS: OpaqueToken = new OpaqueToken('enums');
export const IMAGE_SOURCE: OpaqueToken = new OpaqueToken('image-source');
export const DIALOGS: OpaqueToken = new OpaqueToken('dialogs');
export const APPSETTINGS: OpaqueToken = new OpaqueToken('appSettings');
export const LOADER: OpaqueToken = new OpaqueToken('LoadingIndicator');
export const COLOR: OpaqueToken = new OpaqueToken('Color');
export const COLORPICKER: OpaqueToken = new OpaqueToken('ColorPicker');
export const AUDIO: OpaqueToken = new OpaqueToken('TNSPlayer');
export const FRAME: OpaqueToken = new OpaqueToken('Frame');
export const SEARCHBAR: OpaqueToken = new OpaqueToken('SearchBar');

export const TOKENS_SHARED: Array<any> = [
  { provide: FIREBASE, useValue: {} },
  { provide: FILE_SYSTEM, useValue: {} },
  { provide: ENUMS, useValue: {} },
  { provide: IMAGE_SOURCE, useValue: {} },
  { provide: DIALOGS, useValue: {} },
  { provide: APPSETTINGS, useValue: {} },
  { provide: LOADER, useValue: {} },
  { provide: COLOR, useValue: {} },
  { provide: COLORPICKER, useValue: {} },
  { provide: AUDIO, useValue: {} },
  { provide: FRAME, useValue: {} },
  { provide: SEARCHBAR, useValue: {} }
];

Then, in separate files (you’ll find them at /src/client/tokens.web.ts and /nativescript/app/tokens.native.ts ), you’ll see a list of those tokens that need to be available for the web, split from those needed for the mobile app. Some plugins can be shared while having different implementations. For example, we use the standard Firebase JavaScript library for its web implementation while on mobile we use the NativeScript Firebase plugin. We hide these two tokens from each other by adding them to separate arrays called TOKENS_WEB and TOKENS_NATIVE:

These are the tokens used on PocketRave.me for the web:

import {FIREBASE, FILE_SYSTEM, ENUMS, IMAGE_SOURCE} from './app/frameworks/core/tokens';
var firebasePlugin = require('firebase');

export const TOKENS_WEB: Array = [
  {
    provide: FIREBASE, useFactory: () => {
      return firebasePlugin.firebase;
    }
  },
  { provide: FILE_SYSTEM, useValue: {} },
  { provide: ENUMS, useValue: {} },
  { provide: IMAGE_SOURCE, useValue: {} }
];

While these are the tokens for PocketRave’s mobile apps:

import {FIREBASE, FILE_SYSTEM, ENUMS, IMAGE_SOURCE, DIALOGS, APPSETTINGS, SEARCHBAR, 
  LOADER, AUDIO, COLOR, COLORPICKER, FRAME} from './app/frameworks/core/tokens';
var firebase = require("nativescript-plugin-firebase");
import * as fs from 'file-system';
import * as enums from 'ui/enums';
import * as imageSource from 'image-source';
import * as dialogs from 'ui/dialogs';
import * as frame from 'ui/frame';
import * as appSettings from 'application-settings';
import { LoadingIndicator } from 'nativescript-loading-indicator';
import { Color } from 'color';
import { ColorPicker } from 'nativescript-color-picker';
import * as audio from 'nativescript-audio';
import * as searchbar from 'ui/search-bar';

export const TOKENS_NATIVE: Array<any> = [
  {
    provide: FIREBASE, useFactory: () => {
      return firebase;
    }
  },
  { provide: FILE_SYSTEM, useValue: fs },
  { provide: ENUMS, useValue: enums },
  { provide: IMAGE_SOURCE, useValue: imageSource },
  { provide: DIALOGS, useValue: dialogs },
  { provide: APPSETTINGS, useValue: appSettings},
  { provide: LOADER, useClass: LoadingIndicator},
  { provide: COLOR, useValue: Color },
  { provide: FRAME, useValue: frame },
  { provide: SEARCHBAR, useValue: searchbar },
  { provide: COLORPICKER, useClass: ColorPicker },
  { provide: AUDIO, useValue: audio }
];

These arrays are injected into the appropriate ngModule for use on the relevant platform:

For the web:

// angular
import { NgModule } from '@angular/core';
import { APP_BASE_HREF } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
import { Http } from '@angular/http';
...
//pocketrave
import { TOKENS_WEB } from './tokens.web';
import { AppComponent } from './app/frameworks/pocketrave/components/app/app.component';
import { ENTRY_COMPONENTS } from './app/frameworks/pocketrave/components/index';
import { routes } from './app/frameworks/pocketrave/routes';
import { PocketRaveModule } from './app/frameworks/pocketrave/pocketrave.module';

...

@NgModule({
  imports: [
    BrowserModule,
    CoreModule.forRoot([
      { provide: WindowService, useFactory: (win) },
      { provide: ConsoleService, useFactory: (cons) }
    ]),
    routerModule,
    PocketRaveModule.forRoot(TOKENS_WEB)
  ],
  declarations: [
    AppComponent,
    ENTRY_COMPONENTS
  ],
  providers: [
    {
      provide: APP_BASE_HREF,
      useValue: '<%= APP_BASE %>'
    }
  ],
  bootstrap: [AppComponent]
})

export class WebModule { }

And for mobile:

// nativescript
import { NativeScriptModule, platformNativeScriptDynamic, onAfterLivesync, onBeforeLivesync } from 'nativescript-angular/platform';
import { NativeScriptFormsModule } from 'nativescript-angular/forms';
import { NativeScriptHttpModule } from "nativescript-angular/http";
import { NativeScriptRouterModule } from 'nativescript-angular/router';
import { RouterExtensions as TNSRouterExtensions } from 'nativescript-angular/router/router-extensions';
import { NativescriptPlatformLocation } from 'nativescript-angular/router/ns-platform-location';
import { NSLocationStrategy } from 'nativescript-angular/router/ns-location-strategy';

...

// app
import { Config, WindowService, ConsoleService, RouterExtensions } from './app/frameworks/core/index';
import { NSAppComponent } from './pages/app/app.component';
import { TOKENS_NATIVE } from './tokens.native';

...

@NgModule({
  imports: [
    CoreModule.forRoot([
      { provide: WindowService, useClass: WindowNative },
      { provide: ConsoleService, useFactory: (cons) }
    ]),
    AnalyticsModule,
    ComponentsModule,
    PocketRaveModule.forRoot([
      TOKENS_NATIVE,
      {
        provide: TNSFontIconService,
        useFactory: () => {
          return new TNSFontIconService({
            'fa': 'fonts/font-awesome.css',
            'ion': 'fonts/ionicons.css'
          });
        }
      }
    ]),
    NativeScriptRouterModule.forRoot(routes)
  ],
  declarations: [
    NSAppComponent
  ],
  providers: [
    NS_ANALYTICS_PROVIDERS,
    { provide: RouterExtensions, useClass: TNSRouterExtensions }
  ],
  bootstrap: [NSAppComponent]
})

export class NativeModule { }

And finally, the tokens are used in the component file as needed. In this case, we use the FRAME token to specify which mobile platform we are targeting:

import {OnInit, Inject} from '@angular/core';
import {BaseComponent} from '../../../core/decorators/base.component';
import {LogService} from '../../../core/services/log.service';
import {DIALOGS, FRAME} from '../../../core/tokens';
import {FirebaseService} from '../../../pocketrave/services/firebase.service';
import {Observable} from 'rxjs/Observable';
import {Config} from '../../../core/utils/config';
import {Router} from '@angular/router';

@BaseComponent({
  moduleId: module.id,
  selector: 'sd-home',
  templateUrl: 'home.component.html',
  styleUrls: ['home.component.css']
})
export class HomeComponent implements OnInit {
  public raves$: Observable<any>;
  constructor(public firebase: FirebaseService,
              private logger: LogService,
              private _router: Router,
              @Inject(DIALOGS) private dialogs: any,
              @Inject(FRAME) private frame: any
              ) {}

 ngOnInit() {
    this.raves$ = <any>this.firebase.getRaves();
    if (Config.IS_MOBILE_NATIVE()) {
      if (this.frame.topmost().ios) {
          this.frame.topmost().ios.controller.visibleViewController.navigationItem.setHidesBackButtonAnimated(true, false);
      }
    }
  }

Conclusion

I encourage you to clone the AMPS project, install it, and spin up your web and mobile apps. Take a look at the way the folders are set up, observe the naming conventions, and take a look at the use of tokens.

Maybe you’ll build the next cool social media platform, business app, or entirely useless art and music integration like PocketRave. With Angular 2, NativeScript, and these Advanced Seeds, the sky’s the limit! Let me know how it goes in the comments section.

Related resources:

Comments

  • Pingback: Angular2 | Pearltrees()

  • Pingback: Dew Dump - November 8, 2016 (#2362) - Morning Dew()

  • cgatian

    The unfortunate part is the lack of AOT compilation for web without some other build process, or commenting out portions of code. I really hope the AOT team allows custom decorators so we can take this to the next level!

    • Shripal Soni

      Just came across this nice tutorial. If you want to create web and native mobile app with single shared codebase and want to have AOT support for web app, you can check this https://github.com/shripalsoni04/nativescript-angular-web-starter. This starter template uses angular-cli for web, so you can use all the features of angular-cli in web app. Hope this will help you.

      • cgatian

        That repo has web and nativescript split into two applications, which isn’t necessary. I wouldnt use it as a guide because you will end up repeating a majority of your application

        • Shripal Soni

          You are right. If you use that starter template, the code will split in three folders: Nativescript, Web and X-Shared (Contains platform independent code). According to me, this is good for medium to large Angular 2+ applications due to below reasons:

          1. No need to write any conditional statements as per web or mobile platforms in component file. This makes code very clean, easy to understand and easy to test.

          2. We can have direct access to platform specific APIs and code intellisense for that into our component files without creating any wrappers. This again results in clean and no extra code.

          3. Lesser maintenance. No need to worry about or test the web when you are doing changes specific for mobile and visa versa as the component files are different.

          4. Ability to share only the code which can be shared. Ideally we can only share services, models, view-models, assets and some unit test cases. As view engine is different for mobile and web, we should create different components and share the common view-logic using services. Separating common code from platform specific code also helps in adding more platforms in future with very ease and without any worries. Because we do not need to touch any existing platform specific code. We can even have separate team member to work on new platform, who can use all the existing shared code in x-shared folder.

          5. Some times we want some features to be available on web only and some on mobile only. In such cases, why should the code specific to one platform be bundled into another platform.

          6. Separate application for web and mobile provides flexibility to use platform specific tool chains without any issue.

          7. Many times, the development of applications for web and mobile do not go parallel and it is phase-wise, like web first or mobile first. In such scenarios, if we plan to create single component for web and mobile, then we need to invest some time in creating wrappers of platform specific APIs and their interfaces beforehand. This can be avoided if we just create separate component files and put all the code which is independent of view into view-model services.

          You can check this https://github.com/shripalsoni04/ngxp-quotes-app, which is created based on the starter template mentioned in above comment. In the code of that app, you can check that even if the component files for web and mobile are different we can share many of the view-logic using view-models. So the overhead is just to create extra component,module and routing files and I think it is good for scalability and maintainability due to above reasons.

          Hope this will clarify why we have separate applications for web and mobile in that starter template. Let me know in case of any suggestion or confusion.

          • cgatian

            1) You already have conditional statements cluttering your components
            `this.utilityService.isSmallScreen()` How is this different?

            2) This makes sense, but really those should be offloaded to a service. One that makes use of Angular’s DI system and allows you to still leverage your intellisense as you build native and web service abstractions.

            3) I would argue duplicating component code will become a maintenance nightmare.

            4) This is true for both scenarios.

            5) Using the DI system and a bundler like Webpack should prevent this from occurring

            6) You can still use the tooling systems available in each. This point is invalid.

            7) This could be true, but in my experience both applications rely on the server API, and they DO traditionally go hand in hand. The development of two applications is typically the reason both platforms are unable to release on the same cadence. In my opinion, the business would want cross functionality as much as possible. If indeed a service for mobile cannot be completed I would utilize something like your `utilityService` to hide unwanted features based on platform.

            We all are trying new things out. My approach to glue the whole thing together as one app may fail miserably. However, I think with Angular’s DI system, and Webpack I will be able to accomplish this goal without adding additional overhead.

            With this glued configuration, adding another type of app like Electron would be trivial. Your approach on the other hand, would require a complete copy paste of your entire application, losing all commit history. Which still sounds like a terrible idea.

          • Shripal Soni

            Thanks for sharing your perspectives 🙂 Its always great to have different perspectives as it helps in creating solid solutions. I have some points to comment here:

            1. this.utilityService.isSmallScreen() is not for identifying native mobile and web platforms. You can check that it is inside web application only. It is used to do some changes on screen when the web app is opened on mobile browsers. That quotes application is created for Android, iOS, web and web mobile.

            2. Agreed. So for each such native modules, we need to create abstract class which has same api as that of native module for intellisense. Similar to what I did for firebase.service.ts in that quotes app to abstract firebase implementation for web and native mobile.

            3. This depends from project to project and person to person. No comments for this.

            4. I have one point to say here. Consider scenario where you have already deployed app for some platforms and later you are adding support for new platform. So if we have view specific code of different platforms in single component file, it can be difficult to maintain that component as we may need to do bug fixes, enhancements etc and release them for already deployed platforms and we need to do development for new platforms in the same file at the same time.

            5. Agreed. We can remove other platform files from build by adding one step in build system. It can be of small amount, but all platform specific codes in component files will still ship to the end product. Though the contribution of that in final build size want be that much for many apps. So this is a valid point to do.

            6. Agreed, we can use the platform specific tooling systems, but it can have some issue. For example, currently AOT will not work if we use any custom decorator on component as mentioned at https://github.com/angular/angular/issues/7968#issuecomment-223779992. So it is limiting us using AOT on web without doing some code changes.

            7. Agreed as it will depend from business to business. Some business wants to launch app on one platform first to see how it goes in real market before doing investments for UI/UX, development, environments etc. for other platforms. So my point of view applies on that. But if some business are confident about the market, then can go for all the platforms in parallel.

            Regarding commit history, if we add new platform later on, it will definitely require new html, css and view related logic in component files. So according to me the commit history of this new code should start from when we start development of that and it will be more clean. And anyways we are going to have full commit history of x-shared code because it is common to all the platforms.

            Its great that we are exploring so many options. Thanks for this nice discussion 🙂

          • Nathan Walker

            These are all really great thoughts. Indeed having different perspectives are crucial to arriving at an ideal state of anything so these are very productive comments.

            As with many things (tech), there are certain use cases that are better suited for 1 setup vs another.

            For instance, if you have a web app with a paired mobile app that should inherit and closely mirror the web app’s functionality in terms of behavior and usability flow (but enabling native mobile features where needed), then sharing Angular components is something to be desired. As is the case with https://infowrap.com, a product I have worked on.

            However, not all mobile strategies are desired to closely resemble their web app counterpart, for example: https://www.instagram.com/
            Instagram has clearly custom tailored their mobile experience to the highest spec of usability ideals using native UI’s on their respective targeted platforms (iOS and Android). Their website/web app does not really reflect their mobile app at all and is a greatly simplified view of the experience. In this case, reusing Angular components would not really be that important.

            So in my personal use case (Infowrap), Shripal’s starter seed would not be the desired setup I would want because we want to reuse components.
            However in Instagram’s case, Shripal’s setup would likely work very well.

            AoT compilation indeed does not work with custom component decorators. Therefore their use should be limited if not potentially phased out altogether. The need for custom component decorators were greatly diminished when NgModule was introduced. The phase out of custom component decorators (to share Angular components across web/native mobile) in the advanced seed is taking place here: https://github.com/NathanWalker/angular-seed-advanced/pull/323 Which will also reign in the power of webpack into the build pipeline which will be able to solve the majority of challenges in providing an AoT build for web and native mobile via NativeScript. I would expect to see this reality come to fruition within the next couple weeks.

            My 2 cents from having built several large web apps with mobile counterparts is to reuse everything that makes sense to reuse. It all just goes to help maintain a large app over several target platforms and reduce feature spikes from diverging away from each other since all target platforms are developed in unison off the same shared codebase (services *and* components).

            Bottom line, AoT is coming for both web and NativeScript apps in a shared codebase via Shripal’s starter seed and my advanced seed (web already possible by removing custom component decorator: https://github.com/NathanWalker/angular-seed-advanced#how-to-start-with-aot-compilation) but {N} app very soon: https://github.com/NathanWalker/angular-seed-advanced/pull/323

          • Shripal Soni

            Thanks Nathan for sharing great insights. Totally agree with you. Its good that we have solutions available which covers most of the use cases. It will be helpful to all 🙂

  • Pingback: FRONT-END WEEKLY DIGEST (14-20 NOVEMBER 2016) | Zfort Group Blog()

  • Jen Looper

    I’d like Nathan to comment on AOT support…