0
votes

I'm trying my hand at using Firebase and AngularFire for an app I am building. I am admittedly a newcomer to angular and Firebase, so any help would be appreciated.

I have the following database structure in Firebase:

  • EPICS
    • Epic 1
      • Id
      • Title, etc.
    • Epic 2
      • Id
      • Title, etc.
  • USERS

    • User 1

      -activeepics

      • active epic 1

      • active epic 2

On the following page, I have a ng-repeat for each epic in the database. Each epic has a row of buttons, one of which is giving me trouble. If the current user has already started the epic (e.g. the epic appears in "activeepics" for that user), then the button should say "Tap Out." If the epic is not in the user's activeepics, then it should display "Start."

The button should do different things when it is clicked, depending on the button label. So if the button label says "Start," the app should add the epic into the current users "activeepics" and change the button label to "Tap Out." In contrast, if the button label is "Tap Out," the app should remove the epic from "activeepics" and change the button label back to "Start."

However, I am getting all sorts of weird behavior. The app displays the correct button labels when I use dummy id's such as 1 or 0. However, when I add an epic with a Firebase generated ID, all labels reset to "Start." I've also noticed that the app is looping through each epic multiple times (I entered a debug console.log for each epic displayed).

I think the problem may be that I have multiple promise objects after binding the Firebase data to $scope. Please help if you can!

Here is the code:

View

<div ng-repeat="(name,epic) in epics | filter:search | orderBy:order | filter:{category:category}" class="epic">
        <div>

                <div class="small-block-grid-3 epicActions">
                    <li><a href="#"> Remix </a></li>
                    <li><a href="#/share"> Share </a></li>
                    <li><a href="#" ng-click="handleLabel(name)" prevent> {{getLabel(name)}} </a></li>
                </div>
            </ul>
        </div>  
    </div>

Controller

$scope.desiredUser = UserService.getCurrentUser();

        var ref = new Firebase("https://epicly.firebaseio.com/epics");
        angularFire(ref, $scope, 'epics');

        var ref2 = new Firebase("https://epicly.firebaseio.com/users");
        angularFire(ref2, $scope, 'users').then(function(){

                for (var i = 0; i<$scope.users.length; i++){
                    if($scope.users[i].email === $scope.desiredUser){
                        $scope.currUser = $scope.users[i];
                    }
                }


            var ref3 = new Firebase("https://epicly.firebaseio.com/users/" + $scope.currUser.id + "/activeepics");
            angularFire(ref3, $scope, 'activeEpics').then(function(){

                console.log($scope.activeEpics);
                $scope.getLabel = function(epic){
                    console.log(epic);
                    for(var i = 0; i < $scope.activeEpics.length; i++){
                        if ($scope.activeEpics[i].id === epic) {
                            return "Tap Out";
                        } 
                    }
                    return "Start";
                }   

                $scope.handleLabel = function(name){
                    var label = $scope.getLabel(name);
                    if(label === "Tap Out"){
                        //remove from active epics
                    } else {
                        //add to activeepics
                        $scope.toAdd = {"id": name}
                        $scope.activeEpics.push($scope.toAdd);
                    }
                }
            });
        });
2
A few comments about readability/style: you may want to name your refs something meaningful, like epicsRef, usersRef, activeEpicsRef. Also, it might be clearer if the handleLabel() param is the same as the getLabel() param (e.g. getLabel(epicName) and handleLabel(epicName) instead of getLabel(epic) and handleLabel(name)). - bennlich

2 Answers

0
votes

I think part of your problem might be that you're trying to iterate through an object as though it were an array. If you store data in Firebase with sequential numerical ids (0, 1, etc.) AngularFire will treat your data like an array. If you use keys (like those created with Firebase.push()), AngularFire will treat your data like a dictionary of keys and values.

I would specify the data type you expect your angularFire variables to be ahead of time, so insert $scope.users = [] and $scope.activeEpics = {}, or whatever the case may be, before calling AngularFire(), then inspect the objects to see whether they're actually dictionaries or arrays, and change your iteration methods appropriately.

0
votes

To answer your immediate questions first:

  1. Why do all labels reset to "Start" when you add an epic?

    Your $scope.getLabel method is attempting to iterate over $scope.activeEpics, the result of an angularFire promise. But $scope.activeEpics is an object, not an array, so it doesn't have indices or a .length property. Even if it did, the hypothetical resulting array element wouldn't have an id property the way you are expecting it to. Instead, you are looking for the name() of the activeEpic ref to compare with the name() of the epic ref. This actually simplifies your life, because you just want to know if a given epic name exists in your activeEpics object, so that method could look like this:

    $scope.getLabel = function(epicName){
      var labelText = $scope.activeEpics[epicName] ? "Tap Out" : "Start";
      return labelText;
    }
    

    As it stands, your iteration isn't running and everything just reverts to "Start".

  2. Why is your $scope.getLabel function executing so many times?

    This is a function of the AngularJS watching system, because your view is calling a method that then changes the view, so the watch runs again which triggers the method again (to put it somewhat crudely), and so on and so forth for all of the epics that need labels. See this SO answer for a better explanation.

If I might offer some general suggestions, it looks like you are trying to find a user by their email in a somewhat roundabout way. You might try using the Firebase priority system to accomplish the same thing more elegantly, as explained here. It would also be easier to follow the code if you tightened up the formatting and used more explicit naming of variables and parameters across the board. Just my two cents. Good luck!