Angular - Remove (DELETE)


In this final video in the Angular CRUD implementation part of this series, we are going to cover how to Delete records using Angular and our API.

We have already set up the API side of the implementation, so if you haven't already done so, be sure to check out the API - DELETE video.

We won't need a route for this behaviour, and we have already added the 'button' (technically an anchor / regular old link with some swanky Bootstrap styling applied) to our List / table view, so all we need to do is hook things up.

As a reminder, here is what we have so far in our /app/blogPost/list/list.html file:

<!-- /app/blogPost/list/list.html -->

    <!-- * snip * -->

    <tr ng-repeat="post in blogPosts">
        <td>{{ post.id }}</td>
        <td>{{ post.title }}</td>
        <td>
            <a href="#!update/{{ post.id }}" class="btn btn-default btn-sm">Edit</a>
            <a href="#" class="btn btn-danger btn-sm">Delete</a>
        </td>
    </tr>

    </tbody>
</table>

The first thing to do is add an ng-click directive to the a tag, specifying what function we want to run when the 'button' is clicked:

<a href="#" ng-click="remove(post.id)" class="btn btn-danger btn-sm">Delete</a>

I'm going to want to run a function on the $scope called remove, and that will have the current post.id passed in as its only argument.

There's a quirk to ng-click that we are going to experience here if we leave in the href="#". This will cause a weird page refresh which we don't want.

Two potential fixes to this are either remove the href entirely, or - more preferably for me, as I'd expect to see it there - is to leave it empty:

<a href="" ng-click="remove(post.id)" class="btn btn-danger btn-sm">Delete</a>

Remove and Filter

Now, let's set up the remove function inside the controller. Just as a reminder, we are going to add the delete / remove functionality to the listController, rather a separate deleteController:

// /app/blogPost/list/listController.js

'use strict';

angular.module('myApp.blogPost')

.controller('listController', ['$scope', 'Api', '$filter', function($scope, Api, $filter) {

  $scope.blogPosts = [];

  Api.getAll()
      .then(function (result) {
        console.log('result', result);
        $scope.blogPosts = result.data;
      }, function (error) {
        console.log('error', error);
      });

    $scope.remove = function (id) {
        console.log('this would have removed', id);
    }

}]);

Note now the inclusion of $filter, which will come in handy shortly.

Much like every other API call in this part of the series, we are going to need to add a new function to our Api factory to handle deletes. However, delete is a reserved keyword in JavaScript so we can't use that for our function name. Instead, I have opted for remove, and as you will see at various points in the video, this is something I frequently forget :)

// /app/blogPost/Api.js

'use strict';

angular.module('myApp.blogPost')

    .factory('Api', [
        '$http',
        function ($http) {

            var ROOT_URL = 'http://api.symfony-3.dev/app_dev.php/posts';

            function get(id) {
                return $http.get(ROOT_URL + '/' + id);
            }

            function getAll() {
                return $http.get(ROOT_URL);
            }

            function post(blogPost) {
                return $http.post(ROOT_URL, blogPost);
            }

            function put(id, data) {
                return $http.put(ROOT_URL + '/' + id, data);
            }

            function remove(id) {
                return $http.delete(ROOT_URL + '/' + id);
            }

            return {
                get: get,
                getAll: getAll,
                post: post,
                put: put,
                remove: remove
            }

        }
    ]);

That's the final setup for our Api factory. All functions have now been added. The remove function was essentially the same as the get function, but using $http.delete rather than $http.get. And just to reiterate, all of these actions are intentionally simple, as the interesting 'stuff' happens in the .then / promise handlers.

We already have the Api as a dependency in our listController so no extra work required on that front. We can now flesh out the remove function.

// /app/blogPost/list/listController.js

    // * snip *

    $scope.remove = function (id) {
        Api.remove(id)
            .then(function (result) {
                $scope.blogPosts = $filter('filter')($scope.blogPosts, function (value, index, array) {
                    return value.id !== id;
                });
            }, function (error) {
                console.error('error', error);
            });
    }

This is perhaps the trickiest part of this whole setup. What's happening here is as follows:

Firstly, we send in a DELETE request by including the id in a call to the delete endpoint on our Symfony 3 API backend.

Assuming things go well, then secondly we get back a response with a 204 status code. This means the record was deleted successfully, but the API has "no content" to send us back. Cool.

Then (or, ahem, .then) we need to update the client side to remove the array entry with that id from the local / in memory collection of blog post objects.

Now, at this point we could simply do a hard refresh of the page, and our Symfony API will send back the correct list of BlogPost entries. But we don't want to do a full page refresh, otherwise our Twig implementation would have been good enough.

An alternative possibility would be to trigger another getAll request, and assuming that went well, replace the contents of $scope.blogPosts with the 'refreshed' collection. But that's another API request, and not only would it be quicker to do this 'locally' (in the user's browser / in-memory), it would also be more proper.

With this in mind, what we really want is to remove the deleted entry from our in-memory collection. And one way of doing this is to filter through the collection, in the process creating a new array that doesn't contain the deleted blog post.

Filtering With Angular

I want to stress here that I'm not sure which way is more proper. To me, the simpler of the two is to go with the plain / vanilla .filter approach.

However, knowing about Angular's Filter is fairly useful, and is generally more useful to me, personally, inside templates / views. Interestingly, the syntax to use the filter inside a template is much nicer than inside a controller. Read more here.

If you are interested in trying both, I've created a JSBin example where you can try out both.

filter example on jsbin.com

The gist of the filter - vanilla or Angular - is to run a single function against every item in the array, and that function needs to return true or false.

If the function returns true, then we want to keep the item. If the function returns false, we want to remove - or filter out - the item.

The computer science-y name for this function (regardless of the functions implementation) is a 'predicate'. This is not just limited to JavaScript, in fact Doctrine's Collection uses predicates in its own filter implementation. Useful stuff to know, I'm sure you will agree.

And that's it. Once the blogPost array has been filtered, the result will be immediately updated in the view, thanks to Angular's two-way binding.

Hoorah, all that so that we didn't need to refresh.

As mentioned, that's the last part in this Angular CRUD implementation. In the next video we will get started implementing the very same thing, only using React.

Code For This Video

Get the code for this video.

Episodes