Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

fix(injector): invoke config blocks for module after all providers #7147

Closed
wants to merge 1 commit into from

Conversation

caitp
Copy link
Contributor

@caitp caitp commented Apr 17, 2014

This change ensures that a module's config blocks are always invoked after all of its providers are registered.

NOTE: I'm not entirely sure if this is desirable yet, because the point mentioned in the BREAKING CHANGE section below may actually be something which is desirable. However, I do feel it is probably better to always invoke config blocks for a single module after all of its providers. The config blocks should not play a role in how providers are registered, in my own opinion.

BREAKING CHANGE:

Previously, config blocks would be able to control behaviour of provider registration, due to being invoked prior to provider registration. Now, provider registration always occurs prior to configuration for a given module, and therefore config blocks are not able to have any control over a providers
registration.

Example:

Previously, the following:

   angular.module('foo', [])
    .provider('$rootProvider', function() {
      this.$get = function() { ... }
    })
    .config(function($rootProvider) {
      $rootProvider.dependentMode = "B";
    })
    .provider('$dependentProvider', function($rootProvider) {
      if ($rootProvider.dependentMode === "A") {
        this.$get = function() {
          // Special mode!
        }
      } else {
        this.$get = function() {
          // something else
        }
      }
    });

would have "worked", meaning behaviour of the config block between the registration of "$rootProvider" and "$dependentProvider" would have actually accomplished something and changed the behaviour of the app. This is no longer possible within a single module.

Fixes #7139

@@ -305,6 +305,29 @@ describe('injector', function() {
expect(log).toEqual('abABCD');
});

it('should execute own config blocks after all own providers are invoked', function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test actually will not fail without your fix. :)
You should move the config blocks to be before the provider registration

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Misunderstood what you were saying originally, yes you're right, the test is broken because of that :) fixes

This change ensures that a module's config blocks are always invoked after all of its providers are
registered.

BREAKING CHANGE:

Previously, config blocks would be able to control behaviour of provider registration, due to being
invoked prior to provider registration. Now, provider registration always occurs prior to configuration
for a given module, and therefore config blocks are not able to have any control over a providers
registration.

Example:

Previously, the following:

   angular.module('foo', [])
     .provider('$rootProvider', function() {
       this.$get = function() { ... }
     })
     .config(function($rootProvider) {
       $rootProvider.dependentMode = "B";
     })
     .provider('$dependentProvider', function($rootProvider) {
       if ($rootProvider.dependentMode === "A") {
         this.$get = function() {
           // Special mode!
         }
       } else {
         this.$get = function() {
           // something else
         }
       }
     });

would have "worked", meaning behaviour of the config block between the registration of "$rootProvider"
and "$dependentProvider" would have actually accomplished something and changed the behaviour of the
app. This is no longer possible within a single module.

Fixes angular#7139
@lgalfaso
Copy link
Contributor

LGTM. I am wondering it there should be a note that to migrate from the previous behavior a constant should be used and how that would be done

@caitp
Copy link
Contributor Author

caitp commented Apr 17, 2014

I am wondering it there should be a note that to migrate from the previous behavior a constant should be used and how that would be done

To be honest, if people were actually using the previous behaviour, in a way that this change would "break", they're really doing some things that are probably "wrong", like, putting together really inconsistent APIs that are kind of nightmarish.

I don't really expect that a lot of this is happening, but it should be possible to work-around it using module dependencies, so I could put a note about that in the commit message.

The thing is that such a note would be rather wordy and complicated, and I don't think it is likely to benefit very many people.

@lgalfaso
Copy link
Contributor

@caitp ok, makes sense

@shahata
Copy link
Contributor

shahata commented Apr 17, 2014

I think the easiest way to migrate is using a config block with $provide.provider instead of the angular.module().provider:

Before:

angular.module('foo', [])
    .provider('$rootProvider', function() {
      this.$get = function() { ... }
    })
    .config(function($rootProvider) {
      $rootProvider.dependentMode = "B";
    })
    .provider('$dependentProvider', function($rootProvider) {
      if ($rootProvider.dependentMode === "A") {
        this.$get = function() {
          // Special mode!
        }
      } else {
        this.$get = function() {
          // something else
        }
      }
    });

After:

angular.module('foo', [])
    .provider('$rootProvider', function() {
      this.$get = function() { ... }
    })
    .config(function($rootProvider) {
      $rootProvider.dependentMode = "B";
    })
    .config(function($provide) {
      $provide.provider('$dependentProvider', function($rootProvider) {
        if ($rootProvider.dependentMode === "A") {
          this.$get = function() {
            // Special mode!
          }
        } else {
          this.$get = function() {
            // something else
          }
        }
      });
    });

And yes, this is definitely bad code :)

@lgalfaso
Copy link
Contributor

Please do not add this code on how to migrate. Putting nothing it better

@caitp
Copy link
Contributor Author

caitp commented Apr 17, 2014

It would work, you just wouldn't be able to inject $dependentProvider into config blocks that occurred before the one that registered it. (which is essentially the current behaviour of the loader.js apis)

@caitp caitp added this to the 1.3.0-beta.6 milestone Apr 17, 2014
@caitp
Copy link
Contributor Author

caitp commented Apr 17, 2014

Triaging for beta-6, because it would be good to experiment and figure out if it upsets people or not.

@caitp
Copy link
Contributor Author

caitp commented Apr 18, 2014

@IgorMinar there's a problem with that, it pretty much breaks ngMock to do it that way, because ng registers things in a config block, but ngMock registers (most) things as regular providers. So what ends up happening is, the providers get set up in order (and therefore the ngMock stuff runs first), but the config blocks wait until the end, so the ng stuff added in config blocks overwrites the ngMock stuff.

I mean, there are ways around this, but it kind of sucks, so maybe it's better to just leave it the way it is for now. I think fixing that would be more of a breaking change than this is really worth, so IMO it's probably better to leave it.

@IgorMinar
Copy link
Contributor

ok, so let's get this is as is.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

AngularJS config block causes unknown provider error if provider is added to module after the config block
5 participants