0

My Goal:

I have a list that i need to iterate. Each item in this list has a list of its own (nested). For each of these nested list items I need to have a set of two checkboxes for the user to designate their choice. I need to sync these checkbox choices back into the scope variable that should be synced with firebase. Below is a simplified version of my code.

Example

My Controller

fbutil.syncObject("items").$bindTo($scope, "items");

My View

<div ng-repeat="item in items">
    {{item.title}}

    <div ng-repeat="option in item.options">
        {{option.title}}

        <input type="checkbox" id="{{item.title}}+{{option.title}}+pos" ng-model="option.results" ng-true-value="'positive'" ng-false-value="''"><label for="{{item.title}}+{{option.title}}+pos"></label>

        <input type="checkbox" id="{{item.title}}+{{option.title}}+neg" ng-model="option.results" ng-true-value="'negative'" ng-false-value="''"><label for="{{item.title}}+{{option.title}}+neg"></label>
    </div>
</div>

My Problem

I just updated from angularfire 0.7.1 to and 0.8 and started getting this error when iterating through a nested ng-repeat in my view. it worked fine before, but now i get the error when i select a checkbox, stating:

Firebase.set failed: First argument  contains an invalid key ($$hashKey) in property '0.testCustom.0'.  Keys must be non-empty strings and can't contain ".", "#", "$", "/", "[", or "]"

I understand (i think) this has something to do with Angularfire either getting the data as an array or an object. I understand that this is because angular/firebase attaches these keys (like $id or $$hashKey) to objects and using angular.copy() to clean these up does not work.

Here is a similar question but @kato's only reponse is not to fetch the parent but go directly to the child collection. I can't think of a way to do this in my situation... @kato's post in the comments:

Don't fetch the parent (account/) as an object and then try and use it's child as a collection. The embedded array is not going to work since angular attaches invalid properties to it. [...] AngularFire is a bindings library--it binds objects and collections--not a local copy of your Firebase data tree. Just get the collection directly

/////////Edit

In response to @jacobawenger's comment, how would i save these changes back to firebase if i use $asArray() instead of $asObject() to pull in my collection?

here is a plunker of what I am trying to do:

Plunker example

I have a 'saveChange()' function that i do not know how to implement

7
  • 1
    As @kato was alluding to in his response on the other thread, we are moving away from using $bindTo() on deeply nested structures. It is not efficient and almost never what you actually want. Here, instead of getting the your /items/ node using $asObject(), try using $asArray(). Then you can loop over it's options and call $save() on them individually when you want to update one. This way, you are not saving your entire collection every time one option changes, which is pretty much how $bindTo() works. That simply will not scale if you have lots of items/options. Commented Aug 21, 2014 at 15:36
  • please see my edit above... Commented Aug 21, 2014 at 16:05
  • I'm not really sure where you got fbutil.syncArray from... That is not an AngularFire API you should be using. Instead, use AngularFire's $asArray() method which you can call from any $firebase object. Also, what error are you seeing this second time around? A minimized fiddle or codepen would make this significantly easier for me and would allow me to actually provide you with a fixed/working example. Commented Aug 21, 2014 at 16:13
  • oops, i didn't explain. i am using the wrapped function from the angularfire seed app (see on github). i'll try to come up with a plunker example Commented Aug 21, 2014 at 16:16
  • my plunker: plnkr.co/edit/eaFzGtMxMQ4Wa2rslDIX?p=preview Commented Aug 21, 2014 at 17:22

1 Answer 1

4

When storing data in Firebase, it is very important to use a proper data structure. The data structure you chose is not one that works well with Firebase. You want a list of diagnoses, each with a list of tests. However, simple nesting them like you did forces Firebase to coerce them into a pseudo-array. You should be explicit about it being a list by adding each diagnosis/test via the push() method. Please read our new docs on saving lists of data to understand how to choose a good Firebase data structure. You are doing the big "ANTIPATTERN" at the top of that section which we suggest you avoid.

Using the Plunkr you provided, I went ahead and made a new demo Firebase (since I couldn't see/edit your personal Firebase). I stored the data in a better data structure for Firebase. I then only had to make a few edits to your Plunkr to make this working example. The code should be pretty self explanatory. In your view, loop through the tests for the current diagnosis and then pass the diagnosis to your saveChange() method:

<div ng-repeat="test in diagnosis.tests" class="list-group-item">
  <h4>{{test.title}} - {{test.outcome}}</h4> 

  <input type="checkbox" ng-model="test.outcome" ng-true-value="positive" ng-false-value="negative" ng-change="saveChange(diagnosis)" /> Positive

  <input type="checkbox" ng-model="test.outcome" ng-true-value="negative" ng-false-value="positive" ng-change="saveChange(diagnosis)" /> Negative
</div>

In your controller, just call $save() on $scope.diagnoses:

$scope.saveChange = function(diagnosis) {
  $scope.diagnoses.$save(diagnosis);
}

And that's it!

Sign up to request clarification or add additional context in comments.

1 Comment

It seems the Firebase and Plunkr links are now dead. Any way you can add more of the pertinent code to your answer?

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.