Back to Blog Home
← all posts

NativeScript for Angular 12 Released

June 8, 2021 — by Technical Steering Committee (TSC)

We are excited to release NativeScript for Angular 12 as it is packed with refreshments including a completely revamped engine under the hood. The TSC decided to approach this new major version holistically taking into account from the bottom up all the latest advancements around the Angular framework.

"@nativescript/angular": "~12.0.0"

The full scope of package.json versions you can expect to be using are:

"dependencies": {
  "@angular/animations": "~12.0.0",
  "@angular/common": "~12.0.0",
  "@angular/compiler": "~12.0.0",
  "@angular/core": "~12.0.0",
  "@angular/forms": "~12.0.0",
  "@angular/platform-browser": "~12.0.0",
  "@angular/platform-browser-dynamic": "~12.0.0",
  "@angular/router": "~12.0.0",
  "@nativescript/angular": "~12.0.0"
  "@nativescript/core": "~8.0.0",
  "@nativescript/theme": "~3.0.0",
  "rxjs": "^6.6.0",  // you can use rxjs 7.x.x as well
  "zone.js": "~0.11.4"
},
"devDependencies": {
  "@angular/compiler-cli": "~12.0.0",
  "@nativescript/android": "8.0.0",
  "@nativescript/ios": "8.0.0",
  "@nativescript/types": "8.0.0",
  "@nativescript/webpack": "beta",
  "@ngtools/webpack": "~12.0.0",
  "typescript": "~4.2.0"
}

NOTE: See section below on upgrading as you will need to consider the polyfills.ts file mentioned herein.

Brief History

Some may not be aware that the first Angular integration with NativeScript was written quite a long time ago. The first commit to nativescript-angular, the original integration repo, was June 11, 2015 by Hristo Deshev. It used submodules in those days and was written against 2.0.0-alpha.26!!

That's right. The alpha of the first major Angular rewrite, 2.0. NativeScript for Angular has evolved over the years with each major version but the main engine and structure had always been based on those very early beginning days of modern Angular.

We owe a lot to those early contributors as they paved the way for some wonderful developments over the years.

We love using Angular and believed it was time to approach it from a fresh perspective with all the latest Angular goodies in mind.

Revamped Engine

The core of the new implementation sets out to empower developers even more than previously possible by allowing them to make their own decisions and customize their apps as they see best. We no longer hide NativeScript behind Angular and instead provide a set of helpers that should make developers feel at ease when developing in Angular while targeting the NativeScript platform.

It also sets the stage for Angular's CDK to start blossoming within the integration with new portal based api's around modal dialog handling and other niceties. More on this in a subsequent post.

Versatile boot options

The current bootstrap method used in your NativeScript Angular apps has been made to be backwards compatible so this should work fine for existing apps:

platformNativeScriptDynamic().bootstrapModule(AppModule);

We have developed a new bootstrap method which allows for some new exciting and dynamic configurations. We will go into further detail about this in a subsequent blog post in couple weeks. We encourage everyone to switch their app to use this when upgrading to Angular 12:

import { runNativeScriptAngularApp, platformNativeScript } from '@nativescript/angular';
import { AppModule } from './app/app.module';

runNativeScriptAngularApp({
  appModuleBootstrap: () => platformNativeScript().bootstrapModule(AppModule),
});

Let's take a moment though to expand on at least a little bit about this...

Application Lifecycle

NativeScript has it's own Lifecycle that was previously hidden behind nativeScriptPlatformDynamic. Every app has a launch event and an exit event. Your Angular AppModule has always been bootstrapped when the native iOS and Android app launches and destroyed when the app exits. While your app may still run in the background in many ways, it was not guaranteed that your AppModule would be alive in some scenarios. This new API makes it more clear what's going on:

import {
  runNativeScriptAngularApp,
  platformNativeScript // this is the actual platform used to boot a module into a NativeScript View
} from '@nativescript/angular';
import { AppModule } from './app/app.module';
import { LoadingModule } from './app/loading.module';

