Yegor  Gorodov
Yegor Gorodov

Reputation: 52

angularjs and google chrome extension

I have a simpe google-chrome extension app. I'm using bookmarks and it present in manifest file. Firstly, i use chrome bookmarks api in controller, and it works well. But i decided use factory for clear code and best practices.

My index.html file

<!DOCTYPE html>
<html lang="en" ng-app="BookmarksSharer" ng-csp>
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
    <title>bokmarks sharer</title>
    <link rel="stylesheet" href="/css/main.css"/>
    <script src="/javascript/jquery-2.1.1.min.js"></script>
    <script src="/javascript/angular.min.js"></script>
    <script src="/javascript/bookmark_sharer.js"></script>
    <script src="/javascript/MainController.js"></script>
    <script src="/javascript/BookmarkService.js"></script>
</head>
<body>
    <div id="main_popup" ng-controller="MainController">
        <p>Bookmarks</p>
        <ul>
            <li ng-repeat="bookmark_folder in bookmarks_folders" id="{{bookmark_folder.title}}">
                {{bookmark_folder.title}}
                <ul ng-repeat="bookmarks in bookmark_folder">
                    <li ng-repeat="bookmark in bookmarks | filter">{{bookmark.title}}</li>
                </ul>
            </li>
        </ul>
    </div>
</body>
</html>

bookmark_sharer.js is simple

var app = angular.module("BookmarksSharer", []);

MainController.js very simple too.

app.controller('MainController',['$scope', 'bookmarkFactory', function($scope, bookmarkFactory){
    $scope.boormarks_folders = bookmarkFactory.folders;
}]);

And my factory

app.factory('bookmarkFactory', function () {
    var bookmarks_folders;
    function allBookmarksFolders(){
        chrome.bookmarks.getTree(function(bookmarks_tree){
            bookmarks_folders = bookmarks_tree[0].children;
        });

        return bookmarks_folders;
    }

    return {
        folders: allBookmarksFolders()
    };
});

Why $scope.bookmarks_folders is undefined?

ExpertSystem your code not working too. Simple solution is

app.controller('MainController',['$scope', function($scope){
    chrome.bookmarks.getTree(function(nodes){
        $scope.bookmarks_folders = nodes[0].children;
        $scope.$apply();
    })
}]);

But i want organize my code using factories or services.

Upvotes: 2

Views: 748

Answers (1)

gkalpak
gkalpak

Reputation: 48211

chrome.bookmarks.getTree() is asynchronous, so by the time the getAllBookmarks() function returns, bookmarks_folders is undefined.

Your service exposes a property (folders) which is bound to the result of allBookmarksFolders(), alas undefined.

Since this operation is asynchronous, your service should return a promise instead, so the controller can use that promise and get the actual data when it is returnd:

// The service
app.factory('bookmarkFactory', function ($q) {
    function retrieveAllBookmarksFolders() {
        var deferred = $q.defer();
        chrome.bookmarks.getTree(function (bookmarks_tree) {
            deferred.resolve(bookmarks_tree[0].children);
        });
        return deferred.promise;
    }

    return {
        foldersPromise: retrieveAllBookmarksFolders()
    };
});

// The controller
app.controller('MainController', function($scope, bookmarkFactory){
    bookmarkFactory.foldersPromise.then(function (bookmarks_folders) {
        $scope.boormarks_folders = bookmarks_folders;
    });
});

The main problem arises from the fact that (in your implementation) you return the value bound to bookmarks_folders at that point and later on re-assign bookmarks_folders to hold a reference to a different object (bookmarks_tree[0].children).

The flow of events inside your service is somewhat like this:

  1. bookmarks_folders is declared (and initialized to undefined).
  2. allBookmarksFolders() is executed and returns (the still undefined) bookmarks_folders.
  3. folders is assigned a reference to the object currently referenced by bookmarks_folders (i.e. undefined).
  4. The getTree() callback executes and assigns to bookmarks_folders a reference to a different object (bookmarks_tree[0].children). At that point folders knows nothing about it and continues to reference to the previous value (undefined).

An alternative approach (one that is used by the $resource btw), is to not assign a new reference to bookmarks_folders, but to modify the already referenced object.

// The service
app.factory('bookmarkFactory', function () {
    var bookmarks_folders = [];   // initialize to an empty array
    function allBookmarksFolders(){
        chrome.bookmarks.getTree(function(bookmarks_tree){
            // Here we need to modify the object (array)
            // already referenced by `bookmarks_folders`

            // Let's empty it first (just in case)
            bookmarks_folders.splice(0, bookmarks_folders.length);

            // Let's push the new data
            bookmarks_tree[0].children.forEach(function (child) {
                bookmarks_folders.push(child);
            });
        });

        return bookmarks_folders;
    }

    return {
        folders: allBookmarksFolders()
    };
});

Upvotes: 4

Related Questions