6

I'm trying to use some of angulars best practices defined on the google-styleguide site: https://google-styleguide.googlecode.com/svn/trunk/angularjs-google-style.html

But at the moment I'm struggling with some issues. Before I used this styleguide I had the $scope variable available to do a $watch on a variable for instance.

app.controller('myController',['$scope', function($scope) {
    $scope.$watch('myVariable', function(val) {
       alert("I'm changed");
    });
}]);

Now with my new approach I don't know how to handle this? Should I still inject $scope? Because I do not have to inject $scope when I'm not using $watch.

function myController($scope) {
   var vm = this;

   vm.myVariable = "aVariable";
   vm.$watch('vm.myVariable', function(val) {
      // error because $watch is undefined
   });

   //$scope.$watch - works
}

app.controller('myController',['$scope', myController]);

The styleguide also advices to make use of prototypes. But what if I had to inject a service? What is the best approach to use a service inside your prototype?

function myController(MyService) {
   var vm = this;

   vm.myService = MyService;
}

myController.prototype.callService = function() {
   var vm = this;

   vm.myService.doSomething();
}

Is this correct? Or am I missing something, is there a place where I can find more information about this style of angular programming?

In my opinion it feels more like natural javascript and I want to use this way of organizing my AngularJS apps.

Thanks in advance

Update

For the 'service' problem I was thinking of something as follows:

function MyBaseController(AService, BService, CService) {
   this.aService = AService;
   this.bService = BService;
   this.cService = CService;
}

function myController() {
   var vm = this;
   MyBaseController.apply(vm, arguments);
}

myController.prototype.doSomething() {
   var vm = this;
   this.aService.somethingElse();
}

But this doesn't feel right imo..

Dieterg
  • 16,118
  • 3
  • 30
  • 49
  • Great question! For your question about the $watch, I'm wondering if the style guide is trying to avoid calling $watch within a controller since they state "Controllers are classes." Since you know when data is changing within a controller, could it be that your "$watch" is executed when a specific method is called within the controller? – Pete Mar 27 '14 at 13:50
  • Actually I want to do a `$watch` on a variable that's getting changed inside a directive. I think there wouldn't be a problem when you change a variable inside a method. The, let's call it scope, will get updated anyway. – Dieterg Mar 27 '14 at 13:55
  • 1
    In a directive, you can access scope from Link, Compile, and Controller, so $watch is accessible in those cases. – Pete Mar 27 '14 at 14:00
  • But how do I check for updates in my controller? I know I can $watch a variable inside my directive. But how do I reflect these changes to my controller? Normally I would do an $apply in my directive and afterwards I would $watch for a change inside my controller. – Dieterg Mar 27 '14 at 14:03
  • have you tried `$scope.$watch('controller.field',...)` – vittore Mar 27 '14 at 14:04
  • 2
    I don't think you should be changing the controller variable within the directive because then the view is modifying the model. The directive, should pass commands to the controller, so you would know when the variable is changed. Otherwise, if you need to, you could have the directive-controller fire an event that the controller is listening to and then process the change. – Pete Mar 27 '14 at 14:07
  • @mister_rampage that indeed makes sense, thanks. So basically it's not possible that a variable gets changed outside of a controller function? If so, I'm doing something wrong? – Dieterg Mar 27 '14 at 14:13
  • @DieterGoetelen It's not impossible to change a variable outside of a controller, it's just that it should be avoided. The reasoning is that if you keep all your state changes in one place, it'll be easier for you and others to test, debug, and understand your code. – Pete Mar 27 '14 at 14:18

2 Answers2

7

It is perfectly valid to inject $scope to get access to things like $watch even when your using the "controller as" syntax. For example:

JS

var MyController = function($scope) {
    $scope.$watch('ctrl.someVar' function() {
        ...
    });

    this.someVar = 123;
}

MyController.$inject = ['$scope'];

HTML

<div ng-controller="MyController as ctrl">
    ....
</div>

The first example you gave of injecting a service into a controller is good. For the inheritance example I would do something like this.

var BaseController = function(AService, BService) {
    this.aService = AService;
    this.bService = BService;
}

BaseController.prototype.doSomethingWithAAndB = function() {
    ...
}

var MyController = function(AService, BService, CService) {
    BaseController.call(this, AService, BService);

    this.cService = CService;
}

MyController.$inject = ['AService', 'BService', 'CService'];

//Note: you'll need to add a polyfill for Object.create if you want to support ES3.
MyController.prototype = Object.create(BaseController.prototype);

If you find it is too cumbersome to specify all the parameters in the child controller you could always just inject $injector and pass that up to your base controller.

rob
  • 17,995
  • 12
  • 69
  • 94
  • Re your inheritance example, it might be worth setting the prototype constructor as well `MyControlleer.prototype.constructor = MyController`. See http://stackoverflow.com/questions/8453887/why-is-it-necessary-to-set-the-prototype-constructor/8454111#8454111 – Beyers Mar 27 '14 at 20:17
1

@rob answer is correct. There are perfectly valid reasons to still want $scope injected in your controllers, $watch $on $emit $broadcast to name a few. Sure you can get away with this requirement in some cases using directives or services, but it's not always worth the time or complexity.

I've given the controller as syntax a go, but found that in most of my use cases I still had a dependency on $scope in my controllers. I disliked this and so I ended up with the following convention:

var MyController = function($scope) {
  $scope.vm = {};
  var vm = $scope.vm;

  vm.propA = 1;
  vm.propB = 2;
  vm.funcA = function(){};

  $scope.$watch('vm.propA' function() {
  });
}

So it uses the 'old school' approach but with a new school feel. Everything in the view hangs off vm.

Beyers
  • 8,968
  • 43
  • 56