Reputation: 10930
To learn AngularFire I'm trying to build a simple CRUD app. The INDEX and NEW pages went smoothly but I'm stuck on the SHOW and EDIT pages. I can't get a selected record to display on the SHOW page.
With MongoDB and Angular it's easy to make the SHOW page. On the INDEX page you put in an anchor link with the ID of the record you want to show:
<a ng-href="#/{{record._id}}"><span>{{record.title}}</span<</a>
This puts the record ID into the URL, e.g., www.website.com/#/K9hr32rhror
Then on the SHOW page you can use {{record.title}}
or {{record.author}}
or whatever you want. Angular magically remembers the ID of the record you clicked on the INDEX page. How Angular does this I have no idea. :-)
In MongoDB the ID is called _id
. In Firebase the ID is called $id
. So I changed the code on the INDEX page to
<a ng-href="#/{{record.$id}}"><span>{{record.title}}</span<</a>
This successfully puts the ID into the URL.
The problem is that no record is displayed on the SHOW page. Angular appears to have forgotten what {{record}}
refers to.
I tried putting in a leading /
:
<a ng-href="/#/{{record._id}}"><span>{{record.title}}</span<</a>
That makes no difference.
I tried parsing the ID from the URL with
var key = location.hash.split('#/')[1]
This returns the ID. When I console.log the $firebaseArray(ref) object I see a collection (array) of all my records. But when I use collection.$getRecord(key)
or collection.$keyAt(0)
or collection.$indexFor(key)
I get null or -1, indicating that AngularFire can't find the record.
Here's my full controller code:
app.controller('ShowController', ['$scope', '$firebaseArray', '$location',
function($scope, $firebaseArray, $location) {
var ref = new Firebase("https://my-firebase.firebaseio.com/");
list = $firebaseArray(ref);
console.log(list); // []
console.log(location.hash.split('#/')[1]); // -K9hr32rhror_
var key = location.hash.split('#/')[1];
console.log(list.$getRecord(key)); // null
console.log(list.$keyAt(0)); // null
console.log(list.$indexFor(key)); // -1
Next I tried using ng-repeat="record in collection | filter : key">
but I can't figure out what key
should be.
I also googled "Angular Firebase CRUD SHOW page" but none of the example CRUD apps had SHOW pages. Am I misunderstanding something fundamental? E.g., should I build the SHOW and EDIT views with <ng-hide>
and <ng-show>
on the INDEX page, and not bother to make separate SHOW and EDIT pages?
Upvotes: 2
Views: 1521
Reputation: 21
import { Injectable } from "@angular/core";
import { AngularFirestore } from "@angular/fire/firestore";
import User from "../models/user-model.model";
@Injectable({
providedIn: "root",
})
export class FireStoreService {
constructor(private _db: AngularFirestore) {
fireStore = this;
}
public async userDataById(uid: string) {
const userSnap = await this._db.doc(`users/${uid}`).get().toPromise();
return userSnap.data();
}
public async createUser(_id : string,userModel : User) {
try {
//Generate Firebase Id for New Node
//Push User Data in that Node
return this._db.collection('/User/').doc(_id).set({
isDelete: userModel.isDelete,
isActive: userModel.isActive
});
} catch(error) {
console.log(error)
return null;
}
}
public async getUsersListing() {
try {
// Query to get users where isDelete is false and isActive is true
const usersListingSnapshot = await this._db.firestore.collection("/User/")
.where("isDelete", "==", false)
.where("isActive", "==", true)
.get();
// Mapping the documents to data
const usersListings = usersListingSnapshot.docs.map((doc) => doc.data());
return usersListings;
} catch (error) {
console.error('Error getting user listings:', error);
return null;
}
}
public async getCaptionDetailsById(Id: string) {
try {
const querySnapshot = (await this._db.firestore.collection("/User/").get()).docs
.filter((doc) => doc.id === Id)
.map((doc) => doc.data());
return querySnapshot;
} catch (error) {
return null;
}
}
public async updateUserDetails(userModel: UserModel): Promise<boolean> {
try {
const result = await this._db.collection('/User/').doc(userModel.id).update({
name: userModel.name,
number: userModel.number,
gender: userModel.gender,
isVerify: userModel.isVerify,
updateAt: userModel.updateAt,
photo: userModel.photo,
});
return true;
} catch (error) {
console.error('Error updating user details:', error);
return false;
}
}
public async deleteData(id: string): Promise<Boolean> {
try {
// Mark the document as deleted instead of actually deleting it
await this._db.collection('/RouteMaster/').doc(id).update({ isDelete: true, isActive: false });
console.log(`Document with ID ${priceId} marked as deleted successfully`);
return true;
} catch (error) {
console.error('Error marking document as deleted:', error);
return false;
}
}
}
Upvotes: 0
Reputation: 10930
Thanks, David East, the problem was asynchronous data. Here are two working controllers. The first controller gets the data array and selects the record I want out of the array:
app.controller('ShowController', ['$scope', '$firebaseArray', '$location',
function($scope, $firebaseArray, $location) {
// set up Firebase
var ref = new Firebase("https://my-firebase.firebaseio.com/"); // goes to Firebase URL (fbURL)
var list = $firebaseArray(ref); // puts
list.$loaded().then(function(x) { // promise that executes when asynchronous data has loaded
var key = location.hash.split('#/')[1]; // parses key from URL query string
var showItem = list.$getRecord(key); // gets the record using the key passed in from the URL query string
var showItems = []; // make an array
showItems.push(showItem); // push the record into the array
$scope.showItems = showItems; // attach the array to $scope
// now 'ng-repeat="showIdea in showIdeas"' will iterate through the array, which has only one element
// i.e., this is how you make Angular display one thing
})
.catch(function(error) { // if the data doesn't load
console.log("Error:", error);
});
}]);
The second controller uses David East's suggestion of using $firebaseObject
to download only the record I want. I added some code to make it display using ng-repeat
. Is there another way to display a single record with Angular?
app.controller('ShowController', ['$scope', '$firebaseObject', '$location',
function($scope, $firebaseObject, $location) {
// set up Firebase
var ref = new Firebase("https://my-firebase.firebaseio.com/"); // goes to Firebase URL (fbURL)
var key = location.hash.split('#/')[1]; // parses key from URL query string
var itemRef = ref.child(key); // sets up the query
var showItems = []; // make an array
showItems.push($firebaseObject(itemRef)); // makes the query and push the record into the array
$scope.showItems = showItems; // attach the array to $scope
}]);
And here's the view:
<table ng-repeat="showItem in showItems">
<tr><td>Name:</td><td>{{showItem.itemName}}</td></tr>
<tr><td>Title:</td><td>{{showItem.itemTitle}}</td></tr>
</table>
Upvotes: 1
Reputation: 32604
The problem you're dealing with is asynchronous data flow.
app.controller('ShowController', ['$scope', '$firebaseArray', '$location',
function($scope, $firebaseArray, $location) {
var ref = new Firebase("https://my-firebase.firebaseio.com/");
var key = location.hash.split('#/')[1];
$scope.list = $firebaseArray(ref); // data is downloading
console.log($scope.list.length); // will be undefined, data is downloading
$scope.list.$loaded().then(function(list) {
console.log(list); // data has been downloaded!
list.$getRecord(key); // data is available
});
});
But .$loaded()
is usually unnecessary because AngularFire knows when to trigger $digest
. In your case you want to use a $firebaseObject
.
app.controller('ShowController', ['$scope', '$firebaseObject', '$location',
function($scope, $firebaseObject, $location) {
var ref = new Firebase("https://my-firebase.firebaseio.com/");
var key = location.hash.split('#/')[1];
var itemRef = ref.child(key);
$scope.item = $firebaseObject(itemRef);
});
This grabs the single item based off of the key
, rather than downloading the entire list and plucking out one item.
Upvotes: 3