// The entry point.
// This signals in your code that from now on we're running the app.
// Nothing is bootstrapped imperatively. Everything now comes from generator functions.
runNativeScriptAngularApp({
  // This is an event implementation.
  // Every time we need the main module bootstrapped, you're required to resolve a NgModuleRef via a Promise, which is what `bootstrapModule(...)` does.

  // Booting your AppModule.
  appModuleBootstrap: (reason: string) => platformNativeScript().bootstrapModule(AppModule),
  // Note: The `reason: string` argument should be either 'applaunch' or 'hotreload'.

  // 🌟 Amazing new abilities here. 
  // Bootstrapping a module that's not the AppModule!?
  // For the async APP_INITIALIZER users out there, you can now bootstrap a small (synchronous) module to show until your main AppModule is loaded.
  // This allows you to better isolate an entirely encapsulated rich launch experience (which is only experienced once in your app's lifecycle by your users)
  loadingModule: (reason: string) => platformNativeScript().bootstrapModule(LoadingModule),

  // A feature from the previous versions that makes a return here, for example:
  // launchView: () => {
  //   const grid = new GridLayout();
  //   grid.backgroundColor = 'yellow';
  //   return grid;
  // }
});

This new function will handle launch and exit events and ask you to bootstrap your main app when it's needed. It'll destroy the module on app exit, as it always did, but now you can wire up your own logic as you're the one passing the NgModuleRef. Since we're also exposing the whole platform, there's no limit for how many modules you can bootstrap.

Modules will always bootstrap themselves into a "dummy" NativeScript view that @nativescript/angular makes the Application's Root View. By using the token APP_ROOT_VIEW, it's also possible to make a module bootstrap in any View, very similar to how web components work! We will discuss how you can provide your own logic for Root View handling in a future blog post.

What's quite exciting about the new API is you are no longer limited to bootstrapping only UI-based Modules (ya' know like the main AppModule of the entire UI of your app). Nothing prevents you from bootstrapping your own BackgroundModule that doesn't bootstrap any component, but allows you to use its injector for goodies like HttpClient in a background service! For coordinating between different modules, we recommend using Angular's own providedIn: 'platform', which will allow for a singleton service shared across the whole platform (and its modules)! Just beware that this platform is only destroyed during HMR and you can't use services in it that depend on module providers (so no HttpClient!). You can find more information in the Angular docs.

Zone.js

Zone.js also has seen a major uplift. We no longer provide a custom Zone.js implementation. We previously relied on plugin developers and @nativescript/core to provide callbacks wrapped inside a ZoneCallback to avoid pesky issues where your views wouldn't update after an event, so many developers had to wrap those callbacks into a ngZone.run.

We now use the zone.js package as is and provide our own patches for NativeScript APIs. This means most plugins that previously required a ngZone.run are now probably safe to use. If you're a plugin developer and you're not using Observable (or if you're overriding Observable's addEventListener) for handling events and callbacks, we also provided an API for you to patch your custom event callbacks in your Angular integrations. More on this in subsequent post as well.

The Angular team added to their roadmap they're looking into making Zone.js opt-out, and we're already working on it to make sure you can use NativeScript zoneless when the time comes!

Revamped HMR

We revisited HMR integration and found a number of ways to improve upon it and you should experience better HMR all around.

There are a couple things to keep in mind with iOS specifically. Android is fine in this regard.

  • iOS

    • If you stop the command line (when using ns run or ns debug, etc.) with Ctrl+C and the app remains running on the Simulator or Device (happens often) - you most likely will need to force quit the app on the Simulator or Device before running again for HMR to be engaged probably again so please keep this in mind.

Control of your own polyfills

Each NativeScript Angular 12 app now provides control of your own polyfills just like Angular web apps do.

You will find a ./src/polyfills.ts file when creating a new project with ns create myapp --ng. These polyfills can be added into any existing project you are updating as well; more on upgrading below.

The contents must at least contain the following:

/**
 * NativeScript Polyfills
 */

// Install @nativescript/core polyfills (XHR, setTimeout, requestAnimationFrame)
import '@nativescript/core/globals';
// Install @nativescript/angular specific polyfills
import '@nativescript/angular/polyfills';

/**
 * Zone.js and patches
 */
// Add pre-zone.js patches needed for the NativeScript platform
import '@nativescript/zone-js/dist/pre-zone-polyfills';

// Zone JS is required by default for Angular itself
import 'zone.js';

// Add NativeScript specific Zone JS patches
import '@nativescript/zone-js';

This provides you with the same power that Angular web apps have in controlling what exactly is polyfilled in your app. For most users you won't have to think about this file but the power is now under your fingertips if you ever need it.

Upgrading

  1. If you are upgrading your app you can update your package.json versions to the following:
