GHOST-34 GHOST-34 - 4 months ago 67
AngularJS Question

AngularFire $save() not a function

I've seen other posts about problems with $save(), but I couldn't relate it to my code. I have a profile controller with an updateProfile() method that ultimately attempts to save the new data to the database after it has been changed.

I have defined my profile controller as follows:

angular.module('angularfireSlackApp')
.controller('ProfileCtrl', function($state, md5, profile) {
var profileCtrl = this;
var currentUser = firebase.auth().currentUser;
profileCtrl.profile = profile;

// Retrieves the user's email from input field, hashes it, and saves the data to the database
profileCtrl.updateProfile = function() {
console.log(profileCtrl.profile); // Profile object exists and is populated as expected
profileCtrl.profile.emailHash = md5.createHash(currentUser.email);
profileCtrl.profile.$save();
}
});


My user service:

angular.module('angularfireSlackApp')
// Provides a means of retrieving User data or list of all Users
.factory('Users', function($firebaseArray, $firebaseObject) {
// Provides a means of retrieving User data or list of all Users
// Create a reference to users that can be used to retrieve an array of users
var usersRef = firebase.database().ref("users");
users = $firebaseArray(usersRef);


var Users = {
// Returns a firebase object of a specific user's profile
getProfile: function(uid) {
return $firebaseObject(usersRef.child(uid));
},
// Returns the display name of a specific user
getDisplayName: function(uid) {
return users.$getRecord(uid).displayName;
},
// Returns the Gravatar url that corresponds to the user
getGravatar: function(uid) {
return 'www.gravatar.com/avatar/' + users.$getRecord(uid).emailHash;
},
// Returns a firebase array of users
all: users
};

return Users;
});


Profile state from main app:

.state('profile', {
url: '/profile',
controller: 'ProfileCtrl as profileCtrl',
templateUrl: 'users/profile.html',
resolve: {
auth: function($state) {
firebase.auth().onAuthStateChanged(function(user) {
if (user == null) {
$state.go('home');
console.log("In auth but user NOT valid");
} else {
console.log("In auth and user valid");
}
});
},
profile: function(Users) {
firebase.auth().onAuthStateChanged(function(user) {
if (user != null) {
console.log("In profile and user valid");
return Users.getProfile(user.uid).$loaded();
} else {
console.log("In profile but user NOT valid");
}
});
}
}
});


console.log:

debug

For some reason I'm getting an error that profileCtrl.profile.$save() is not a function. I know that the profileCtrl.profile object is legitimate and that I'm using $save() appropriately, but I just can't figure out what else could be the problem.

My gut feeling is that I'm missing something simple, but I'm brand new to AngularJS and Firebase so I wouldn't be surprised.

Answer

In the resolve of your "profile" state you are not returning anything.

You should fix it to:

auth: function($state, $firebaseAuth) {
  return $firebaseAuth().$onAuthStateChanged().then(function(user) {
    if (!user) {
      $state.go('home');
      console.log("In auth but user NOT valid");
    } else {
      console.log("In auth and user valid");
    }
  }); // classic situation to use $requiresAuth() 
},
profile: function(Users, $firebaseAuth) {
  return $firebaseAuth().$requireSignIn();
}

Moreover, your service shouldn't keep the reference to the $firebaseArray but create it for each controller that wants to use it.

The down-side is you'll have to make some changes, but the up-side is a more predictable, maintainable code:

    var Users = {
    // Returns a firebase object of a specific user's profile
    getProfile: function(uid) {
        return $firebaseObject(usersRef.child(uid));
    },
    // Returns the display name of a specific user
    getDisplayName: function(uid) {
        // This is actually an issue to get the name synchronously, but I see no reason why not using the Firebase SDK and fetch a-sync
        //return users.$getRecord(uid).displayName;
        return usersRef.child(uid)
            .child('displayName')
            .once('value')
            .then(function (snap) {
                return snap.val();
            });
    },
    // Returns the Gravatar url that corresponds to the user
    getGravatar: function(uid) {
        // Again fetch a-synchronously
        //return 'www.gravatar.com/avatar/' + users.$getRecord(uid).emailHash;
        return usersRef.child(uid)
            .child('emailHash')
            .once('value')
            .then(function (snap) {
                return 'www.gravatar.com/avatar/' + snap.val();
            });
    },
    // Returns a firebase array of users
    all: function () {
        return $firebaseArray(usersRef);
    }
};

Also refer to the new AngularFire docs: API reference