Why I Have Not Moved to AngularJS 2

I evaluated AngularJS2 at the end of April, and have not revisited it since, so here are the reasons I decided not to migrate to AngularJS2, based on the Beta release back then... (Note that I am not familiar with TypeScript.)

  • Template and controller in the same file and tied directly together (via decorations on the "controller") make for poor MVC separation (?) Wait, are components like directives?
  • Styles being scoped to a component is cool, but that's just Web Components.
  • Component Router exposes route parameter names in Router.navigate. May be an unnecessarily tight coupling (changing position requirement to name requirement).
  • AngularJS 2 is not yet production ready (at least the Beta I evaluated). Errors with `npm start`... (poorly defined command in standard tooling)
  • Standard npm scripts leave a lot of crap in the source code directory. Don't distribute libraries and frameworks via NPM for gads sake!
  • TypeScript's type system seems to suck. Witness Exhibit A: Promises.
  • Again with the promises: How do you make a promise which is resolved with type A but rejected with type B?
  • TypeScript promises again: OMG look at .all()!!!!!
  • Cordova / SystemJS

And here's a mystery I had not managed to decode back then:

  1. Providers are automatically found based on type? How does Angular know the type since there is no Typescript type info at runtime?
 
 

AngularJS2 + Cordova = System not defined

I am currently learning and testing AngularJS2, and all is not rosy... But my thoughts on Angular 2 will come in a later blog post; for now, let's solve a big problem if you're trying to run an Angular 2 app inside Cordova/Phonegap - your app does not run at all!

If you launch your Angular 2 + Cordova/Phonegap app, and your Angular code does not initialise, plus you have a nice JS error in your log: System is not defined, not to mention multiple "XYZ url failed to load" errors, please try this.

1. Base URL

<base href="/">
If you have the HTML above, change it to:
<base href="./">
This will stop your Cordova app from trying to load file:///node_modules/systemjs/dist... etc and instead load from the correct URL, e.g. file:///android_asset/www/node_modules/systemjs/dist... (Android).

2. Content Security Policy

If you're using even a remotely new version of Cordova/Phonegap, you will have the Whitelist Plugin. Along with that, you may have a Content Security Policy tag in your HTML, like this:

<meta http-equiv="Content-Security-Policy" content=" default-src 'self' data: gap: https://ssl.gstatic.com; style-src 'self' 'unsafe-inline'; connect-src 'self' file://* http://api.myhappyhour.com.my; " />

If you have this, SystemJS (which Angular 2 depends upon) will not load correctly. To fix this, you first have to add the 'unsafe-inline' and 'unsafe-eval' sources to the default-src directive:

<meta http-equiv="Content-Security-Policy" content=" default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; connect-src 'self' file://* http://api.myhappyhour.com.my; " />

Next, you have to load the version of SystemJS which works with Content Security Policy. i.e. change this:

<script src="node_modules/systemjs/dist/system.src.js"></script>

To this:

<script src="node_modules/systemjs/dist/system-csp-production.js"></script>

That should be it! Your Angular 2 code should at least start running in your Cordova/Phonegap app now :-)

 

ngAnimate: Animating Both Ancestor and Descendent Elements at the Same Time

By default, Angular JS applies a rule for animations: a child element's animation will be skipped if an ancestor element is already being animated. I'm not sure why this policy exists, but this is what will trip you up if you try to:

  • animate in child elements while a page transition is taking place,
  • fade in some content while performing a full-page reveal upon page load,
  • etc

This policy sure caused me some painful troubleshooting sessions, so I'll show you where this policy is enforced in Angular and how to override this default rule.

This is the function which checks whether or not to run an animation when $animate.enter / $animate.leave etc are called: https://github.com/angular/angular.js/blob/v1.3.13/src/ngAnimate/animate.js#L1296. The policy of not allowing descendent elements to animate when an ancestor is being animated is stated here.

