stcho
stcho

Reputation: 2119

How to filter out or get rid of null objects within array? AngularJS, Firebase

My app is built using AngularJS, Yeoman stack, connected to the Firebase, AngularFire database. In my app I am trying to upload and delete pictures as shown below

enter image description here

It works fine except when I delete any imgs in between other images it does not actually remove the img but replaces it with an img with width and height of 0 as such... or it seems (more info in the bottom)

enter image description here

and if I delete another "in-bewteen" img same deal

enter image description here

only when I delete the outer most img does the page really remove the imgs.

enter image description here

^^Notice how the one in between two other images on the top row did not delete because it is surrounded horizontally via other img objects. I had to firebase.remove the bottom two imgs to the right of the one I deleted very first and only then the whole bottom row was removed.

I am not sure why I am getting this bug, could anyone give me some feedback?

Here is my deleteImg and updateImg function in my controller

angular.module('myApp').controller('MainCtrl', ['$scope', '$firebase', '$filter', function($scope, $firebase, $filter) {
$scope.deleteImg = function(img) {
    if (confirm('Are you sure you want to delete the img?') === true) {
        var deletingID = img.id; 
        var deletingLink = fireRef + "/" + img.id;
        var deleteRef = new Firebase(deletingLink);
        deleteRef.remove();
        $(".delete").remove();
        location.reload();
    }
};

$scope.updateImg = function(Img) {
    if (confirm('Are you sure you want to update the Img?') === true) {
        var updatingID = $scope.selectedImg.id; 
        var updatingLink = fireRef + "/" + $scope.selectedImg.id;
        var updateRef = new Firebase(updatingLink);

        var dataObject = {
            "name" : $scope.selectedImg.name, "description": $scope.selectedImg.description, "type": $scope.selectedImg.type ... etc...
        };
        updateRef.set(dataObject);
        location.reload();
    }
};
}]);

Here is how I instantiate my array and Firebase database as well as create new $scope.selectedImg. I am currently grabbing the data from firebase database and instantiating an array of Img objects. I use the array as my img models in the app.

var fireRef = new Firebase('MY_FIREBASE_URL');

$scope.imgs = []; //<<< instantiate a new array

var imgNum = 0;
var toArrayFirebaseLink = fireRef + '/' + imgNum;

fireRef.once('value', function(allSnapshot) {
    allSnapshot.forEach(function(imgSnapshot) {
        var i = imgSnapshot.child('id').val();
        if( i !== null ){
            // set databaseSnapshot to our img array
            $scope.imgs[i] = imgSnapshot.val();
        }
   });
});

$scope.instantiateImg = function(object) {
    $scope.selectedImg = {};
    // $scope.selectedImg.name = "";
    $scope.selectedImg.etc = "";
    $scope.selectedImg.etc = "";
};

//Created selectedImg with data retrieved from database to be used within the views.
$scope.selectImg = function(object) {
    $scope.selectedImg = object;
};

Here is my html view

<div class="container">  
 <div class="col-xs-6 col-sm-4 col-md-4 col-lg-3 campaign-thumbs" ng-repeat="img in imgs track by $index" ng-animate="'animate'">

<button type="button" class="delete" ng-click="deleteImg(img)">
      <span>&times;</span>
      <span class="sr-only">Delete</span>
</button>

<img ng-click="selectImg(img)" src="{{img.imgSrc}}" class="img-responsive" data-toggle="modal" data-target="#imgModal">
  </div>
</div>

Using the chrome AngularJS extension I identified that the elements that I deleted within the database using the firebase remove() function, my $scope.imgs array still has a "null" element within the space in which I removed it. See below picture

enter image description here

How do I get past this bug?


EDIT1: I followed @m.e.conroy's advice and added the splice functionality and thought it worked perfectly, especially because I removed the location.reload() and in the view it showed that the pictures were getting deleted without a refresh. However, when I do refresh the page the same "ghost" imgs and its delete buttons are showing up again.

When I checked the AngularJS chrome extension for the scope models, it was showing that the outermost img (the img that is to the right of the "in-between" ghost imgs) is taking over the deleted images, meaning the deleted images are getting set with the same name, id, and other attributes of the outer most img object. Once again, according to the chrome extension the array elements are not removed but only replaced. Once I refresh though it goes back to the same issue I had before of "null" element in array. This is weird because in my Firebase dashboard it shows that the imgs are indeed deleted however it does not reflect on my view until the outer most img is deleted. Below is a view of my Firebase dashboard and index. Only when the img 24 is deleted does the changes actually apply.

