davideast / angularu-a2-migration

A demo app of upgrading an Angular 1.4 app to Angular 2.
51 stars 4 forks source link

Why not directly bind to the service? #1

Open juristr opened 9 years ago

juristr commented 9 years ago

Hi,

I was just looking u'r Angular U video about preparing for the NG 2 upgrade. Great video, thx. I was just curious about a thing.

Here: https://github.com/davideast/angularu-a2-migration/blob/master/a1/app.js#L29 Was there a reason you didn't directly bind to boxService.boxes?

function BoxListDirective() {
  return {
    ...
    controller: ['$scope', '$attrs', 'boxService', function($scope, $attrs, boxService) {
      //this.boxes = $scope.$eval($attrs.boxes);
      this.boxes = boxService.boxes;
      ...
    }]
  };
}

In that case you wouldn't have the need for a $watch at all, as Angular would be watching implicitly. Tried it here: http://plnkr.co/edit/bLTZhfhL4eaYYjsLG9oZ?p=preview

Was that just for the purpose of keeping the demo simple, as I know there are more complex scenarios where you would have to introduce watches and where your Rx observable strategy is great.

thx for your response :smile:

davideast commented 9 years ago

Great question! There are two main reasons behind this.

Firstly, the boxes property of the BoxService should have been marked private. I was going to use a the underscore convention with this._boxes. However, this was the main part I choked on the most while practicing. I also didn't want to get too much into detail on why it should be marked private.

Secondly, services shouldn't be stateful. This BoxService is okay when the boxes property is marked private, however it could be better. Below I refactored the BoxService to a BoxFactory out of preference. The big difference is that I now have a factory that doesn't store state. To call add you have to pass in an array of boxes. Dealing with state is tricky, its best to remove it where possible.

Plunker Demo

function BoxFactory() {
  return {
    _boxSubject: new Rx.ReplaySubject(),
    subscribe: function(subscription) {
      this._boxSubject.subscribe(subscription);
    },
    add: function(boxes) {
      boxes.push({ value: boxes.length + 1 });
      this._boxSubject.onNext(boxes);
    }
  };
}

Then inside of my controller I can subscribe.

function BoxCtrl(boxFactory) {
  this.boxes = [{ value: 1}];
  boxFactory.subscribe(function(boxes) {
    this.boxes = boxes;
  }.bind(this));
}
BoxCtrl.$inject = ['boxFactory'];

Finally, in my directive all I have to do is pass the boxes I passed from the controller.

function BoxListDirective() {
  return {
    controllerAs: 'boxListCtrl',
    controller: ['$scope', '$attrs', 'boxFactory', function($scope, $attrs, boxFactory) {
      this.boxes = $scope.$eval($attrs.boxes);

      this.add = function add() {
         boxFactory.add(this.boxes);
      }.bind(this);

    }]
  };
}
juristr commented 9 years ago

Awesome answer! Thx a lot for the time, really appreciate it :smiley:

So what you're basically suggesting is to have stateless services which are basically reduced to containing the pure logic (which in this stupid sample is a one-liner) and which serve as a hub for event registration (through Rx). The state is kept on the single UI elements which keep themselves updated through the observables mechanism.

Sounds great. I'll think about that and see what that'd mean on my existing Angular 1.x app. Maybe I'm coming back with some question again (if u don't mind :wink: )

Thx again!

NicholasBoll commented 9 years ago

I had the same question after watching the video. The updated code might be better at reducing state (actually now lives in the controller... I'd argue it should live in a model). But there is still a strange mixing of concerns. I think this is the original concern from @juristr

The box app has a subscription to the boxFactory which updates this.boxes which is passed down to the BoxListDirective. The BoxListDirective then also requires boxFactory so that it knows how to update. If the BoxListDirective needs to have such deep knowledge of the boxFactory, why involve a higher party?

At my work, I've been separating UI components out of applications - in a different repository even. This means that UI components cannot have any idea about how to get data - it is only passed down. This prevents the mess of 2-way data binding (also how to update data - ex: observables, immutable, etc), components just notify that something interesting happened (like events in Angular2). This makes those UI components truly reusable in the application as well as other applications - no assumptions of the data model are made.