Angular Js和Google API客户端.js(gapi)

30

我花了一天的时间才使它正常工作,所以我认为我的经验可能对某些人有用。也许其他一些人会找到改进。

所以我两天前开始使用AngularJS。我想让它与Google Cloud Endpoints一起工作,创建一个后端接口。这对我来说是个麻烦。

gapi的JavaScript客户端带有异步加载,因此在gapi未定义时,Angular初始化将崩溃。

所以您需要在gapi初始化时引导Angular:

  1. 删除ng-app="myApp"
  2. 添加<script src="https://apis.google.com/js/client.js?onload=googleOnLoadCallback"></script>
  3. 添加回调函数:

    function googleOnLoadCallback(){  
        var apisToLoad = 1; // must match number of calls to gapi.client.load()  
        var gCallback = function() {  
            if (--apisToLoad == 0) {  
                //Manual bootstraping of the application  
                var $injector = angular.bootstrap(document, ['myApp']);  
                console.log('Angular bootstrap complete ' + gapi);  
            };  
        };  
        gapi.client.load('helloWorld', 'v1', gCallback, '//' + window.location.host + '/_ah/api');  
    }
    

感觉很好,不过打个电话怎么样?

所以这是控制器:

angular.module('myApp.controllers', []).  
    .controller('MyCtrl', ['$scope' ,'helloWorldService',  
        function($scope,greetingsService) {
          helloWorldService.loadData($scope);  
    }]);

这里是服务:

angular.module('myApp.services', [])
service('helloWorldService', [function() {
   this.loadData = function($scope)  {
     //Async call to google service
     gapi.client.helloWorld.greetings.listGreeting().execute(
        function(resp) {
            if (!resp.code) {
                console.debug(resp);
                $scope.greetings = resp.items;
                // Because it's a callback,
                // we need to notify angular of the data refresh...
                $scope.$apply();
            }
      });
   };
}]);

通过 Angular,您的页面可以神奇地更新。

请随意指出我犯错的地方。


嗨,@Samuel,它不起作用。我在这个问题上卡住了。你能详细解释一下吗? - ARV
谢谢,这个很好用。 - shieldstroy
谢谢您发布这个 - 对我帮助很大 :) - Xander
只是一个优化提示:在这里最好使用$scope.$digest,因为它仅在当前的$scope对象上启动一个脏检查循环;而$scope.$apply更加昂贵,因为它会启动整个应用程序的脏检查循环。 - hyang123
我们有 Angular2 的周转时间吗? - Rahul Sharma
8个回答

25
与其使用引导程序或设置超时,最有效的方法是在/同时进行服务器请求时让Angular加载。我遵循了在AngularJS + Cloud Endpoints:构建现代Web应用程序的食谱中描述的建议,该建议执行以下操作。

像往常一样保留你的ng-app指令(无需引导程序)

<html ng-app="myApp">
<head>
  <script src="angular.js" type="text/javascript"></script>
  <script src="app.js" type="text/javascript"></script>
  <script src="https://apis.google.com/js/client.js?onload=init"></script>
</head>
<body ng-show="backendReady">

在你的JS文件中任意位置创建一个全局变量,用于GAPI回调函数。

var app = angular.module('myApp', []);

var init = function() {
  window.initGapi();
}

app.controller('MainController', function($scope, $window, gapiService) {
  var postInitiation = function() {
    // load all your assets
  }
  $window.initGapi = function() {
    gapiService.initGapi(postInitiation);
  }
});

app.service('gapiService', function() {
  this.initGapi = function(postInitiation) {
    gapi.client.load('helloWorld', 'v1', postInitiation, restURL);
  }
});
从上面的链接中:
“你不想在第一个init()方法中执行初始化的原因是为了尽可能将代码放在AngularJS世界中,例如控制器、服务和指令。这样,你就可以充分利用AngularJS的全部功能,并拥有所有的单元测试、集成测试等等。”
“这似乎是一种迂回的方式,但它优化了速度、可测试性和关注点分离。”

@CadeThacker,您的编辑被拒绝了,但它是正确的,所以我复制了它。谢谢。 - willlma
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - mpgn
1
看看我的回答。简而言之:对于initinitGapi,使用不同的函数名称。 - willlma
嗯,但是为什么单元会在控制器之前死亡呢?请提供您的代码问题,我来看看。 - willlma
我已经为此创建了一个新问题。http://stackoverflow.com/questions/31524300/the-correct-order-to-load-google-client-api-gapi-and-angular-js - Tianxiang Zhang
显示剩余2条评论

6

很好的帖子,谢谢!这种方法对我很有效。您的index.html文件中代码的顺序可能很重要。在我按照以下顺序排列后,它才能正常工作。

...
<script>
  function googleOnLoadCallback(){
      alert('googleOnLoadCallback called');
      var apisToLoad = 1; // must match number of calls to gapi.client.load()
      var gCallback = function() {
          if (--apisToLoad == 0) {
              //Manual bootstraping of the application
              var $injector = angular.bootstrap(document, ["myApp"]);
              console.log("myApp bootstrap complete " + gapi);
          };
      };
      gapi.client.setApiKey("my_client_id");
      gapi.client.load("translate", "v2", gCallback);

  }
</script>
<!-- See https://developers.google.com/api-client-library/javascript/samples/samples -->
<script src="https://apis.google.com/js/client.js?onload=googleOnLoadCallback"></script>
</head>

