This morning, I posted a blog post on monkey-patching the $q service to include an .fcall()-inspired method for function execution in a Promise chain. After posting it, Tomasz Stryjewski and Phil DeJarnett both pointed out that my use of the .run() block was a poor choice when it came to service augmentation. Instead, they suggested that I use a decorator in a .config() block so that the service could be augmented before the dependency-injector caches it. I've never used a decorator before, so I wanted to try and modify my previous demo.
When an AngularJS application is being bootstrapped, it runs through a configuration phase followed by a run phase. Services aren't available until the run phase, which means that we can use the configuration phase as a place to setup our monkey matching. That said, it's not quite that simple - services aren't available in the configuration phase, so we can't directly affect them at that time.
But, the configuration phase does give us an opportunity to define a service decorator before any other part of the AngularJS application requires that service. The decorator is kind of like an interceptor for service instantiation; when the service is instantiated, it is passed-off to (or "through") the decorator before it is finally cached in the dependency-injection container.
The decorator function can make use of dependency-injection; however, as we are intercepting the instantiation of a service, you can't inject the target service by name. Instead, AngularJS injects the target service as "$delegate", which it provides as a "local" override to the DI's invoke() method.
To see this in action, I've refactored my earlier demo to use a .config() block:
We're not actually altering the $q service during the configuration phase. Instead, we're simply using the configuration phase as a means to define the decorator before any other part of the application has had a chance to instantiate it. This ensures that the $q service will always have the .fcall() method at the time it is injected.
Thinking about the configuration phase of an AngularJS application is fairly new to me. Granted, I've used the configuration phase in the past to define things like routing and exception handling. But, I don't think I really ever understood how it worked - it was mostly copy/paste on my part. Hopefully, this step is the first in my journey towards truly understanding the how configuration and providers work.
Want to use code from this post? Check out the license.