A Christmas Story with Nx, Angular and NativeScript — Part 1
- Introduction
- Santa goes mobile
- Technical challenges
- The gift of NativeScript for Angular
- The 3 repo circus
- The gift of Nx
- The Nx advantage
- The Ngrx key to unlock full code sharing potential
- Dynamic Forms with Reactive Forms
- Angular and RadListView — Reduce, Reuse, Recycle
- Reusing legacy Objective C/Swift/Java code
- Last words: “700 ms should be enough” — A lesson in mixing Angular lifecycle hooks with NativeScript services
- Let’s meet the team in Part 2
- ngATL 2018 Conference
- NativeScript for Angular book
- nStudio can help
There is magic in the air around the holidays. It surrounds us in all sorts of ways whether it’s the excitement of seeing family and loved ones from afar, the beautiful lights strung about our cities, the delightful chorus permeating the airwaves or the hope of a new year.
I’d like to share a magical story this season which brings fruitful gifts and joy to many families around the world every year. It begins in Montréal, Québec with a neat company named UGroupMedia Inc., the creators of Portable North Pole, “An online platform used by Santa and his elves to send personalized video messages and calls to the people you care about most anywhere in the world.”
Wait a minute…Santa isn’t real right? Well you might be surprised to learn he is alive and well on the web right here:
https://www.portablenorthpole.com
A web app built atop the foundations of the mighty Angular amongst a small team of developers all working within an Nx workspace made possible by the wonderful folks at Nrwl. At the time of this writing, the core web dependencies look like this:
- @angular/* 4.4.4
- @ngrx/* 4.1.1
- @ngx-translate/* 7.1.0
- @nrwl/nx 0.2.2
- rxjs 5.5.2
For the past 10 years (since 2007!) UGroupMedia has been delivering a unique customized Christmas experience to families all over the globe, officially localized in 4 languages at the moment (English, Spanish, French and Italian). Doing so has involved Angular since 2010 when it was officially released.
Santa goes mobile
The primary audience for Portable North Pole are families wanting to share in the excitement of Christmas with their kids. The products offered by Portable North Pole are videos and games often enjoyed in the comfort of individual family homes all of whom were excited to download an app which provided value they could open to create and share videos they create throughout the holidays with their loved ones.
The team started offering specialized mobile experiences on iOS and Android including the ability to capture a video reaction of a recipient while they are watching a personalized video message from Santa. This experience involved several technical challenges including video layering, audio mixing and background file uploading which were not easily achieved in a mobile web browser alone. Additionally they began offering fun games which could be played all within the iOS and Android mobile apps which were written purely in Java for Android and Objective C/Swift for iOS.
Therefore you can also drop some Santa magic on your phone:
- iOS: https://itunes.apple.com/us/app/pnp-2016-portable-north-pole-create-santa-videos/id902026228?mt=8
- Android: https://play.google.com/store/apps/details?id=com.ugroupmedia.pnp14
The various apps account for millions of free and paying users a year.
Technical challenges
UGroupMedia had been considering several technologies to achieve the following immense challenges:
- Upgrade legacy Angular 1.x web app to modern versions or change frameworks altogether
- Reduce maintenance costs of 3 separate code repositories each containing different languages (JavaScript/Web, Objective C/iOS, Java/Android)
- Improve ease of feature development in unison across all product deployment targets (web, iOS and Android)
- Robust backend data-driven dynamic forms (validation, fields with dependencies on other fields) to allow management to create entirely new products on the fly without development team involvement during peak season
- Maximize code reuse
- Speed up development workflow
- Automated Travis CI integrations for web/aws deployments as well as TestFlight automation for iOS and Google Play deliveries
- Just to make things a tad more daunting, UGroupMedia had spent 5 years developing Android/Java and iOS/Objective C/Swift games for their Android and iOS apps. It was imperative we limit wasteful time recreating what had already been done.
- Target 5 month delivery schedule
You read that correctly. Five month delivery schedule. Start time was May 2017 with a target initial deployment on the web as well as on the Apple App Store and Google Play by October 2017.
The first choice was to decide if Angular should be continued or a new platform pursued. After looking at several technologies it was decided early on that TypeScript provided enormous value to productivity and maintenance in addition to the fact that Angular had already been used to build the existing version. It was also decided that the new platform would be built from scratch rather than pursuing an incremental upgrade approach to Angular via something like ngUpgrade. Rewriting an existing legacy app in a new language (TypeScript) can be a frightening task however we remained confident that it would not only result in a cleaner overall end result but would provide us the power we needed to make deep changes with great degree of confidence where needed.
With the core framework choice out of the way, that left how to “Reduce maintenance costs of 3 separate code repositories each containing different languages (JavaScript/Web, Objective C/iOS, Java/Android)”? Since the team had written a significant amount of code in Objective C and Java we knew we wanted to reuse as much as we could given the tight timeline. We didn’t have time to write a wrapper around mobile api’s the app needed but rather needed to call them directly without further maintenance delay and preferably without leaving the speed and comfort of our preferred IDE’s of choice (VS Code and WebStorm). This is where the stack to address these mounting problems came into clear focus.
The solutions to these challenges are all truly magical gifts from the North Pole (They might as well be directly from Santa):
- Angular: An open source platform to build robust applications.
- Nx: An open source toolkit for enterprise Angular applications.
- Ngrx: An open source toolkit for RxJS powered architecture inspired by Redux.
- NativeScript: An open source framework for building truly native mobile apps.
The gift of NativeScript for Angular
In a multi-platform product deployment project, bringing NativeScript into the mix can really help align development efforts and goals for feature parity with it’s web counterpart. At the time of this writing, the core iOS and Android dependencies look like this:
- @angular/* 4.4.4
- @ngrx/* 4.1.1
- @ngx-translate/* 7.1.0
- @nrwl/nx 0.2.2
- rxjs 5.5.2
- nativescript-angular 4.4.1 ({N} custom renderer for Angular)
- tns-core-modules 3.4.1 ({N} core modules for iOS and Android)
You may notice this is identical to our web app stated above with 2 additional core dependencies on NativeScript libraries. The custom renderer written by the core NativeScript team for Angular (nativescript-angular) is the magic to this combo. NativeScript is versatile and can work with many frameworks including Vue for instance. The brilliance in the heart of NativeScript comes from tns-core-modules which is a lightweight abstraction layer providing a single condensed and consistent api to a myriad of native iOS and Android device specific api’s. This gives you a single set of api’s to deal with rather than worrying so much about Java and Objective C/Swift to target both platforms.
However the most exciting NativeScript benefit to me is in the versatility and strength of using TypeScript to interact directly with iOS and Android api’s. An example of this is mentioned further in this article. The power of combining NativeScript with Angular opens up a rather limitless range of development possibilities to product deployment/delivery.
The 3 repo circus
We initially started with 3 separate code repositories:
- shared libs: common code which could be shared between web and native mobile apps
- web app: Angular CLI setup for web app (consumes shared libs)
- mobile iOS and Android apps: NativeScript app (consumes shared libs)
There are pros and cons to the 3 repo circus:
- Pros: isolation of conflicting changes and clarity of platform focus
- Cons: Slow integration workflow, high maintenance costs (CI integrations for each), slow to test shared lib changes against target platforms
This worked fairly well the first 2 months while developer specialists were largely focused on either web or native mobile but the pain really began arising certainly in the 3rd month. Oftentimes web specialists would push code we found could easily be refactored into shared libs and immediately shared directly with the native mobile apps. However doing so involved a time consuming process of making changes to the shared libs project, compiling, packing, and then running against the other projects to ensure it worked everywhere.
We desperately needed to make changes to shared libs much faster and with confidence that it worked in web and native mobile apps.
What about angular-seed-advanced?
I had co-developed angular-seed-advanced a couple years ago in the early days of modern Angular/TypeScript as a natural expansion to Minko Gechev’s angular-seed which I was contributing on at the time. It was a proving ground for a lot of what I desired in multi-platform development. It was also intended to maintain it’s parental roots which were rooted in SystemJS. Over time webpack evolved and once my 2016 AngularAttack 48 hour hackathon teammate, Sean Larkin, got involved I’d say the story now speaks for itself. Eventually the Angular team adopted webpack with Angular CLI and SystemJS became harder to manage with multi-platform development. The setup works today and exemplifies one way to maximize shared code across not only web and mobile deployment targets but desktop as well via Electron.
However there were some pain points with the seed which made it not ideal. Largely build tooling which exceeded the ease of maintenance I had always desired. Additionally mixing platform specific view templates (NativeScript .tns.html) side by side with each other (web .html) I had found often lead to developer confusion and slowed down workflow speed. It was also designed on some hopes and dreams that custom component decorators would eventually play nice with AoT (ahead of time) release builds which never came to fruition. Since my intention with the seed was to never stray too far from it’s parent the idea of re-rooting it’s fundamental setup was not an option.
The gift of Nx
Nx solved all of this in elegant ways with greater simplicity.
Nx showed up to party a bit late but not without plenty of party favors and the timing was akin to divine intervention. Victor Savkin published an article announcing the availability of Nx near the beginning of October [2017] and it stopped me in my tracks. The enduring feeling of love bubbled all over and I became starry eyed. Honestly Victor and Jeff have always been angels of glory to me (sheer brilliance to be quite frank) and likely to many of you as well. Nx immediately peaked my interest so I took it for a Saturday stroll largely to ensure I could use NativeScript inside the setup and much to my excitement it worked beautifully all while consuming libs shared with web apps as well. You can learn how to use NativeScript with Nx on egghead here.
Within 2 days I had converted all 3 repos to Nx and we had the entire team using the single Nx workspace the following Monday. Say goodbye to 3 separate repositories and hello to sweet development speed.
The Nx Advantage
Let’s look at a diagram of how this looks with the Portable North Pole frontend architecture using an Nx workspace:
We can see that libs serve as the foundation in which all business logic and data flow reside. Things like authentication data flows, api services and feature action/effect chains which are the same for both web and mobile live in core. When sharing code between web and mobile apps the main difference within an Nx workspace is that all web dependencies (including web plugins) are maintained in the root of the workspace whereas mobile dependencies are managed solely within the mobile app’s name-spaced directory. This allows the mobile app to consume anything it needs from the root as well as safely isolate it’s mobile specific libs from the rest of shared workspace.
Let’s take a look at what the dependency management looks like for web and mobile side by side:
The wonderful thing here is that changes to libs can trigger livesync updates on the web app in addition to updating mobile apps. See here for an example in action.
The Ngrx key to unlock full code sharing potential
In my opinion, ngrx is the single most important and critical element to unlocking full code sharing potential in a multi-platform project.
It’s conceptually inspired by Redux and is the gift that keeps on giving. Separation of concerns lives strong in ngrx and it’s exactly the kind of strength I have found works best when trying to maximize code reuse with stability and predictability in mind. The ngrx project is maintained by some fantastically talented folks and their priceless effort does not go unnoticed in the community. If you’re able to contribute back to the open source you use and are interested in helping to ensure a great ngrx future you might consider chipping in whatever is reasonable on the Open Collective.
To learn more about ngrx, please check out Todd Motto’s wonderful course.
Let’s look at an example of a login effect which is shared across web/mobile apps:
The Effect handles any dispatch to the LoginAction which can be dispatched from literally anywhere in any of the apps which can inject ngrx Store. There’s a lot of power in those few lines. It’s a single chain which handles the data flow for loading a user account onto the Store for web, iOS and Android upon logging in/creating account. It also seamlessly enforces legalities around user registration which require a user be at least 14 years old. If the loaded user object doesn’t satisfy legal requirements it routes the data through a different effect chain which ultimately results in a legal age prompt verification screen on web and both iOS and Android mobile apps:
To achieve maximum code sharing we not only want to share the data flow but also want to share common component traits. The interface above is actually sharing a common base level class between all views you see there. This can be achieved by abstracting the common properties and methods of the component into a base level abstract class inside libs which can be consumed by all target apps:
Not only does this work well but provides a lot of bang for the buck. This approach is easily maintainable, alleviates developer confusion from mixing platform specific code in a single component class and works flawlessly when run through AoT (Ahead of Time) compilation.
Dynamic Forms with Reactive Forms
PNP’s management team wanted the ability to create new products [videos] at anytime during the holiday season without development team intervention. Achieving this involved creating an abstraction around how the backend form data was collected and projected in the web and mobile apps. Angular provides Reactive Forms which are perfect for this level of abstraction.
Todd Motto published an article which sparked a great deal of inspiration in me while working on this feature. I recall reading it for the first time and becoming energized by the thought and power that Angular enables in his approach. In fact his approach worked “as-is” for NativeScript as well. Here’s a brief example:
The fields are populated via backend data and projected into an ng-container
which resolves it's own form component via the appDynamicField
directive.
I talk about this more as part of Sebastian Witalec’s talk from NativeScript Developer Days 2017.
Angular and RadListView — Reduce, Reuse, Recycle
There’s a couple critical things you’ll want to be aware of when mixing these 2 party animals. RadListView is a very powerful mobile UI component which takes advantage of highly optimized list views on both iOS and Android. The most surprising and ultimately frustrating thing Angular developers will run into here is when you use custom Angular components as the item row template:
That CustomAngularComponent
will not behave like you might expect leading to all sorts of audible expletives. There is a simple answer: The row's are recycled. Therefore if 6 rows are in view on your iPhone for example, 6 instances of that Angular component will be created and reused/recycled as the user swipes/scrolls rapidly through the list. Note: If you only have 10 or so less items and your device’s screen is large enough to accompany most of them you may not experience what I'm about to describe.
Best Practice Note: Whether you have a small or large list you should always set your custom Angular components up according to these details to avoid common pitfalls when integrating Angular with RadListView.
Since only a limited number of Angular components are instantiated this means that ngOnInit
is going to get called for the limited initial set of components. This means you will want to use ngOnChanges
if you desire for various binding updates to occur while the user scrolls however be careful: Do so conservatively. Be aware that ngOnChanges
may fire many times throughout the scope of a user's interaction with your list. Scope your conditions appropriately to consider both sides of any condition.
For example let’s say that showGears
is a boolean bound to a *ngIf
applied to a Image
which should display on some rows whose item's are processing but not on others:
This will show the gears for the item whose property is processing
no problem. However as you scroll further down the list, you will notice that the gears may show for others that are not processing. This happens because as the user scrolls the rows are recycled hence the Angular components are also recycled meaning the one which showed properly is getting reused to show another but it's showGears
property was not reset. Therefore:
This will ensure that the showGears
property is set properly for each item as the user scrolls through the list.
Reusing legacy Objective C/Swift/Java code
Since this project had been developed over a 10 year period there was a lot of legacy code around. That doesn’t necessarily mean we want to abandon it. For example, the backend with Portable North Pole expected base64 encoded images in a particular way. NativeScript provides a method to generate base64 from an ImageSource
however it wasn't exactly the format this particular backend needed when encoded on a mobile device. We just grabbed the legacy code, dropped it in and moved on thanks to NativeScript.
Potentially a couple days of trouble reduced to a matter of minutes.
Last words: “700 ms should be enough” — A lesson in mixing Angular lifecycle hooks with NativeScript services
Things are great when you’re getting rave reviews:
But panic sets in when they go South and for good reason.
We had a bad bug in this project which was dificult to find and reproduce. QA Dept had a hard time reproducing it as did us developers. On faster devices this appeared to happen more readily as well as on faster networks. We had ourselves a nasty race condition.
The essence of the issue came down to an assumption that ngOnInit
would fire at a consistent time when mixed with using NativeScript's ModalService which turned out to be problematic in this particular case. The component displayed in a modal for a brief moment (with localized text 'Please wait a moment...') while an image was repositioned and after "700 ms" it would close itself. Here's what the code was doing:
Basically after 700 ms
just close itself since we found that to be adequate time for this modal view to appear and then go away. One of the problems with this logic is that on iOS modals are animated in. Since animation is used (by default) natively to present that modal view using NativeScript's ModalService, depending on device speed and numerous conditions, the Angular component itself would be constructed early well before it actually slid up vertically to appear on screen. What would happen is that in these rare cases, the component would fire the timeout to close before it was ever finished opening. iOS does not like it when a view controller is in the process of presenting another and a call to dismiss occurs at the same time. It won't crash the app but users would often get a dead black screen of death when this would happen. This is the exact iOS Warning which would show up in the logs when this problem occurred:
Warning: Attempt to dismiss from view controller <UIViewController: 0xf296ab0> while a presentation or dismiss is in progress!
The answer was to use a more appropriate Angular lifecycle hook:
This lifecycle hook would fire consistently after the modal had fully opened and shown the view. For good measure we bumped the timeout to 1500
and the rest is history. Lesson learned the hard way. Don't let it happen to you!
Let’s meet the team in Part 2
In Part 2 (coming soon!), we will meet several members of the team including accounts of their development experiences in this effort.
ngATL 2018 Conference
I will be hosting a 2 day workshop at the exciting ngATL Conference coming up Jan. 30 — Feb. 2 2018 covering many of these topics and more. Please join Sebastian Witalec, Nitish Dayal and myself on the adventure — we look forward to seeing you there!
NativeScript for Angular book
https://www.packtpub.com/web-development/nativescript-angular-mobile-development
This book focuses on the key concepts that you will need to know to build a NativeScript for Angular mobile app for iOS and Android. Build a fun multitrack recording studio app, touching on powerful key concepts from both technologies that you may need to know when you start building an app of your own. The structure of the book takes the reader from a void to a deployed app on both the App Store and Google Play, serving as a reference guide and valuable tips/tricks handbook.
By the end, you’ll know the majority of key concepts needed to build a successful NativeScript for Angular app.
nStudio can help
nStudio would love to help you and your team with similar projects. Contact us anytime to discuss objectives and scheduling: team@nstudio.io