If you scroll down to the animationsDisabled function, you can see what Angular actually checks for. You will also see a mysterious check for an NG_ANIMATE_CHILDREN data attribute. This is the undocumented secret which will let us override Angular's default policy of no nested animations!

Here's how you use NG_ANIMATE_CHILDREN.

Sample HTML:

<body ng-app="exampleApp">
    <div class="someAnimatedParent">
        <div class="someAnimatedChild">
            Why won't Angular let me animate?
        </div>
    </div>
</body>

In your script, for example in exampleApp.run(), add this line:

$(".someAnimatedParent").data("$$ngAnimateChildren", true)

Once the script runs, any element within .someAnimatedParent will be animatable even if .someAnimatedParent itself is also running an animation. That's all you have to do!

Notes:
  • You can of course set the data attribute on the <body> element, in which case all elements will animate whether their parents are animating or not.
  • We have to set the data attribute using a script because of the funny characters and uppercases. If anyone knows how to insert such an attribute in HTML directly, please send me a note on Github or Twitter!
  • This code was tested with Angular JS 1.3.13.

If you are unsure whether you are facing this problem, check if the promise returned by $animate.enter() is immediately resolved, without calling any animations defined by yourAppModule.animation(). If that is the case, you may be facing the issue described here.

Angular JS & Config-phase Services

AngularJS runs in two phases: config phase and run phase. Simply put, the config phase is where all modules, services, and so on are declared and made known to the Angular framework; the run phase is where the app module is initiated and all required services, controllers, etc are instantiated.

This separation is one of the things that allows you to swap out one module for another, by telling Angular that the $http service will now come from provider X instead of the original provider. However, it also comes with the drawback that code which runs in config phase only have access to providers and value factories.

In my projects, I often have a configuration service which specifies things like the server URL, various timeout values, etc. Any value is meaningful to configure on an app-wide level is defined in this service, which lets me easily swap out the development config for the production config, for instance.

All too often, I will need one of these configuration values during the config phase. Here is a simple hack my team has been using since our second ever Angular project to allow us access to the configuration "service" from both Config phase and Run phase.

Define your config service using the full "provider" definition syntax:

App.provider "configService", [
   ->
       # These values will be available in config phase and run phase:
       @apiUrl = "http://my.server.here"
       @anotherConfigValue = 123
     
       Service = ->
       Service.prototype = angular.extend {}, @
       @$get = (someOtherService) ->

           # Anything which requires run phase things to work
           # are defined here, and available in run phase only:
           Service.prototype.doSomething = ->
               someOtherService.x()

           new Service
]

To use your "service" from either the config phase or run phase, do this:

App.config (configServiceProvider) ->
   # config phase code accesses the config via the provider.
   # .apiUrl and .anotherConfigValue are available here.

App.run (configService) ->
   # run phase code uses the service as usual.
   # .apiUrl, .anotherConfigValue, and .doSomething are all available here.


Synchronizing CSS Animations

CSS Animations are great. They allow you to specify smooth, beautiful transitions between element states. However, because CSS Animations apply to each element, there is a very high chance for animations involving multiple elements to go out of sync.

Example: we are making a dashboard page which may contain flashing warnings. These warnings come on as our AJAX calls complete. Sample implementation at http://jsfiddle.net/tjwoon/503ztqm1/

In this simple implementation, each warning flashes to its own rhythm, causing the entire page to be difficult to read.

To improve readability, we have to make all the warnings flash at the same rhythm. This can be accomplished by making the animation styles depend on an "activation" class, which we can add when we want our animations to start: http://jsfiddle.net/tjwoon/d7u9y6Lc/

What we did in the second implementation was to specify that animations are only active when our containing element has a certain class. Then, whenever we add a warning to our dashboard, we first remove the class so that all ongoing animations stop, then restart the animation so that the new warning and all old warnings are started at the same time and in sync.

