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

feat($injector): support instantiating classes. #12598

Closed
wants to merge 1 commit into from

Conversation

mprobst
Copy link
Contributor

@mprobst mprobst commented Aug 17, 2015

ES6's class Foo {} constructors cannot be instantiated using
fn.apply. This change extracts injection argument collection and then
uses new (Function.bind.apply(ctor, args)) to instantiate the service
instance.

Fixes #12597.

@mprobst
Copy link
Contributor Author

mprobst commented Aug 17, 2015

Note: this change requires using Function.bind, which is quite a bit slower in instantiating objects in a synthetic benchmark (x20). However I wouldn't expect that the instantiate code flow is very critical - it's usually only used for services and controllers, both of which should be instantiated relatively rarely in a typical application's lifecycle.

@IgorMinar
Copy link
Contributor

tests are failing

@mprobst
Copy link
Contributor Author

mprobst commented Aug 17, 2015

@IgorMinar should be fixed (FF doesn't enable class syntax by default).

var clazz = eval('(class { constructor(a) { this.a = a; } aVal() { return this.a; } })');
var instance = injector.instantiate(clazz);
expect(instance).toEqual({a: 'a-value'});
// Check if prototype is set up correctly.s
Copy link
Contributor

Choose a reason for hiding this comment

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

you are not checking the prototype + there is a typo at the end of the comment. I'd just remove this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, the call to aVal() would fail if the prototype was incorrect. But point taken, dropped the comment.

@mprobst
Copy link
Contributor Author

mprobst commented Aug 20, 2015

PTAL.

@Narretz
Copy link
Contributor

Narretz commented Aug 21, 2015

@mprobst While you are at it, could you check this related issue: #12614? If $controller uses $injector, shouldn't this PR make it work?

@mprobst
Copy link
Contributor Author

mprobst commented Aug 21, 2015

#12614 is not fixed by this – $controller uses $injector, but bindToController has its own logic to instantiate controllers on directives. I'm not sure it'll be possible to fix bindToController, it's way of instantiating a class on top of an existing object, then applying the ctor to it might be inherently incompatible with ES6.

@caitp
Copy link
Contributor

caitp commented Aug 21, 2015

then applying the ctor to it might be inherently incompatible with ES6

It generally is, but this might not be too much of a problem for much longer. It's likely that the "new-only" restriction will be lifted, or otherwise a way of specializing a "call" behaviour/factory method for classes.

In the mean time, you could add some other compat mechanism, like falling back on a static "init()" method if calling the constructor fails. There's no reason this couldn't be solved using conventions rather than language features

@mprobst
Copy link
Contributor Author

mprobst commented Aug 21, 2015

@caitp: indeed, but what about doing that in another PR?

@caitp
Copy link
Contributor

caitp commented Aug 21, 2015

Yeah, doesn't need to be this one, although that feature probably shouldn't be specific to controllers if we do that

@stefan--
Copy link

when can we expect this to work?

@mprobst
Copy link
Contributor Author

mprobst commented Aug 31, 2015

@IgorMinar PTAL, I believe this is good to merge. It does not solve the issue for controllerAs yet, but that's more tricky (and may or may not be impossible).

@mprobst mprobst force-pushed the es6-instantiate branch 2 times, most recently from f27e77e to e0567ce Compare August 31, 2015 09:23
@IgorMinar
Copy link
Contributor

lgtm. @petebacondarwin should we wait with a merge until master is open for 1.5 beta features?

@IgorMinar
Copy link
Contributor

@petebacondarwin, one more thing - this feature is not documented at all. where should the documentation live?

for (i = 0, length = $inject.length; i < length; i++) {
key = $inject[i];
for (var i = 0, length = $inject.length; i < length; i++) {
var key = $inject[i];
Copy link
Contributor

Choose a reason for hiding this comment

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

Why move the declarations of i and key here? Being var declarations they will still be hoisted to the top of the function block.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also we seem to have lost the declaration of the length variable here.

Copy link
Contributor

Choose a reason for hiding this comment

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

length is delayed as part of the var declaration

Copy link
Contributor

Choose a reason for hiding this comment

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

Declared, darned Swype

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@petebacondarwin I was moving the code around anyway, and I personally find it easier to declare things as close as possible to their respective use site, hoisting or not. Makes it easier to see what's used where and why, and also makes it easier to refactor code without forgetting to remove declarations.

@petebacondarwin
Copy link
Contributor

However I wouldn't expect that the instantiate code flow is very critical - it's usually only used for services and controllers, both of which should be instantiated relatively rarely in a typical application's lifecycle.

I am not sure about this statement. Controllers can be instantiated very often. Every instance of a directive which defines a "directive controller" would need to instantiate a directive. Also imagine if you use ng-controller inside a frequently changing ng-repeat block.

@petebacondarwin
Copy link
Contributor

I guess the documentation for this should be in the $injector class: https://docs.angularjs.org/api/auto/service/$injector

@mprobst
Copy link
Contributor Author

mprobst commented Sep 7, 2015

@petebacondarwin absent a good benchmark, this is all guesswork. However I would imagine the cost of this particular call being dwarfed by the cost of both ng-repeat and the DOM manipulations for directive controllers or ng-controller in a loop. I'd expect you can construct a synthetic benchmark that does slow down, but I wouldn't expect actual apps experiencing that.

ES6's `class Foo {}` constructors cannot be instantiated using
`fn.apply`. This change extracts injection argument collection and then
uses new (Function.bind.apply(ctor, args)) to instantiate the service
instance.
@mprobst
Copy link
Contributor Author

mprobst commented Oct 26, 2015

Friendly ping?

@IgorMinar
Copy link
Contributor

@petebacondarwin is this too high risk for 1.5? several people at Google are asking for this change.

@IgorMinar
Copy link
Contributor

@lgalfaso - what do you think?

@lgalfaso
Copy link
Contributor

@IgorMinar it mostly moves code around, and it adds a little magic but it is reasonably documented. From my point of view, it looks safe for 1.5. If @petebacondarwin does not oppose, then I will merge it EOD Friday.

@petebacondarwin
Copy link
Contributor

Go for it @lgalfaso

@mprobst mprobst closed this in 8b6b428 Dec 10, 2015
@bisubus
Copy link

bisubus commented Dec 10, 2015

@petebacondarwin It looks like the commit does not affect later argument in $controller which still uses $injector.invoke.

@IgorMinar
Copy link
Contributor

sgtm

On Thu, Dec 10, 2015 at 12:21 PM bisubus [email protected] wrote:

@petebacondarwin https://github.com/petebacondarwin It looks like the
commit does not affect later argument

if (later) {

in $controller which still uses $injector.invoke.


Reply to this email directly or view it on GitHub
#12598 (comment).

@petebacondarwin
Copy link
Contributor

@petebacondarwin It looks like the commit does not affect later argument in $controller which still uses $injector.invoke.

@bisubus - thanks for pointing that out.
Can we create a new issue for that case so that we can track it and work on fixing it?

@petebacondarwin
Copy link
Contributor

I have created the issue at #13510

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.

10 participants