0

I have a page with a button that will change the state of the resource on the server. Everything is wired up and working fine. But I had to resort to a server response that sent the browser "back" because I don't want to display a new template for the action url.

This seems like a typical thing to do: ask the server to do something, then get the current state of the resource and redisplay it in the current template without refetching the template.

The markup on the page is like this:

<a ng-href="/api/preference/{{video._id}}/add_to_watchlist" data-method="get">
    <i rel="tooltip" data-original-title="Add to watchlist" class="icon-3x action-icon
        icon-plus-sign" ng-class="{iconSelected : video.user_watchlist == 1}">
    </i>
</a>

This highlights the icon depending on the value of $scope.video.watchlist. When clicked it fires a custom action on the server to add the video being viewed to the current user's watchlist. The url created is /api/preference/{{video._id}}/add_to_watchlist, this fires the server controller, which does the right thing but instead of going to a template for /api/preference/{{video._id}}/add_to_watchlist the server responds by telling the client to go "back" in Rails this is redirect_to :back.

Clearly this is wrong. It works but refetches the entire page markup and data.

The AngularJS controller that loads the original data is here:

GuideControllers.controller('VideoDetailCtrl', ['$scope', 'Video',
    function($scope, Video) {
        var pattern = new RegExp( ".*/([0-9,a-f]*)", "gi" );
        //ugh, there must be a better way to get the id!
        $scope.video = Video.get({ id: pattern.exec( document.URL )[1] });
    }
]);

Here is the resource that gets the json

var VideoServices = angular.module('VideoServices', ['ngResource']);

VideoServices.factory('Video', ['$resource',
    function($resource){
        return $resource("/api/videos/:id", {id: "@id"}, {
            update: {method: "PUT"},
            query: {
                isArray: true,
                method: "GET",
                headers: {
                    "Accept": "application/json",
                    "X-Requested-With": "XMLHttpRequest"
                }
            },
            get: {
                isArray: false,
                method: "GET",
                headers: {
                    "Accept": "application/json",
                    "X-Requested-With": "XMLHttpRequest"
                }
            }
        });
    }
]);

Somehow I need to

  1. tell the server to add_to_watchlist without changing the browser URL
  2. trigger a refetch of the video json without reloading the template.

1 Answer 1

1

It seems like you're relying on routing to handle a model update on the server, instead of doing it through angular's $resource service. Here's a quick and dirty:

Instead of routing, call a function when the user clicks:

<a ng-click="addToWatchlist(video._id)">
<i rel="tooltip" data-original-title="Add to watchlist" class="icon-3x action-icon
    icon-plus-sign" ng-class="{iconSelected : video.user_watchlist == 1}">
</i>
</a>

Add the click handler function to your controller (I'm using $http here, but you would be better off adding a custom action to your Video resource). On the success callback you can reload the video. Or, better yet, you can return the video from the add_to_watchlist action.

Check out http://docs.angularjs.org/tutorial/step_07 which explains the $routeParams (to answer your comment about getting the video id).

GuideControllers.controller('VideoDetailCtrl', ['$scope', '$http', '$routeParams', 'Video',
    function($scope, $http, $routeParams, Video) {
        $scope.video = Video.get({ id: $routeParams.videoId });
        $scope.addToWatchlist = function(videoId) {
            $http.get('/api/preference/'+videoId+'/add_to_watchlist').success(function() {
                $scope.video = Video.get({ id: $routeParams.videoId });
            };
        };


    }
]);
Sign up to request clarification or add additional context in comments.

5 Comments

That explains a lot. However, the response from the $http.get is 200 with a blank body as I expected but the success function never is called to refresh the model.
I just tested $http.get(...) with an empty 200 response, and the success function was called. Are you sure that's the issue?
Oops, 405, which I fixed. For posterity I learned from your answer that $routeParams is an object filled with each part of the route as named in the routeProvider. Also that attaching a function to a click looks first in the scope for that function name. The resource or $http.get or other verb returns a promise. To look into the data returned or fire the next http action you need a function attached to the ".success" method of the get (or other verb). Your example showed how to use all this.
Taking this a step further I tried returning just the changed data from $http.get() to save a round trip, as redmallard suggested but they never get resolved. Doing: $scope.video.prefs = $http.get('/api/preference/'+id+'/add_to_watchlist.json') Not sure how to do this because it turns video.prefs into a promise I think, but the promise never is resolved? Tried saving the promise and then on $http.get(...).success(... assigning the variable to videos.prefs but that didn't work either.
answer to the returning partial model here:angularjs-how-to-change-part-of-a-model

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.