enter image description here


EDIT2: As referenced by @katowulf in this article (https://www.firebase.com/blog/2014-04-28-best-practices-arrays-in-firebase.html), I am having the exact problem he describes in the "Good Use Cases for Storing Data in Arrays" section. Currently, I am trying to target each and every one of the imgs after the one that I delete and change their IDs to id - 1 as well as the imgs location in the fire ref to firebar_url/ref - 1 but I feel like I am trying to force an implementation rather than finding the correct solution. Should I try to refactor my code to utilize objects like he suggests? (I tried that initially but I wasn't able to grab the objects and its attributes therefore had to rely on firebase snapshots to instantiate and create my array. Reference: How would I convert my new Object data I am retrieving from Firebase into the JSON form as I had before). Any feedback is welcome.


MY SOLUTION: To manually change index values as well as corresponding 'id' attributes to -1 for all img objects following the one that has been deleted.

$scope.deleteImg = function(index, img) {
    if (confirm('Are you sure you want to delete the Img?') === true) {
        $scope.imgs.splice(index,1); //remove objects in array

        var i; 
        for(i = index; i < $scope.imgs.length; i++) {
            //I want to grab the next img object 
            var previousIndexID = i; //start from the picture right after the one deleted and change its id to id-1
            var newIndexID = i+1;
            var previousIndexLink = fireRef + "/" + previousIndexID;
            var newIndexLink = fireRef + "/" + newIndexID;
            var previousIndexRef = new Firebase(previousIndexLink);
            var newIndexRef = new Firebase(newIndexLink);

            var idIndexLink = fireRef + "/" + previousIndexID + "/id";
            var idIndexRef = new Firebase(idIndexLink);

            console.log(previousIndexRef.toString()); // element[index] that got deleted
            console.log(newIndexRef.toString());      // element[index] next img object

            //copy the next img object into the location of the object that just got removed() 'url/num-1' and 'url/id' value 
            newIndexRef.once('value', function(snap) {
                var i = snap.val();
                previousIndexRef.set(i);
            });
            idIndexRef.once('value', function(snap) {
                var i = snap.val();
                idIndexRef.set(i-1);
            }); 

        };
        // until the length is reached and then I want it to delete the last one
        var lengthID = $scope.imgs.length; 
        var lengthLink = fireRef + "/" + lengthID;
        var lengthRef = new Firebase(lengthLink);
        lengthRef.remove();
    }
};

Next time I will most definitely use services/object style but for this app this works great. It was fun to solve it anyway :) Thanks for all the help @Kato and @m.e.conroy

Upvotes: 1

Views: 1393

Answers (2)

m.e.conroy
m.e.conroy

Reputation: 3538

You need to create a method for ng-click that passes the $index from the ng-repeat then in your controller method called by ng-click remove the image from the array (of course after the user confirms) and remove it from the database. removing it from the array will automatically trigger ng-repeat to re-render.

Currently you're using jQuery to remove the object from the DOM but it still exists in the Angular array. Angular doesn't know you used jQuery to remove the object from the view. If you do what is describe above you won't even need to use jQuery or manipulate the DOM, Angular will do it for you. You should not have to do a $location.reload either.

$scope.deleteImg = function(i,id){
    if(confirm('..') === true){
        $scope.imgs.splice(i,1);
        // do firebase deletion
    }
};

In template:

<button type="button" class="delete" ng-click="deleteImg($index,img.id)">
    ...
</button>

EDIT

Actually you'll only need to pass the $index parameter to the method since splice returns the removed element and that will be your image object. i.e.:

$scope.deleteImg = function(i){
    var _img = $scope.imgs.splice(i,1);
    // use _img.id with your firebase delete statement
};

and then the template is simply just:

<button type="button" class="delete" ng-click="deleteImg($index)">...

Upvotes: 1

paka
paka

Reputation: 1641

Not sure what is Firebase, but as i understan you delete item in array, that become null. And you need to .splice() $scope.imgs array to really remove an item from ngRepeat

Upvotes: 0

Related Questions