3

When using AngularJS, I keep running into the problem of how to deal with the asynchronous behaviour and callback functions. In the below, how can I modify PostsService.getPostBySlug to return the desired post?

Posts service

Website.factory( 'PostsService', 
[ '$filter', '$http', function( filter, $http ) 
{
    // declare service
    var PostsService = {};

Return all posts (reads posts.json)

    PostsService.getPosts = function( callback )
    {
        $http
            .get( 'posts/posts.json' )
            .success( callback );
    }

Return one post based on its slug

    PostsService.getPostBySlug = function( slug, callback )
    {
        // declare post
        var postForSlug = null;
        console.log( postForSlug ); // prints 'null'

        // get all posts from service
        var posts = PostsService.getPosts( function( data )
        {
            // all posts
            var posts = data;
            console.log( posts ); // prints array of objects

            // return all posts
            return posts;
        });

        // filter by slug
        postForSlug = filter( 'filter' )
        ( 
            posts,
            { 
                'slug': slug
            } 
        );  

        console.log( postForSlug ); // prints 'undefined'

        // return post for the given slug
        return postForSlug;
    }

Return service

    // return service
    return PostsService;

}]);    

The output is

null BlogController.js:26
undefined BlogController.js:51
[Object, Object] BlogController.js:33

which goes to show that the order of execution is different from what I expected. I know that it is about the asynchronous behaviour and the callback function, but I don't really know how to fix it. I keep running into this problem and it would be very much appreciated if someone could show me how to deal with this type of situation.

1
  • 1
    I would suggest reading about promises in $http and $q. Commented Dec 6, 2013 at 21:53

2 Answers 2

1

In the meantime, here's what I would do.

WebSite.controller('BlogController',function(PostsService,$scope){
    PostService.getPosts()
        .then(function(posts){
            $scope.posts = posts;
        });
    PostService.getPostBySlug()
        .then(function(post){
            $scope.postBySlug = post;
        });

});

And your PostsService would look like this: (a chained promise!)

PostsService.getPosts = function()
{
    return $http.get('posts/posts.json').then(function(response){
        var data = response.data;
        //look it over, is it what you want?
        return data;
    },function(errResponse){
        //handle error.
    });
}

As for the filter. They are applied in the view using the | operator with desired parameters in the view, they're not really useful at all outside the template.

So Your getPostsBySlug should look like this:

PostsService.getPostBySlug = function( slug )
{     
    return this.getPosts().then(function(posts){
        var post = {};
        angular.forEach(posts,function(value,index){
            if(value.slug == slug){
                post = value;
            }             
        });
        return post;
    });
}

Hope this helps!

Sign up to request clarification or add additional context in comments.

8 Comments

Yes, I have a controller that pretty much does what your one does. But that wasn't the question. My question was how to clean-up the mess I made in PostsService.getPostBySlug so that it actually returns one post (to the controller who calls it)?
I want the filtering to be performed in the service. You wouldn't get all posts from a db only to then filter them down to a single one in your view using |, would you? Same case for this implementation that uses a json file instead of a db.
The json file contains a number of posts. Each post has a property called slug. PostsService.getPostBySlug is called from within a controller and is supposed to return one post based on its slug. A post slug is a unique identifier similar to an id. Hope that makes it clearer.
I updated the answer. Filters only filter data that is already on the client. If you can only get all of your posts in one glob(json file) and not query for them individually using $http. To filter the view would be your best solution. And by using promises.($q) angular will $digest and update the view for you.
|
1

I would use promises and the $q promise Api to achieve this.

see http://jsfiddle.net/6WuM3/

module.factory('PostsService', ['$filter', '$http', '$q', function (filter, $http, $q) {
    // declare service
    var PostsService = {};
    PostsService.getPosts = function () {
        var defered = $q.defer();
        $http.get('posts/posts.json').then(function(response){
          defered.resolve(response.data);
        });
        return defered.promise;
    }
    PostsService.getPostBySlug = function (slug) {
        var defered = $q.defer();
        PostsService.getPosts().then(function(posts){
            var filtered = filter('filter')(posts, {
                'slug': slug
            });
            defered.resolve(filtered);
        });
        return defered.promise;
    }
    return PostsService;
}])

regards :)

2 Comments

I have tried your above code in combination with Website.controller( "PostsCtrl" , [ '$scope', 'PostsService', function( $scope,PostService ) { $scope.posts= PostService.getPosts(); }]); (like in your jsfiddle). The posts do not show up in the view, and I think the problem is that the function PostService.getPosts() does not return the actual post objects, but instead returns Object {then: function, catch: function, finally: function}.
Changing the controller code from $scope.posts= PostService.getPosts(); to PostsService.getPosts().then( function( response ) { $scope.posts = response; }); solves the problem. But I don't really understand why?

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.