2

Hello TypeScript/Angularists!

TL;DR Can I pass angular dependencies to a TypeScript module/class, so that those dependencies DON'T become attributes of class objects - instead, they are available through scope function parameters?

I know the recommended way of declaring things in angular/typescript is to create a TS class inside a TS module and release it as an angular service, because:

  • it can't be a factory (see this)
  • it's good to have modules
  • a class (comparing to a plain function) might be used to check compile-time types in typescript

An example of such approach is below:

/// <reference path="../../project.d.ts" />

module project.core.services {

  export class UiRoutes {
    static $inject = ['$state'];

    constructor(private $state: angular.ui.IStateService) {
    }

    public reloadCurrentView(): void {
      this.$state.go(this.$state.current.name, this.$state.params, {
        reload: true
      });
    }
  }

}

project.core.CoreModule.service('UiRoutes', project.core.services.UiRoutes);

Anyway, there is one great problem with that approach and that is - I'm forced to pass all angular dependencies into class constructor to make them object attributes (available everywhere, not encapsulated - because if the object is accessible then all it's attributes are accessible).

As an alternative, there is Backbone.js + Require.js example:

define(['jquery', 'use!backbone','models/model'], function($, Backbone, Model){

  View = Backbone.View.extend({
    //...
  });

  return new View();
});

where, as you can see, jquery, backbone and some internal stuff is available, but it's not saved as an object attribute - it's available through scope. This is purely natural JavaScript way of doing things - passing things through scope function parameters. In this situation, you can encapsulate more things and your code becomes less verbose. And, mainly, it's good to have a choice rather than to be forced to follow the only right rule.

Now I know that backbone is different than angular and, moreover, DI is totally different than require.js, blah blah blah.

What I want is a an example (or just a design) that allow me to specify all angular dependencies and make them accessible for a TypeScript module/class but not to make them attributes of class objects. This is possible with pure AngularJS (without TypeScript) afterall, so it might be possible along with TypeScript.


PS I'll happily provide better description of the problem, if it's not clear (comments, please). Buit I'm not really interested in answers like "it's not what you want, but it works for me, try this" :) if the answer is "no, that's not possible", than I'd appreciate a proof and/or reasons why.

2
  • hi @ducin, did you solve that issue? I am moving my angular ES5 code to TS and I have the same problem. Commented Jan 28, 2016 at 6:50
  • @demsey Actually the solution is what Ryan wrote in the accepted answer. Instead of a class you provide a function, but the usage of what you register in angular is the same. You keep angular DI-ed elements in a closure and you reuse them from the closure, not from object properties. Commented Jan 29, 2016 at 11:13

1 Answer 1

1

because if the object is accessible then all it's attributes are accessible

This may be true from a runtime perspective, but the runtime perspective is not the only one that matters. If you're using TypeScript and you mark the injected services as private, then no one is accessing these things unless they're being actively malicious. And odds are extremely good that your code is already not resilient to malicious actors inside its own runtime environment (trivially, someone who wanted to screw with your app could inject in any Angular service and overwrite its properties with their own).

So in that regard, the best thing to do is to not care about what things happen to be on a object somewhere versus in a closure somewhere.

Now I know that backbone is different than angular and, moreover, DI is totally different than require.js, blah blah blah.

To expand on "blah blah blah", a relevant difference here is that your requirejs module is effectively a singleton, whereas your Angular classes usually aren't (most of them are probably controllers).

We need to go back to the object vs closure difference to understand why that matters. If you're going to have closure privacy over your injected services (because compile-time privacy wasn't good enough for whatever reason), it follows that every instance of your object is going to need its own set of subclosures to be able to access those closed-over values.

Duplicating your function closures for every single instance of an object incurs a large performance penalty when there are many instances of the object. It's more allocations, more memory pressure, more time spent cleaning up things in the GC, more time spent marking or sweeping or refcounting in the GC, less locality of reference, less efficient JIT, and so on. This is why TypeScript and other languages don't give you ready-made footguns for making closure-based classes.

Now you're saying "But my service is a singleton!" and you're right. Singletons can be written as a closure like this:

module project.core.services {
  export function UiRoutes($state: angular.ui.IStateService) {
    function go() {
      $state.go(/* etc*/);
    }

    function other() { }
    function foo() { }

    return {
      go,
      other,
      foo
    }
  }
}
Sign up to request clarification or add additional context in comments.

1 Comment

am I right, that the UiRoutes you wrote is a factory design pattern which - in angular DI - returns a singleton for all depending modules? Is the above pattern useful for defining services, directives, models (e.g. restangular) appropriate or do you discourage using it that way?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.