"dependencies": {
  "@angular/animations": "~12.0.0",
  "@angular/common": "~12.0.0",
  "@angular/compiler": "~12.0.0",
  "@angular/core": "~12.0.0",
  "@angular/forms": "~12.0.0",
  "@angular/platform-browser": "~12.0.0",
  "@angular/platform-browser-dynamic": "~12.0.0",
  "@angular/router": "~12.0.0",
  "@nativescript/angular": "~12.0.0"
  "@nativescript/core": "~8.0.0",
  "@nativescript/theme": "~3.0.0",
  "rxjs": "^6.6.0",  // you can use rxjs 7.x.x as well
  "zone.js": "~0.11.4"
},
"devDependencies": {
  "@angular/compiler-cli": "~12.0.0",
  "@nativescript/android": "8.0.0",
  "@nativescript/ios": "8.0.0",
  "@nativescript/types": "8.0.0",
  "@nativescript/webpack": "beta",
  "@ngtools/webpack": "~12.0.0",
  "typescript": "~4.2.0"
}
  1. As mentioned above you will also need to create a ./src/polyfills.ts file with the following:
/**
 * NativeScript Polyfills
 */

// Install @nativescript/core polyfills (XHR, setTimeout, requestAnimationFrame)
import '@nativescript/core/globals';
// Install @nativescript/angular specific polyfills
import '@nativescript/angular/polyfills';

/**
 * Zone.js and patches
 */
// Add pre-zone.js patches needed for the NativeScript platform
import '@nativescript/zone-js/dist/pre-zone-polyfills';

// Zone JS is required by default for Angular itself
import 'zone.js';

// Add NativeScript specific Zone JS patches
import '@nativescript/zone-js';
  1. Now modify your tsconfig.json to contain the following modification (note the mention of polyfills):
"files": ["./src/main.ts", "./references.d.ts", "./src/polyfills.ts"],
  1. Lastly you can update your ./src/main.ts to use the new versatile bootstrap api:
import { platformNativeScript, runNativeScriptAngularApp } from '@nativescript/angular';

import { AppModule } from './app/app.module';

runNativeScriptAngularApp({
	appModuleBootstrap: () => platformNativeScript().bootstrapModule(AppModule)
});

Now ns clean and give it a run.

Angular 12 Testing

We've deprecated all of our previous nsTestBed helpers! They're just not needed anymore, as we implemented whenRenderingDone and we're now using Angular's own handling of fixtures. They still work, but you may get some warning messages when using them. This is how your tests should look now:

  • tests-main.ts (your first test entrypoint)
import '@nativescript/core/globals';
import '@nativescript/angular/polyfills';
import '@nativescript/zone-js/dist/pre-zone-polyfills';
import 'zone.js';
import '@nativescript/zone-js';
import 'zone.js/testing';
import { TestBed } from '@angular/core/testing';
import { NativeScriptTestingModule } from '@nativescript/angular/testing';
import { platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';

TestBed.initTestEnvironment(NativeScriptTestingModule, platformBrowserDynamicTesting());
  • your.spec.ts
describe('your spec', () => {
  beforeEach(() => TestBed.configureTestingModule({
      declarations: [YourComponent],
      imports: [],
      providers: [],
    }).compileComponents());
  it('creates', () => {
    const fixture = TestBed.createComponent(YourComponent);
    fixture.detectChanges();
    expect(fixture).toBeDefined();
    expect(fixture.componentRef.instance).toBeDefined();
  });
  it('renders', waitForAsync(async () => {
    const fixture = TestBed.createComponent(YourComponent);
    fixture.detectChanges();
    await fixture.whenRenderingDone();
    // do other tests with native views
  }));
});

Angular Router & Navigation notes

We are providing the same api your app's are using today with regards to Angular Router integration, for example page-router-outlet, RouterExtensions, etc.

We don't expect any particular route issues your app may have experienced over time to be resolved by this update however we wanted to provide you the ability to update to Angular 12 today and should work as you are used to.

Alongside this release we have a new navigation api in the works with this revamped integration. We will introduce a way for your apps to use the new api's for routing improvements this summer.

Upcoming

NativeScript 8 and Webpack 5 brought some exciting new features that made all this possible, but we're not done yet. As a sneak peak into the future, we already managed to make NGRX in NativeScript work nicely with the standard Redux Devtools and we're looking forward to finishing up the details so we can make developing an Angular app with the full NGRX state management capabilities even easier! Developer Experience is just as important as User Experience, so we expect to keep improving the way you work with NativeScript for Angular.

New and easy to maintain source repository

If you are curious to poke around at the revamped integration and contribute to anything you may find when updating your apps, we absolutely love contributors:

https://github.com/NativeScript/angular

It's organized like a lot of NativeScript repo's now, using Nrwl's Nx.

Cheers to Angular!

Great work to the Angular team for providing a versatile framework capable of a vast amount of fascinating cross platform work.