We need to run the animation restart code in a zero-duration timeout so that the browser layout engine first detects that we have removed the animation activation class before we re-add the class. Otherwise the browser never stops the ongoing animations so the new warning starts at a different time compared to the other warnings,

To improve upon the second example, we may add some additional processing so that during the stop-and-restart period, the warnings do not briefly flash/jitter. I may add a 3rd sample with such processing at a later date...

Blink Engine bug regarding "transitionend" event's propertyName (a.k.a Google has broken Sencha again)

If you're using Sencha Touch 2.x, you will probably face problems on at least Android System WebView version >= 44.0.2403.39 and Google Chrome Canary >= 45.0.2430.0 (Blink version unknown to me), and possibly earlier versions. Fortunately this System WebView is in Beta at the moment, so fix your apps before it leaves Beta!

In my case, I was using the Sencha Picker Component extensively. The Picker component is a modal "drop down menu" with a mask behind it. The mask prevents the user from interacting with other UI elements when the Picker is visible. Unfortunately, on the affected versions of Blink (Google's layout engine), the mask does not hide itself when the Picker is dismissed, continuing to block the UI...

After some investigation, I have found that the root cause is that animation events are broken on affected versions of Blink.

By default, the Picker component performs a slideOut animation when being dismissed. A series of events is fired during this animation, which should include "transitionend" folloed by "animationend". On affected versions of Blink, the "animationend" event does not fire...

It seems that the "animationend" event fails to fire because Sencha is looking out for the "transitionend" event on the "-webkit-transform" property only - however, the affected Blink versions return "transitionend" on the "transform" property instead.

This jsFiddle demonstrates the problem: http://jsfiddle.net/tjwoon/o2v34neq/

I have filed a bug report with the Chromium Project (Issue 500126). I hope it is fixed before the new WebView leaves Beta!



UPDATE 19 June 2015: Well it looks like the Chromium project is undecided whether to use "transform" or "-webkit-transform" (Issue 493177). In case they do not decide to revert to the old behavior by the time the System Webview hits production, I have added a workaround which fixes all Sencha CSS Transition animations.

The patch:

Index: touch/src/fx/runner/CssTransition.js
===================================================================
--- touch/src/fx/runner/CssTransition.js (revision 22505)
+++ touch/src/fx/runner/CssTransition.js (revision 22506)
@@ -23,7 +23,11 @@
             id = target.id;

         if (id && this.runningAnimationsData.hasOwnProperty(id)) {
-            this.refreshRunningAnimationsData(Ext.get(target), [e.browserEvent.propertyName]);
+            // Workaround: Sencha does not fire animationEnd event in Android System Webview >=44.0.2403.39
+            var prop = e.browserEvent.propertyName;
+            var propNames = [prop];
+            if(prop == "transform") propNames.push("-webkit-transform");
+            if(prop == "-webkit-transform") propNames.push("transform");
+
+            this.refreshRunningAnimationsData(Ext.get(target), propNames);
         }
     },


Instructions:
  1. Open the Sencha file: touch/src/fx/runner/CssTransition.js
  2. Look for the line
    this.refreshRunningAnimationsData(Ext.get(target), [e.browserEvent.propertyName]);
  3. Replace that line with this:
    // Workaround: Sencha does not fire animationEnd event in Android System Webview >=44.0.2403.39
    var propNames = [e.browserEvent.propertyName];
    if(e.browserEvent.propertyName == "transform") propNames.push("-webkit-transform");
    this.refreshRunningAnimationsData(Ext.get(target), propNames);

Explanation:

The new Webview problem affects only the "transform"/"-webkit-transform" properties, which is an "aliased" propertyName with additional special rules for common use cases (source). My workaround just makes Sencha check for animationEnd listeners on both "transform" and "-webkit-transform" whenever a transitionEnd event with either propertyName is received.

Disclaimer: I am not familiar with internal Sencha architecture, but in my testing nothing is broken by this workaround.



