We just recently launched the Android and iOS versions of our first Ionic 2 application. The Schlep app, built for longtime client Schlep, lets Schleppers manage their upcoming jobs, and search a job board for new jobs they can take. Schleppers supply the vehicles and muscle to help clients move heavy items across town or just up a few flights of stairs (clients schedule the requests through Schlep’s eCommerce site).
Introducing the app lets Schlep move from a top-down process where Schlep operations staff assigned each incoming job request to a more hands-off, bottom-up approach where Schleppers themselves are responsible for setting their schedules.
When we began discussing this app’s development, we knew we would be working under some pretty tight constraints. We only had a couple months to complete the app, and most of the time only I would be working on this app’s development, with some significant design assistance from Kurt. Yet despite the limited manpower, we still needed to produce apps for both the Android and iOS platforms.
I already had some experience working with Ionic (v1), and the whole team was experienced in Angular, so looking to Ionic to help us produce a high-quality hybrid mobile app seemed natural. The next choice was whether to stick with Ionic/Angular 1, or move to Ionic/Angular 2. We didn’t have much experience with Angular 2, and there was a good reason for that: Angular 2 was still in beta, as was Ionic 2. Nonetheless, we wanted to build Schlep an app that was ready to expand and be built upon for the next couple years, and we thought Angular 2 was the best foundation for that plan.
What Was Great
One of the most impressive aspects of developing the Ionic 2 app was just how mature and stable both Ionic 2 and Angular 2 were. It came as a surprise; I had expected to run into far more difficulty during this project. That’s not to say that everything was perfectly stable, especially with all of the changes Angular 2 made as it moved into its release candidate phase. But far more often than not, both frameworks were exceedingly reliable for being betas, and once I got used to them I completely stopped worrying about whether it would be prudent to ship a production app on beta frameworks.
The most significant problem I ran into with regards to the stability of Angular 2 was how the pervasive and numerous changes made as Angular 2 made its way from alpha to beta and then release candidate left much of the existing documentation outdated and frequently of little use.
I really enjoyed how clean and understandable the architecture of Angular 2 was. Building with components (which are also available in Angular 1.5) was much more straightforward than working with the Angular 1 directives, especially their directive definition object and link method. That providers — Angular 2’s replacement for Angular 1’s services — were scopable either to the entire app or to a particular component and its children also made a lot of sense to me.
Angular 1 advised that services could be used for inter-component (directive, controller, component, etc.) communication. However, that could be an awkward implementation if more than one instance of a service needed to be present at once, as Angular 1 services seemed most at home in singleton configurations. With Angular 2’s component-scoped providers, you can build implementations such as a TabContext provider that helps a tab container, selects, and panels interact, while still having multiple tabs on a single page.
It’d be difficult to have a discussion of Angular 2 architecture without mentioning the core role that observables play. Building the entire system as an observer of a series of events representing changing application state really helped me identify a fuller range of states for the providers I built, and made it easier to understand how those states interact. And the interaction itself was easier to wire together, as app-wide providers exposing observables made it a breeze to publish state events out to multiple subscribers.
Another feature that helped to build a clean codebase was TypeScript and, by extension, ES6. Coming from a Java background, the type system in TypeScript was very easy to pick up and had a comforting familiarity. The type system really came in handy when dealing with observables, as they served as excellent reminders of what kind of data the observable was expected to emit, long after I had forgotten.
Finally, clearly defined and well documented lifecycle hooks were a huge boost. The app makes widespread use of lifecycle hooks to prepare views and then clean up (observable) listeners and other resources. Ionic goes further than Angular in this regard, adding a quartet of methods to its Page component that provide very fine-grained control over how a page handles being made visible and its subsequent dismissal.
What Was Not Quite So Great
One of the first problems I ran into was finding that templates felt very fragile. Where Angular 1 was pretty tolerant of mistakes and missing values, Angular 2 seemed quite strict. To make matters worse, when those templates did find fault in my code, they seemed to fail silently. Instead of an error message, or a partially rendered template, I would instead find a blank page and equally empty console log — which was worse.
At those points, being new to the framework and not having error messages to clue me in, I ended up just reverting my most recent changes, one at a time, until the app was back into a working state. Eventually I was able to solve all of those problems (many of them were due to reference errors), but it certainly wasn’t a very efficient way to work.
Along the same lines of difficult to troubleshoot errors were the errors surrounding observables. Frequently the stack traces that popped out into the console seemed to have little relationship to any specific piece of the app I was developing, not even mentioning a component or script from my project that would help me start to track down the root cause of the error.
Some of this was resolved by writing better error handlers for my observables, some is just avoided now because I’m more experienced in writing observable-centric code and don’t trip myself up as much. But there are still rare occasions when observable errors pop up and contain no obvious links to my own code, so I don’t have this issue entirely solved yet.
With much Angular 1 experience under my belt, I have a pretty good command of promises, and building apps around them. Observables, which take center stage in Angular 2, are a completely different beast. While promises are still useful in some situations (especially for simple representations of operations that can be considered one-time events), observables are far more powerful and configurable. They’re also — unsurprisingly — a lot more complex.
Building an application around a series of events that convey the application’s state took a while to get my head around fully. I don’t think I’ve ever rewritten so much code in so little time. But what’s resulted is a much more capable and reliable system than I could have built with promises alone.
Styling Ionic 2 apps is one area where a lack of documentation or ready examples led to difficulties. The design effort was led by Kurt, and we were able to get the app looking at least as good (and often better) than Kurt had originally mocked up in Sketch.
The looks are backed up by clean Sass and a well-organized and consistently used component system. However, getting there required writing a lot of our own styles, and ignoring those styles provided by Ionic 2. Was there a better way to mold the existing Ionic 2 styles into what we wanted without blowing them away or ignoring them completely? I think so, but we never got there.
It took a lot of extra work to get the Schlep app built in Ionic 2. There were a lot of things that would have been more familiar, more straightforward, more predictable if we had developed the app with Ionic 1.
Building both a mental model for how a well-architected Ionic/Angular 2 app works, as well as building the app out itself, made for a lot of long days and late nights. Did I have my doubts about whether we made the right call in selecting Ionic 2? Certainly, especially during the first couple weeks.
Did we make the right call, and was all the extra effort worth it? Absolutely. The app takes advantage of the new features in Angular 2 and Ionic 2 to assemble a stronger architecture than would’ve been achieved with Ionic 1. It is both a better app now — for instance, picking up the speed and efficiency improvements the Ionic team worked hard to bring to v2 — and a better app going forward as it supports new stages of development over the coming years.
And we ended up getting the entire thing done with some time to spare, so we were able to spend extra time testing the app and improving its resiliency, especially in face of server errors or poor/unreliable network conditions. All in all, it was a great experience and it left me looking forward to the next Ionic 2/Angular 2 project we take on.