5

I'm tring to get 3-way data binding with firebase and angularfire. You can see what I've got in Plunker: http://plnkr.co/edit/RGA4jZK3Y6n4RkPCHK37

app.js:

angular.module('ideaBattle', ["firebase"]);

services:

angular
    .module('ideaBattle')
    .constant('FBURL', 'https://ideabattle.firebaseio.com/')
    .service('Ref', ['FBURL', Firebase])
    .factory('dataBank', function(Ref, $firebase) {
        return $firebase(Ref).$asArray();
    });

controller:

angular
    .module('ideaBattle')
    .controller('ideaListCtrl', displayIdeas);

displayIdeas.$inject = ['dataBank'];
function displayIdeas(dataBank){
    var vm = this;
    vm.ideas = dataBank;

    vm.upVote = function(idea){
        vm.ideas[idea.id].votes++;
    };
}

HTML:

<div ng-controller="ideaListCtrl as vm">
    <div ng-repeat="idea in vm.ideas | orderBy: '-votes'">
        <div>
            <h2>{{idea.name}}</h2>
            <p>{{idea.desc|limitTo: 190}}</p>
            <span class="btn" ng-click="vm.upVote(idea)">Vote! <span class="badge"> {{idea.votes}}</span></span>
        </div>
    </div>
</div>

Plunker version: http://plnkr.co/edit/RGA4jZK3Y6n4RkPCHK37

What it does, it gets the data from firebase and displays it correctly, but when I push the button to call upVote function it only updates locally. I know why it only works locally, but I don't know how to make it also update in firebase.

I've tried with $bindTo, but from what I understand it requires $scope to work, and I'm trying to use "Controller as vm" pattern without injecting $scope.

Can anybody tell me how to bite that?

5
  • did you call $save(), see the documentation Commented Dec 8, 2014 at 21:38
  • From what I understand $save is a function that I need to call everytime I want firebase to update. That's not really the approach I want. I'd like more of a "set-it-and-forget-it" 3-way data binding so that every change made to the data locally would be reflected in firebase. I know it can be done easiliy using $bindTo($scope, "data") method, but I don't want to use the $scope at all, hence my question, how to do it keeping this pattern. Commented Dec 8, 2014 at 21:48
  • vm.ideas[idea.id].votes++; vm.ideas.$save(idea.id).then(function(ref) { ref.key() === vm-ideas[idea.id].$id; // true }); Commented Dec 8, 2014 at 21:59
  • This works - ofcourse, but it's still not a 3-way data binding - just function updating firebase. I would need to call it everywhere I make a change, but I want it to be automatic, just like it is locally (2-way data binding). I know it can be done. I cannot figure out how. Commented Dec 8, 2014 at 23:03
  • Well everything is just a function. Try extending Array factory, to keep things DRY Commented Dec 8, 2014 at 23:14

3 Answers 3

10

tl;dr; — 3-way data-binding does not work with ControllerAs syntax. The bindTo method requires $scope.

You can use AngularFire with ControllerAs syntax, but you can't use it with ControllerAs with $bindTo.

$bindTo has a hard dependency on $scope and it will break without it.

If you want an example of using AngularFire with ControllerAs syntax, check out this Plunker demo.

  angular.module('app', ['firebase'])

  // constant for the Firebase we're using
  .constant('FBURL', 'https://<your-firebase>.firebaseio.com/todos')

  // return the Firebase ref as a service
  .service('Ref', ['FBURL', Firebase])

  // return the Todos from Firebase by returning the
  // array from the factory 
  .factory('Todos', function(Ref, $firebase) {
    return $firebase(Ref).$asArray();
  })

  // inject the Todos and assign them to "this"
  // for the ControllerAs syntax
  .controller('MainCtrl', function(Todos) {
    this.todos = Todos;
  });
Sign up to request clarification or add additional context in comments.

4 Comments

If you take a look at my code, you can see, this plunker you mentioned is exactly the one I build mine upon. But the problem still stands. I don't know how to make it use 3-way data binding.
As I mentioned in my answer, you can't use AngularFire and ControllerAs syntax with 3-way data-binding. bindTo relies on $scope, which goes against the grain of the ControllerAs syntax.
Is bindTo the only way to get 3-way data binding? Isn't there some way around it?
The bindTo method sets up automatic 3-way data-binding. However, you can still create a Firebase array like in the example above. Whenever a change happens you can manually add/update the array. The change will update Firebase, which will automatically update the array and rebind your template.
5

John Papa talks about one of the purposes of using the var vm = this; syntax instead of $scope in every controller is to make the use of $scope a conscious choice. In this case we need to include $scope.

I took David East's plunkr in his answer and fiddled with it a bit. It isn't perfect because it depends on the controllerAs value being 'vm'.

http://plnkr.co/edit/vLLaa7QJvfryYRD7cZvO?p=preview

  .controller('MainCtrl', function(Todos, $scope) { /* Add $scope */
    var vm = this;

    vm.todos = Todos.all();

    vm.lonelyTodo = Todos.get('-JeNOtYPv7AZmVAoZ1bu');
    vm.lonelyTodo.$bindTo($scope, 'vm.lonelyTodo'); /* Add three way binding */
  });

2 Comments

This is a great workaround especially if you're using TypeScript which promotes using classes with ControllerAs.
I think this is the answer people are looking for and it's also done the right way.
1

Adding a few clarifications comments to the response above using ES6/JS2015 systax as an example.

export class SomeController {
  constructor($firebaseObject, $scope) {
  'ngInject';    

  //using the firebase SDK 3.0 
  let obj = $firebaseObject(firebase.database().ref().child('someKey'));

  // To make the data available in the DOM, assign it to
  // 'this.data' accessible from DOM as $ctrl.data
  this.data = obj;

  // For three-way data bindings, you will still need to inject '$scope'
  // but you can alias your controller on $scope
  obj.$bindTo($scope, '$ctrl.data');

  // Why does this work? 
  // This works because angular 1x puts controllerAs
  // on top of $scope. So '$scope.$ctrl.data' is the same as 'this.data'.
  // Note: $ctrl is the default controllerAs syntax if not specified,
  // just change $ctrl to your defined controllerAs ailias if 
  // specified. 
  }
}

Comments

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.