Compiling with Closure Compiler in Advanced Mode
An alternate solution is using the @expose annotation on these $scope properties so they won’t be minified. This allows you to reference exposed properties using dot notation everywhere. The disadvantage of this method is it potentially prevents other properties with the same name from being minified if the compiler can’t prove that they aren’t related. We also experienced an issue with the compiler throwing an “incomplete alias created for namespace” error if an exposed property shared a name with an existing namespace. For example, exposing $scope.date will cause the compiler to complain about goog.date having an incomplete alias. In this case we had to rename the property to something not used by a namespace such as $scope.dateModel to resolve the error.
The compiler also minifies function argument names which breaks the dependency injector. The $injector service uses some clever regex to extract the arguments and find the corresponding provider which doesn’t work with the minified names. One of the ways described in the docs is to declare the dependencies in an array of strings for the injectors:
With advanced mode we need to take this one step further and use bracket notation since the $inject property name will be minified:
A similar problem happens when using the directive definition object to declare a directive.
The object property names will be minified and prevent Angular from parsing it correctly. You can use quotes around the property names to prevent minification.
Model Modification Outside of Angular
Native Angular APIs such as ngClick and $http internally wrap calls with $scope.$apply which kicks off a $digest loop to check for model changes. Model changes that take place outside of Angular do not have this automatic wrapping so we need to notify it that a change has taken place by calling $scope.$apply manually. However, calling $scope.$apply when you’re already in a $digest loop will throw an exception. This becomes a problem when integrating with Closure because the async primitives (particularly, goog.async.Deferred) may fire callbacks synchronously or asynchronously. Therefore, for a given callback, you can’t be sure if you’re already in a digest or not. One solution from Alex Vanston is something he calls “safeApply” which performs this check before calling $scope.$apply. We are using a slightly modified version for the purpose of making requests to our back-end API.
The service is called with fn as the callback, opt_context as the “this” object, and an arbitrary number of arguments can be passed in after that which fn will be called with.
As an aside, while $scope.$apply(fn); vs fn(); $scope.$apply(); may seem to produce identical results, there is actually an advantage of using the former. When $scope.$apply wraps the function call, any exceptions thrown from the call can be caught and handled by your $exceptionHandler.
Angular makes it incredibly easy to integrate your existing error reporting system. We created a provider whose $get method returns an adapter object that we can use to log errors. We then overrode the native $exceptionHandler by calling provider() in the module configuration with our $exceptionHandler provider. We wrote this as a provider rather than a factory so that we could do additional configuration via the config() method in the individual application modules.
To propagate model changes to the UI layer in Closure, we use a homebrewed data binding implementation based on goog.pubsub.PubSub. While quite powerful, these bindings need to be manually set up each time and only solve the problem of getting data to the view from the model, and not the other way around, which often times is a bigger challenge. Angular data bindings solve both of these issues with continuous updates that can go in either direction, allowing the model to always be the single source of truth. We continue to use pubsub in Angular components that need to interact with our Closure code.
We organize our Angular project similar to our current structure where files are grouped by their functionality. We like this approach since it allows us to easily reuse these components across multiple applications. Our Angular files are kept separate from the Closure files, with top level folders for controllers, directives, services, tests, and modules. The controllers, directives, and services folders are further subdivided by module to group components that are related in functionality and/or depend on each other. The modules folder contains files with the configuration blocks for our Angular modules. We use modules for two main purposes; large application modules which are used to boot up our various applications on Zoosk, and smaller component modules for groups of controllers, directives, and services which are used together. These components level modules are then declared as dependencies for the application modules.
Angular Framework Modules
One of the nice aspects of Angular is it doesn’t force you to use all of the framework components. In our case, we already have a well-tested router in Closure that we continue to use in Angular. We also have a great deal of code written to deal with constructing, sending requests and parsing responses from our back-end API. At this time it isn’t feasible for us to migrate these to the $http services since many of our features are not using Angular yet so the code needs to be callable both with and without Angular available. In the future, the Angular team plans to split each component of the framework into separate modules so you won’t even need to load code for framework components you aren’t using.
With single page applications, users perform full page reloads less often, leading to greater opportunity for memory leaks to accumulate and cause performance issues in an application. Managing memory leaks should always be a concern, but using multiple frameworks creates more opportunities for memory leaks to creep in. When an Angular component is torn down, it will destroy all the Angular components associated with it along with any jqLite event listeners or data. However, events that are attached to these components outside of Angular or other behaviors added on top will not be disposed and continue to consume memory. To prevent these leaks, add a listener on the scope for the $destroy event where you can dispose these components and allow the garbage collector to free this memory later.
One thing that we’ve been having issues with is html5shiv with Angular. In IE < 9 we’ve consistently had problems when a template that contains HTML5 tags is cloned, such as in ngTransclude, html5shiv will get confused and construct the DOM incorrectly. Our short term solution has been to avoid using HTML5 is transcluded templates or avoiding transclusion altogether.
At a high level, we’ve found that we are able to ship features much faster using Angular and on average, the Angular rewrites weigh in at about 1/3 the amount of code that it took in Closure. Overall we have been very pleased with the power Angular brings and plan to start building all new features with it.