Skip to content

Instantly share code, notes, and snippets.

@eriktrom
Created February 3, 2015 01:31
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eriktrom/5f730817b21ad5565aff to your computer and use it in GitHub Desktop.
Save eriktrom/5f730817b21ad5565aff to your computer and use it in GitHub Desktop.

angular.injector

Creates an injector object that can be used for retrieving services as well as for dependency injection

  // create an injector
  var $injector = angular.injector(['ng']);
  // use the injector to kick off your application
  // use the type inference to auto inject arguments, or use implicit injection
  $injector.invoke(function($rootScope, $compile, $document){
    $compile($document)($rootScope);
    $rootScope.$digest();
  });

$injector service

Used to retrieve object instances as defined by the $provide provider, instantiate types, invoke methods and load modules.

  var $injector = angular.injector();
  expect($injector.get('$injector')).toBe($injector);
  expect($injector.invoke(function($injector){
    return $injector;
  })).toBe($injector);

The essence of this is:

By adding an `$inject` property onto a function the injection parameters can be specified.

$injector.get

Return an instance of the service

$injector.invoke

Invoke the method and supply the method arguments from the $injector

$injector.instantiate

Creates a new instance of a constructor function by calling new Ctor(args, are, supplied, by, the, annotator).

$injector.annotate

Declare a constructor function and then annotate it by using annotation.

function MyCtor(dep1, dep2) { /* do stuff */ }


// sometime later
MyCtor.$inject = ['dep1', 'dep2'];
angular.injector().invoke(MyCtor)

// you can also do it like this
angular.injector().invoke(['dep1', 'dep2', function(dep1, dep2) {
  
}])

$provide service

Register components with the $injector. Many methods here are also exposed on angular.module

An angular service is a singleton object created by a service factory. A service factory is a function which is is created by a service provider. A service provider is a constructor function. When instantiated, a service provider must contain a property called $get, which holds the service factory function.

When you request a service, the injector is responsible for finding the correct service provider, instantiating it and calling it's $get service factory function to get the instance of the service

When the service provider will be no more than a constructor function with a $get property, the provide service has additional helper methods to register services without specifying a provider.

  • $provide.provider(SomeProvider) - registers a provider function (which is a constructor function) with the $injector. An instance of of a provider is responsible for providing a factory for a service. When you hear the term service provider object, this is what they mean. Thus a service provider is like a factory for making factories. A factory(general definition) is an method or function that, when called, returns a new instance. In angular, this means that you place a new provider into the injector. Think of the $injector as a storage container that knows about all the providers in the app. Each provider has a $get method defined on this inside the provider. Keep in mind that when you call $provide.provider(Ctor), you are passing in a constructor function, therefore this.$get, while yes is a method, it's defined not on the prototype of the constructor, but an instance of the provider. Furthermore, when you call this.$get you are calling a factory function, meaning the result is, in general, supposed to be a new instance too. You'd can use this pattern to do cool things, but in general, you'd really only be defining this.$get inside your serviceProvider, hence the helper methods. This also means their is a provider for every angular service, including the $provide and $inject services. In theory this perhaps means you could override the providerProvider.$get method to change everything. I wonder what happens if you define a method on the prototype of the ProviderProvider constructor function? It's important to note that the takeaway here is that if you want to modify all instances of a service, you do so in the services provider. This is a bit like adding to the prototype/class of an object, where every instance of the 'class' can share state. It also means you can set new state on every instance, after they've been instantiated by calling methods directly on the provider which change free variables on the inside of the constructor function and which can therefore provide a way to capture those globally available variables by capturing them in a closure(via methods) defined on the factory method ($get). The $provide provider(i know sounds odd) registers new providers within the $injector, which means you can retrieve the original constructor of a provider via $injector.get('ProviderName').

