justinmahar / SublimeCSAutocompletePlus

CoffeeScript autocompletions and more!
172 stars 12 forks source link

Support for AngularJS #14

Open Iristyle opened 11 years ago

Iristyle commented 11 years ago

This might be a bit tricky, but I thought I'd suggest it anyhow.

AngularJS has a module and dependency injection system, where you register a type by name, and essentially return a function used to construct your type.

Here's the docs on controllers and how you would generally register one with angular.module.

This plugin would be totally amazing if it could map the injected dependency back to the function that's returned.

This may sound a bit abstract -- here's a simple example.

angular.module('app').controller 'OrderCtrl',
[
  # named dependencies registered with Angular
  '$scope',
  '$routeParams',
  'Order',
  # local names we're associating with them
  ($scope, $routeParams, Order) ->

    params =
      # $routeParams services maps orderId at runtime, so nothing to do here
      orderId: $routeParams.orderId

    # here's where autocomplete would really pay off -- when using the Order service
    $scope.myOrder = Order.get
]

The name of the controller being registered is OrderCtrl. It has the dependencies $scope, $routeParams and Order (in this case, an Angular service) injected, and they are mapped to variables of the same name. Note that Angular has two syntaxes for it's DI system, but the one I'm using here is preferred, for minification purposes (normally a variable named $scope is good enough to have Angular find it... BUT.. that name is changed during minification).

There are a bunch of different constructs in Angular -- but the one that would be the most advantageous to have autocomplete over would be services (so functions registered by module.service, module.provider, module.factory or module.value). Generally the only thing depending on an Angular controller is some data-binding code in HTML... but it's common for Angular services to be used across many controllers, directives, etc.

Does that make sense? Not sure how feasible this is, but man would it be awesome to have... and hey, maybe there's already a way to make this work that I'm not aware of.

If none of this was clear, please let me know.

Thanks!

And btw -- I maintain a package for AngularJS snippets in CoffeeScript, but the snippet system is clearly a bit dumbed down compared to the capabilities here.. so I might work on trying to bring the snippets in here as type definitions.

Iristyle commented 11 years ago

To make this a little more clear. The Order service I'm injecting looks like this

'use strict'

angular.module('app').factory('Order',
[
  '$resource', '$location', 'Config',
  ($resource, $location, Config) ->
    base = Config.Values.ServiceBase
    uri = "#{$location.protocol()}:#{base}/orders/:orderId"
    return $resource(uri, { orderId: '@Id' })
])

In my custom type definitions I have the definitions for $resource

// http://docs.angularjs.org/api/ngResource.$resource
{
  "name": "$resource",
  "enabled": true,
  "constructors": [
    {"args": [{"name": "url"}]},
    {"args": [{"name": "url"}, {"name": "{paramDefaultsHash}"}]},
    {"args": [{"name": "url"}, {"name": "{paramDefaultsHash}"}, {"name": "{actionsHash}"}]}
  ],
  "static_properties": [],
  "static_methods": [],
  "instance_properties": [],
  // TODO: handle $prefixed actions
  // var user = User.get({userId:123}, function() {
  // user.abc = true;
  // user.$save();
  // });
  // non-GET instance actions: instance.$action([parameters], [success], [error])
  "instance_methods": [
    {"name": "delete", "args": [{"name": "postData"}]},
    {"name": "delete", "args": [{"name": "{paramsHash}"}, {"name": "postData"}]},
    {"name": "delete", "args": [{"name": "{paramsHash}"}, {"name": "postData"}, {"name": "() -> successFn"}]},
    {"name": "delete", "args": [{"name": "{paramsHash}"}, {"name": "postData"}, {"name": "() -> successFn"}, {"name": "() -> errorFn"}]},
    {"name": "get", "args": [{"name": "{paramsHash}"}]},
    {"name": "get", "args": [{"name": "{paramsHash}"}, {"name": "() -> successFn"}]},
    {"name": "get", "args": [{"name": "{paramsHash}"}, {"name": "() -> successFn"}, {"name": "() -> errorFn"}]},
    {"name": "query", "args": [{"name": "{paramsHash}"}]},
    {"name": "query", "args": [{"name": "{paramsHash}"}, {"name": "() -> successFn"}]},
    {"name": "query", "args": [{"name": "{paramsHash}"}, {"name": "() -> successFn"}, {"name": "() -> errorFn"}]},
    {"name": "remove", "args": [{"name": "postData"}]},
    {"name": "remove", "args": [{"name": "{paramsHash}"}, {"name": "postData"}]},
    {"name": "remove", "args": [{"name": "{paramsHash}"}, {"name": "postData"}, {"name": "() -> successFn"}]},
    {"name": "remove", "args": [{"name": "{paramsHash}"}, {"name": "postData"}, {"name": "() -> successFn"}, {"name": "() -> errorFn"}]},
    {"name": "save", "args": [{"name": "postData"}]},
    {"name": "save", "args": [{"name": "{paramsHash}"}, {"name": "postData"}]},
    {"name": "save", "args": [{"name": "{paramsHash}"}, {"name": "postData"}, {"name": "() -> successFn"}]},
    {"name": "save", "args": [{"name": "{paramsHash}"}, {"name": "postData"}, {"name": "() -> successFn"}, {"name": "() -> errorFn"}]}
  ]
}

I would expect completions over my Order instance above to pick up these methods from $resource. This is a more complicated example, but I hope you get the idea.

One way I thought to make the tools do what I want is to do something like classifying Order... but I still don't get proper completion on my Order class.

'use strict'

class Order

  # @param [$resource] resource
  # @param [$location] location
  # @param [Config] Config
  # @returns [$resource]
  constructor: (resource, location, Config) ->
    base = Config.Values.ServiceBase
    uri = "#{location.protocol()}:#{base}/orders/:orderId"
    location.absUrl()
    return resource(uri, { orderId: '@Id' })

angular.module('app').factory('Order',
[
  '$resource', '$location', 'Config',
  ($resource, $location, Config) ->
    o = new Order($resource, $location, $Config)
    # still nothing here for 
    #o.
])

So almost there, but not quite. I've also tried the above as a non-constructor method like foo or @foo and nada. I can't use something like class Order extends $resource either -- even if that were semantically correct, autocomplete doesn't seem to work. The only thing I can do at the moment to force the hand of the autocompletion is to use a line like this... then I don't need Order to appear in a class (and if it is in a class, I lose any other properties / methods I might have for Order)

o = new Order($resource, $location, $Config) # [$resource]

BTW -- I have all of the AngularJS mapping in a CoffeeComplete Plus Custom Types.sublime-settings locally... testing out what I can do to improve the overall experience. It's pretty good, but there are a few rough edges (most of the other PRs I've submitted).