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

Prototype Accessors Called Before Constructor/Injection. #12865

Closed
rcollette opened this issue Sep 16, 2015 · 3 comments
Closed

Prototype Accessors Called Before Constructor/Injection. #12865

rcollette opened this issue Sep 16, 2015 · 3 comments

Comments

@rcollette
Copy link

I'm seeing a behavior that seems to be both undocumented, and leads to an "impedance mismatch" with TypeScript, which I think the Angular team is generally interested in.

In AngularJs, prototype accessors of a directive's controller are called before the controller's constructor. I am not sure where this is documented or why it happens this way. If an accessor needs to use an injected service, you have to abandon that first set call. I don't know if returning from a setter and doing nothing has ramifications in a real world setting, but in a simple example it seems to solve the problem. "Prototype accessors" (my terminology may be incorrect) are what are generated by TypeScript and I believe are what ES6 get/set accessors do.

  var TestController1 = (function() {
    function TestController1(testService) {
      console.log("TestController1 constructor");
      this.testService = testService;
    }
    //This is how TypeScript generates properties.
    Object.defineProperty(TestController1.prototype, "aprop", {
      get: function() {
        console.log("TestController1 Get Value:",this._aProperty);
        return this._aProperty;
      },
      set: function(value) {
        console.log("TestController1 setter called with value: ", value);
        if(!this.testService){
          //in order to make this work I have to exit on the first call.
          console.log("TestController1: exiting, no service yet");
          return;
        }
        this._aProperty = this.testService.doSomething(value);
      },
      enumerable: true,
      configurable: true
    });
    return TestController1;
  })();

To make the "first" (which may not actually be the first) set call work, I can define a property on _this captured in the constructor. In this case the accessor has access to the injected service it needs. But now I'm back to the bloated defineProperty(ies) JS syntax rather than being able to use TypeScript's get/set syntax.

  var TestController2 = (function() {
    function TestController2(testService) {
      console.log("TestController2 constructor");
      var _this = this;
      this.testService = testService;

      //This is how I would typically create properties with angular
      Object.defineProperty(_this, "aprop", {
        get: function() {
          console.log("TestController2 Get Value:",this._aProperty);
          return this._aProperty;
          return _this._aProperty;
        },
        set: function(value) {
          console.log("TestController2 setter called with value: ", value);
          this._aProperty = _this.testService.doSomething(value);
        },
        enumerable: true,
        configurable: true
      });
    }
    return TestController2;
  })();

Console output:

script.js:61 TestController2 constructor
script.js:28 TestController1 setter called with value:  x
script.js:31 TestController1: exiting, no service yet
script.js:18 TestController1 constructor
script.js:72 TestController2 setter called with value:  y
script.js:8 service called with value:  y
script.js:28 TestController1 setter called with value:  x
script.js:8 service called with value:  x
script.js:68 TestController2 Get Value: Hello y
script.js:68 TestController2 Get Value: Hello y
script.js:24 TestController1 Get Value: Hello x
script.js:24 TestController1 Get Value: Hello x
script.js:68 TestController2 Get Value: Hello y
script.js:24 TestController1 Get Value: Hello x

See:
http://plnkr.co/edit/LDiKSzIJe2dQ7X3jS7V8?p=preview

Is this documented and intended behavior, or should TypeScript classes with getters/setters be able to use injected services straight away?

@nickroberts
Copy link

When using injected services in the get and set functions for Typescript class properties, the services are not available when setting them in the constructor to this.someService = someService;.

@lgalfaso
Copy link
Contributor

When using controllerAs, any values that will bind to the controller are set before the controller constructor. This is the expected behavior and is documented here https://docs.angularjs.org/api/ng/service/$compile#-bindtocontroller-

@rcollette
Copy link
Author

For posterity... the fact that prototype accessors are called before object instantiation when using controllerAs and isolate scope is documented as:

When the controller is instantiated, the initial values of the isolate scope bindings are already available.

Thank you for pointing to the documentation @lgalfaso

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

No branches or pull requests

3 participants