UPDATE 29 July 2015: This is now permanent behavior for Chrome - the Chromium team has closed the issue.

Disabling Text Selection and the Context Menu in Hybrid Mobile Apps

Mobile apps written in HTML run in WebViews, which tend to be optimized for displaying Web articles, webpages, etc. Being optimized for Web content means that there will be default behavior which we may not want in a mobile app.

One of those undesirable behaviors is touch-and-hold to select text. It can be very confusing to users who are have their finger on a menu button, deciding whether they want to select it or not, to suddenly be presented with a copy text interface. Menu buttons are UI elements rather than content, thus it is simply a context in which text selection is not appropriate. Similarly, the long-touch menu is also unsuitable in such a context.

To disable text selection and the long touch menu on an element, simply apply the following CSS styles to the element, or an ancestor. In fact, because so much of your app UI will be unsuitable for selection, you might apply the rule to all elements:

html {
      -webkit-touch-callout: none; /* Disables long-touch menu */
      -webkit-user-select: none; /* Disable text selection (for Webkit) */
      user-select: none; /* Disable text selection (standard syntax) */
}

A big gotcha is that input elements with these styles may not trigger the device's soft keyboard properly. Symptoms include the iOS keyboard's previous/next field buttons being visible but the keys being off screen; or the keyboard may appear, but user input does not appear to be sent to the element; and possibly other strange behavior.

To make your input elements work properly again, just reset the rules to the default values:

input , textarea {
      -webkit-touch-callout: default;
      -webkit-user-select: text;
      user-select: text;
}

In addition, to make your life easier, you could create a class for selectable content.

.content {
      -webkit-touch-callout: default;
      -webkit-user-select: text;
      user-select: text;
}

Finally, elements with -webkit-touch-callout: none; do not close the soft keyboard when tapped (as tested on iOS Safari browser). This is terrible as the user will either have to know how to close the keyboard using the hide keyboard key, or find a selectable element to tap on. To work around this problem, we add some JavaScript code:

$("*").click(function () {
    if(!$(this).parents("input , textarea")) {
        $("input , textarea").blur()
    }

})


A full demo which includes all the things discussed above is at http://jsfiddle.net/tjwoon/ves5qyb0/.
#Cordova #Android 4.0.0 - major changes including official #AndroidStudio support. It's major #LearnTimeWeekend

Synchronizing CSS Animations

CSS Animations are great. They allow you to specify smooth, beautiful transitions between element states. However, because CSS Animations apply to each element, there is a very high chance for animations involving multiple elements to go out of sync.

Example: we are making a dashboard page which may contain flashing warnings. These warnings come on as our AJAX calls complete. Sample implementation at http://jsfiddle.net/tjwoon/503ztqm1/

In this simple implementation, each warning flashes to its own rhythm, causing the entire page to be difficult to read.

To improve readability, we have to make all the warnings flash at the same rhythm. This can be accomplished by making the animation styles depend on an "activation" class, which we can add when we want our animations to start: http://jsfiddle.net/tjwoon/d7u9y6Lc/

What we did in the second implementation was to specify that animations are only active when our containing element has a certain class. Then, whenever we add a warning to our dashboard, we first remove the class so that all ongoing animations stop, then restart the animation so that the new warning and all old warnings are started at the same time and in sync.

We need to run the animation restart code in a zero-duration timeout so that the browser layout engine first detects that we have removed the animation activation class before we re-add the class. Otherwise the browser never stops the ongoing animations so the new warning starts at a different time compared to the other warnings,

To improve upon the second example, we may add some additional processing so that during the stop-and-restart period, the warnings do not briefly flash/jitter. I may add a 3rd sample with such processing at a later date...

About The Author

TJ Woon is a Mobile App Developer and Team Lead at Nexstream Sdn Bhd.

He splits his time between computer programming, photography, family, dogs, and now his car too. Some of his accomplishments are listed here.