In this Post pseudoclass, we’ve created a constructor that will set up our object. We created an updateDate() function, which sets the “date” property of the Post object to be now. We have a save() function that replaces much of the “save” functionality from our previous version of ContentCtrl. Our stopEditing() function simply flips the “editing” flag and resets our temporary object. Finally, if we want to view a string representation of our Post object (using toString()), it’s pretty handy to simply dump a JSON representation of it.

What follows is a controller actually using this service.

 // Controls the blog content including all methods related to posts
blog.controller('ContentCtrl', function ($scope, Posts, Post) {

    // util function to stop editing all of the posts
    $scope._stopEditingAllPosts = function (posts) {
        var i;
        if (angular.isArray(posts)) {
            i = posts.length;
            while (i--) {
                posts[i].stopEditing();
            }
        }
    }; 

    // Retrieve the posts from our "server". If this succeeds, we'll
    // put the posts into our $scope and do some post-processing.
    Posts.getPosts().success(function (data) {
        var posts = data.posts, i, post;
        $scope.posts = [];

    // Create Post objects and put them into the list of posts.
    i = posts.length;
    while (i--) {
        post = posts[i];
        // convert to millisecond precision
        $scope.posts.push(new Post(post.title, post.body, post.date * 1000,
            post.author, true));
        }
     });

    // This closes all editors if we happen to click the "new post" button
    $scope.$watch('posts', function (new_val, old_val) {
        if (new_val !== old_val) {
            $scope._stopEditingAllPosts(old_val);
        }
    });

    // Begins editing a post by making a temporary copy of the title and body,
    // and setting the "editing" flag for that post to be true.
    $scope.edit = function (post) {
        $scope._stopEditingAllPosts($scope.posts);
        post.beginEditing();
    };

    // Saves a post, sets its editing flag to false, puts it in the list of posts
    // and eliminates the 'new_post' from the scope.
    $scope.save = function (post) {
        post.save();
        $scope.posts.unshift(post);
        delete $scope.new_post;
}; 

    // Cancels a post edit. Does not copy temp data, and sets the editing flag
    // to false. In addition we set the controller-wide "new_post" model to be
    // false, so the "New Post" button appears.
    $scope.cancel = function (post) {
        post.stopEditing();
        delete $scope.new_post;
    }; 

    // instantiate new Post object provided by Post factory
    $scope.newPost = function () {
        $scope._stopEditingAllPosts($scope.posts);
        $scope.new_post = new Post('Enter Title Here', 'Enter Body Here', undefined, 'me');
        $scope.new_post.beginEditing();
    }; 
});

Procedurally, what this controller does, is ask the Posts service for posts whenever it is instantiated. The controller is instantiated when the ngController directive is encountered, compiled and linked. So if you had two ContentCtrls hanging out in your markup, the request for Posts would be made twice.

The functions we place into the scope are mainly straightforward. However, we do have a $watch that detects if the posts object has changed. If it has, that means we’ve gotten new data from the server. And if we’ve got new data from the server, then we want to stop editing stuff (throw it away) because we may have new stuff to edit. A smart system would detect collisions, but we’re the only person writing on the blog and this is not necessary.

What follows are unit tests for ContentCtrl:

 describe('ContentCtrl', function () {
    describe('basic methods', function () { 

        // all this stuff happens whenever we instantiate the controller
        beforeEach(inject(function (Posts, Post, $routeParams, $httpBackend, $http) {
            $http.defaults.transformRequest = []; // remove any fancy request transforms
            $httpBackend.expectGET('data.json').respond({posts: []});
            $controller('ContentCtrl', {$scope: scope, Posts: Posts,
                Post: Post, $routeParams: $routeParams});
            $httpBackend.flush();
        }));

        describe('_stopEditingAllPosts', function () {
            it('should call stopEditing() on all members of posts array',
                inject(function (Post) {
                    var posts = [
                         new Post(),
                         new Post(),
                         new Post()
                    ];
                    spyOn(Post.prototype, 'stopEditing');
                    scope._stopEditingAllPosts(posts);
                    expect(Post.prototype.stopEditing).toHaveBeenCalled();
                    expect(Post.prototype.stopEditing.calls.length).toBe(3);
                }));
        });

        describe('edit', function () {
            it('should call _stopEditingAllPosts', inject(function (Post) {
                var post = new Post();
                spyOn(scope, '_stopEditingAllPosts');
                scope.edit(post);
                expect(scope._stopEditingAllPosts).toHaveBeenCalled();
            }));
            it('should call Post.prototype.beginEditing', inject(function (Post) {
                var post = new Post();
                spyOn(Post.prototype, 'beginEditing');
                scope.edit(post);
                expect(Post.prototype.beginEditing).toHaveBeenCalled();
            }));
     });

    describe('save', function () {
        it('should call save on the post', inject(function (Post) {
            var post = new Post();
            spyOn(Post.prototype, 'save');
            scope.save(post);
            expect(Post.prototype.save).toHaveBeenCalled();
        }));
        it('should prepend to list of posts', inject(function (Post) {
            var post = new Post();
            scope.$apply(function () {
                scope.save(post);
            });
            expect(scope.posts.length).toBe(1);
            expect(scope.posts[0]).toBe(post);
        }));
        it('should delete new_post', inject(function (Post) {
            var post = new Post();
            spyOn(Post.prototype, 'save');
            scope.$apply('new_post = "foo"');
            scope.$apply(function () {
                scope.save(post);
            });
            expect(scope.new_post).toBeUndefined();
        }));
    });

    describe('cancel', function () {
        it('should call Post.prototype.stopEditing', inject(function (Post) {
            var post = new Post();
            spyOn(Post.prototype, 'stopEditing');
            scope.$apply(function () {
                scope.cancel(post);
            });
            expect(Post.prototype.stopEditing).toHaveBeenCalled();
        }));

        it('should delete new_post', inject(function (Post) {
            var post = new Post();
            spyOn(Post.prototype, 'save');
            scope.$apply('new_post = "foo"');
            scope.$apply(function () {
                scope.cancel(post);
            });
            expect(scope.new_post).toBeUndefined();
        }));
    });

    describe('newPost', function() {
        it('should call _stopEditingAllPosts', function() {
            spyOn(scope, '_stopEditingAllPosts');
            scope.newPost();
            expect(scope._stopEditingAllPosts).toHaveBeenCalled();
        });
        it('should create a new post', inject(function(Post) {
            spyOn(Post.prototype, 'beginEditing'); // blasts our data
            scope.$apply(function() {
                scope.newPost();
            });
            expect(scope.new_post.temp.title).toBe('Enter Title Here');
            expect(scope.new_post.temp.body).toBe('Enter Body Here');
            expect(scope.new_post.author).toBe('me');
        }));
        it('should call Post.prototype.beginEditing', inject(function(Post) {
            spyOn(Post.prototype, 'beginEditing'); // blasts our data
            scope.$apply(function() {
                scope.newPost();
            });
            expect(Post.prototype.beginEditing).toHaveBeenCalled();
       }));
    });
});

Nine times out of ten, you are going to want to use a service or a factory. But what if you have a service and need to configure it before it’s instantiated? This is when you use a provider.