Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularFire 3-way data binding is not updating firebase when a checkbox change

I'm developing a simple todo app with Angular and Firebase using AngularFire module.

So I have a boolean attribute in my model represented by a checkbox in the template, the problem is that I'm trying to use the three way data binding from AngularFire using the $bind method to keep the all changes syncronized (firebase data, DOM and ng-model) but the firebase data is not updating when I select a checkbox.

Here's my controller where I'm using the AngularFire $bind method:

angular.module('singularPracticeApp')
  .controller('TodoCtrl', ['$scope', 'TodoService', function ($scope, todoService) {
    $scope.todos = todoService;

    $scope.todos.$bind($scope, 'todo.done');

    $scope.addTodo = function () {
      $scope.todos.$add({text: $scope.todoText, done:false});
      $scope.todoText = '';
    };

    $scope.remaining = function () {
      var count = -11;
      angular.forEach($scope.todos, function(todo){
        count += todo.done? 0 : 1;
      });
      return count;
    };

    $scope.clear = function (id) {
      $scope.todos.$remove(id);
    };
  }]);

And here is the tempalte file:

<div ng-controller="TodoCtrl">
  <h4>Task runner</h4>
  <span>{{remaining()}} todos left.</span>
  <ul>
    <li ng-repeat="(id, todo) in todos">
      <input type="checkbox" ng-model="todo.done">
      <span ng-if="todo.done" style="color: #ddd;">{{todo.text}}</span>
      <span ng-if="todo.done == false">{{todo.text}}</span>
      <small ng-if="todo.done"><a href="" ng-click="clear(id)">clear</a></small>
    </li>
  </ul>
  <form ng-submit="addTodo()">
    <input type="text" ng-model="todoText" placeholder="New todo item">
    <input type="submit" class="btn btn-primary" value="add">
  </form>
</div>  

Am I missing something? Is really possible to make this work with a simple checkbox?

Thanks in advance.

like image 523
Bernardo Avatar asked Jan 12 '23 08:01

Bernardo


1 Answers

You haven't included todoService here so it's going to be difficult to give you an accurate answer. I'll assume that todoService returns a $firebase instance containing the todos since that seems likely. Keep in mind that the problem could be in that code as well.

Several problems you can address, which may resolve your issue:

Your TodoCtrl is not per-item

You seem to be using TodoCtrl as if it were created per-item in the ng-repeat. However, it exists outside the scope of ng-repeat and is only created once for the entire list.

Ng-repeat does not re-use your existing controller scope.

Directives operate in an isolate scope. That means that they do not share scope with your controller. So when you do ng-repeat="todo in todos" you do not add todo into your controller's scope.

This makes sense since each ng-repeat iteration would overwrite the same todo object.

You are trying to double-bind to a synchronized object

You are trying to create a three-way binding $scope.todos.[$todo].done, but you have already created a three-way binding on $scope.todos. Instead, let $scope.todos take care of synchronization.

You've attempted to bind $scope.todos to a property in itself

When you call $bind, you are binding $scope.todos to $scope.todos.todo.done. Obviously this self-referential statement isn't what you intended. I can't tell what is returned by your service but maybe you meant this:

todoService.$bind($scope, 'todos');

If you don't want to automatically push changes on the entire todos list, you can add a $save call instead of using $bind:

$scope.todos = todoService;

<input type="checkbox" ng-model="todo.done" ng-change="$parent.todos.$save(id)">

All together:

angular.module('singularPracticeApp')
   .service('todoService', function($firebase) {
      return $firebase( new Firebase(URL_TO_TODOS_LIST) );
   });
   .controller('TodoCtrl', function($scope, todoService) {
      todoService.$bind($scope, 'todos');

      $scope.addTodo = function () {
         $scope.todos.$add({text: $scope.todoText, done:false});
         $scope.todoText = '';
      };

      /** ... and so on ... **/      
   });
like image 68
Kato Avatar answered Jan 15 '23 11:01

Kato