4
虽然已经有进展,但值得一提的是angular-googleapi,它很好地包装了一些Google Calendar和Google Plus API调用,并且易于扩展。
在检查授权时,您需要将以下代码添加到控制器中:
$scope.authenticated = false;

$scope.$on("google:authenticated", function(){
   $scope.authenticated = true;
   $scope.$on('googleCalendar:loaded', function(){
       # work your magic here
       # $scope.calendars = googleCalendar.listCalendars();
       # $scope.$apply();
   });
});

function checkAuth() {
   setTimeout(function(){
       gapi.auth === undefined ? checkAuth() : googleLogin.checkAuth();
   }, 20);
}

checkAuth();

我不确定我完全理解了。但是为什么我们不能将checkAuth()附加到提供者上呢?这似乎更方便。谢谢你的建议。 - contributorpw

3
我编写了一个简单的指令来异步加载谷歌地图API:
// js/directives/gmapAsync.js

(function(){
'use strict';

angular.module('app').directive('gmapAsync',
    ['$window', '$rootScope', gmapAsync]
);

function gmapAsync($window, $rootScope){

    var gmapScript = $window.document.createElement('script');  

    $window.onGmapScriptLoaded = function(){
        console.log('google maps script loaded');

        $rootScope.gmapApiLoaded = true;
        $rootScope.$broadcast('gmap.api.loaded');

    };

    return {
        restrict: 'A',
        transclude: false,
        scope:false,
        link:   function(scope, element, attributes){

            if (navigator.onLine) {
                appendScript();
            } else {
                $window.addEventListener('online',appendScript);
            }

            function appendScript(){
                gmapScript.type = 'text/javascript';
                gmapScript.src = 'https://maps.googleapis.com/maps/api/js?v=3.exp&' + 'callback=onGmapScriptLoaded';
                $window.document.body.appendChild(gmapScript);
            }
        }
    };
}
})();

然后在您的主控制器中,您可以处理该事件:

// js/controllers/AppCtrl.js

(function(){
'use strict';

    angular.module('app').controller('AppCtrl',[$scope,AppCtrl])

    function AppCtrl($scope){

        $scope.$on('gmap.api.loaded',function(){
            // your stuff to init after the api is loaded
        });
    }

})();

你只需要在body标签中声明指令即可:

<!DOCTYPE html>
<html>

    <head></head>

    <body data-ng-app="app" data-gmap-async data-ng-controller="AppCtrl">

        <!-- template body -->

        <script type="text/javascript" src="js/app.js"></script>
        <script type="text/javascript" src="js/controllers/AppCtrl.js"></script>
        <script type="text/javascript" src="js/directives/gmapAsync.js"></script>
    </body>

</html>

0

所以我也遇到了同样的问题。将这段代码放在我的工厂中解决了问题。

var initialize = function() {
    if(gapi.client == undefined) {
        setTimeout(function() {
            initialize()
        }, 1000);
    } else {
        gapi.client.setApiKey("<api_key>");
        gapi.client.load('youtube', 'v3').then(function() {
            console.log("youtube is ready")
        });
    }
};

initialize()

基本上,问题在于尝试在gapi.client加载之前调用它。如果您只是检查它是否已加载,如果没有,则在一秒钟后再次尝试(您可以将时间设置为任何您想要的值,如果您希望用户在页面加载后相对较快地需要此功能,则将其设置得更低)。
我曾经为此苦苦挣扎,这是唯一对我有效的方法...希望这能帮到你!

0

我使用了类似于willlma的解决方案,但我的应用程序使用了UI Router,因此无法知道将调用哪个控制器。

我能够通过JavaScript Promise解决这个问题。

index.html

<html ng-app="myApp">
<head>
    <script src="angular.js" type="text/javascript"></script>
    <script src="app.js" type="text/javascript"></script>
    <script src="https://apis.google.com/js/client.js?onload=init">
</head>

app.js

var app = angular.module('myApp', []);

app.controller('MainController', function($scope, gapiService) {
    gapiService.then(function(gapi) {
        // You can use gapi normally here;
    });
});

app.service('gapiService', function($window) {
    return new Promise(function(resolve, reject) {
        if ($window.gapi !== undefined) {
            console.log("Have gapi already");
            resolve($window.gapi);
        } else {
            console.log("Waiting for gapi");
            $window.init = function() {
                resolve($window.gapi);
            }
        }
    });
});

0

我做了以下操作

gapi-service.js

'use strict';

app.factory('Gapi', ['ENV', function(ENV) {

return {
load: function load() {
  console.log('loading google apis...');
  if (typeof gapi.client === 'undefined') {
    setTimeout(load, 500);
  } else {
    gapi.client.setApiKey(ENV.googleToken);
    gapi.client.load('storage', 'v1', function() {
      console.log('loaded! :)');
      var request = gapi.client.storage.buckets.list({ project: ''});
      console.log(request);
      request.execute(function(response) { console.log(response); });
    });
  }
}
  };
}]);

index.html

<!DOCTYPE html>
<html>
  <head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
<title>"Txtbinge"</title>
  </head>

  <body ng-app="myApp">
<script src="bower_components/jquery/dist/jquery.js"></script>
<script src="bower_components/angular/angular.js"></script>

<script src="scripts/client.js"></script>
<script src="scripts/app.js"></script>
<script src="scripts/gapi-service.js"></script>


  </body>
</html>

controllers.js

'use strict';

app.controller('AppController', function($scope, $state, Camera, Gapi) {

  Gapi.load();

});

看我的回答如何避免设置定时器。 - willlma

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接