Concepts are best shown through example:

 // Define the eventTracker provider
 function EventTrackerProvider() {
   var trackingUrl = '/track';

   // A provider method for configuring where the tracked events should been saved
   this.setTrackingUrl = function(url) {
     trackingUrl = url;
   };

   // The service factory function
   this.$get = ['$http', function($http) {
     var trackedEvents = {};
     return {
       // Call this to track an event
       event: function(event) {
         var count = trackedEvents[event] || 0;
         count += 1;
         trackedEvents[event] = count;
         return count;
       },
       // Call this to save the tracked events to the trackingUrl
       save: function() {
         $http.post(trackingUrl, trackedEvents);
       }
     };
   }];
 }

 describe('eventTracker', function() {
   var postSpy;

   beforeEach(module(function($provide) {
     // Register the eventTracker provider
     $provide.provider('eventTracker', EventTrackerProvider);
   }));

   beforeEach(module(function(eventTrackerProvider) {
     // Configure eventTracker provider
     eventTrackerProvider.setTrackingUrl('/custom-track');
   }));

   it('tracks events', inject(function(eventTracker) {
     expect(eventTracker.event('login')).toEqual(1);
     expect(eventTracker.event('login')).toEqual(2);
   }));

   it('saves to the tracking url', inject(function(eventTracker, $http) {
     postSpy = spyOn($http, 'post');
     eventTracker.event('login');
     eventTracker.save();
     expect(postSpy).toHaveBeenCalled();
     expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track');
     expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track');
     expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 });
   }));
 });
  • $provide.constant(objOrVal) - registers a value/object with the injector that can be accessed by providers and services. Note this is the only helper that allows a way register a simple obj or value that can be accessed by a service provider itself.
  • value(objOrVal) - registers an obj or val with the injector that can only be accessed by services, not providers of those services.
  • factory(fn) - registers a service factory function that will be wrapped in a service provider object whose $get property will automatically be set to the given fn
  • service(constructorFn) - registers a constructor function, aka, class, that will be wrapped in a service provider object whose $get property will instantiate a new object using the given constructor function.

See https://github.com/trombom/angular.js/blob/v1.2.28-branch-from-tag/build/angular.js#L3198-3991 for the codez for v1.2.28

$provide.decorator

Intercept the creation of a service (aka, intercept calling $get on the service's provider), allowing you to override it. The decorator can return the original service(aka, modified instance of the service), or a new service which replaces or wraps and delegates to the original service.

$provide.decorator('serviceName', fn)

The callback fn is called using $injector.invoke, and is therefore fully injectable(like all services in angular).

Pass $delegate into the callback to gain access to the original service that, aka, the one named by the string.

Good things to know for testing

  • browser global variables have been overriden. The ngMock.$browser service allows testing your app without actually hitting the browser
    • $window
    • $log
    • $sniffer
    • $document
    • $location
      • url
      • onUrlChange
    • baseHref
    • cookies ( service)

$TemplateCacheProvider

$templateCache.put('templateId.html', 'html here') $templateCache.get('templateId.html')

$compile service (line 5180)

compile an html string or dom into a template, returns a template function, which can then be used to link scope and the template together

compilation walks the dom tree and matches DOM elements to directives

Angular Mocks

Promises in Angular

In order to resolve a $q promise in a test, you have also inject a $rootScope

  it('should simulate promise', inject(function($q, $rootScope) {
    var deferred = $q.defer();
    var promise = deferred.promise;
    var resolvedValue;

    promise.then(function(value) { resolvedValue = value; });
    expect(resolvedValue).toBeUndefined();

    // Simulate resolving of promise
    deferred.resolve(123);
    // Note that the 'then' function does not get called synchronously.
    // This is because we want the promise API to always be async, whether or not
    // it got called synchronously or asynchronously.
    expect(resolvedValue).toBeUndefined();

    // Propagate promise resolution to 'then' functions using $apply().
    $rootScope.$apply();
    expect(resolvedValue).toEqual(123);
  }));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment