Angular.jsとFacebook Loginは友達になりますか?

こんにちは、Habrの読者の皆さん!



AngularとNodeという、今までで最も面白いと思うものに最初の投稿を捧げたいと思います。



Angularでの作業(約7か月)の間、私自身のアイデアがいくつか現れました。それを共有したいと思っています。 もちろん、これはFacebook JS SDKセクションで説明されているFacebookログイン自体ではなく、「Hello World with Angular.js」ではありませんが、非常に簡単です。



この記事を書く動機は、興味深い方向でいくつかの経験を共有したいという願望です。



[小さな免責事項]


おそらく、このテキストの下で起こることはすべて、ナンセンスとコードの破片が爆発的に混ざったものですが、客観的な評価と、より適切で興味深いソリューションを選択する経験豊富な開発者の助けを心から願っています。



バックエンド



私の選択は偶然ではなくNodeでした。 まあ、私はJavaScriptが大好きです。 さらに、それは私には思えるので、非常に便利です。 主なものは、Webアプリケーションをインターネットに迅速かつ自由に展開できることです。 「理由」をリストしても意味がありません。 サーバー側のフレームワークとして、Expressを選択しました。 その上にハウツー記事がたくさんあり、非常にわかりやすく、簡単なルーティングです。 今のところすべてです。



さらに、重要なポイントをより詳細に分析しようとします。



サーバーがブラウザからのリクエストにどのように応答するかを見てください。



app.js
var express = require('express') , expressLayouts = require('express-ejs-layouts') , less = require('less-middleware') , routes = require('./routes') , config = require('./settings') , http = require('http') , path = require('path') , app = express() ; // all environments app.set('view engine', 'ejs'); app.set('views', __dirname + '/views'); app.set('layout', 'layout'); app.locals({ appName: config.APP_NAME, appId: config.APP_ID, appUrl: config.APP_URL, scope: config.SCOPE, random: Math.random() }); app.use(expressLayouts); app.use(express.favicon()); app.use(express.logger('dev')); app.use(express.bodyParser()); app.use(express.methodOverride()); app.use(express.cookieParser()); app.use(express.session({ secret: config.SECRET, maxAge: new Date(Date.now() + 3600000) })); app.use(require('less-middleware')({ force: true, dest: path.join(__dirname, 'public', 'css'), src: path.join(__dirname, 'less'), prefix: '/static/css/' })); app.use('/static', express.static(path.join(__dirname, 'public'))); app.use('/static', express.static(path.join(__dirname, 'bower_components'))); app.use(app.router); // landing page app.get('/' , routes.index); app.get('/partials/:name' , routes.partials); app.get('/partials/:folder/:name' , routes.partials); app.all(/^(\/((?!static)\w.+))+$/ , routes.index); http.createServer(app).listen(config.PORT, function(){ console.log('Express server listening on port ' + config.PORT); });
      
      









ルーティングについてのみ説明しましょう 残りは純粋に技術的な設定であり、そのほとんどは「Get Started with Express」などのチュートリアルで説明されています。



単一ページのアプリケーションを作成する準備をしているので、Angularが現在のページを処理できるように、常に<ng-view> </ ng-view>タグを持つ同じページをリクエストに提供するようにサーバーを構成する必要がありますクライアント側のルーティングに応じたURLで、対応するパーシャルをロードします(詳細は後ほど..)



だから-静的ファイル(js、css、画像)と通常のページ(サーバーに記述されていない場合)の要求には違いがあることをExpressにどのように説明できますか。

-彼は、存在しないページパスについて私たちを非難せず、Angularの作業に干渉しませんでした。

-それでも、存在しない静的ファイルをリクエストした場合、彼はあなたについて考えるすべてをあなたに表明しました。



このようなもの:

 //       app.use('/static', express.static(path.join(__dirname, 'public'))); app.use('/static', express.static(path.join(__dirname, 'bower_components'))); app.use(app.router); //  -        . // landing page app.get('/' , routes.index); // GET      index, .. homepage   <ng-view> app.get('/partials/:name' , routes.partials); // ,      Partials'  Angular'a app.get('/partials/:folder/:name' , routes.partials); //  ,      app.all(/^(\/((?!static)\w.+))+$/ , routes.index); //    ,  ",       /static/      Angular'a".
      
      







論理的な質問が発生します-要求されたページ(/ ololoなど)が実際にない場合、ユーザーに404エラーをどのように伝えるのですか?



フロントエンド



ページにアクセスしたときに、Angularがどこを間違えたかを理解する方法を説明する前に、アプリケーションのファイル構造(およびbowerのサードパーティライブラリを除く、サーバー上のすべての統計情報)を見てみましょう。



非表示のテキスト
├──公開

│├──css

││└──style.css

│├──画像

││└──fb_button.png

│├──js

││├──application.js-アプリケーションの初期化。 詳細は後で...

││├──controllers.js-コントローラのグローバルモジュール

││├──directives.js-ディレクティブのグローバルモジュール

││├──filters.js-フィルター用

││├──services.js-サービス用

││└──モジュール-アプリケーションエンティティはこのフォルダーに保存されます

││└──friends.js-ここにそれらの1つがあります-このエンティティのみに関係する/ friendsページとコントローラー、ディレクティブ、およびフィルターについては、ここで説明します



一般的に、モジュールフォルダーは別の方法で呼び出すことができます-たとえば、ページ...

つまり 一言で言えば-モジュールには2つの領域があります。

-グローバル-プロジェクト全体で使用できるディレクティブ、フィルター、サービスについて説明します

-essential-エンティティのルーティング、およびこのエンティティにのみ一意に関連し、このモジュールの外部で使用できないディレクティブ、フィルター、サービスについても説明します。



もちろん、それはすべて理論上です...誰もが心の望みどおりにしていますが、このアプローチは、テンプレートの途中で無限の小さな指示と突然のコントローラーから麺にdrれないように助けてくれました。 たとえば、おそらく別の記事が必要です。 トピックに戻りましょう。



layout.ejs
 <html ng-app="angularfb"> <head> <title>Facebook app</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="/static/css/style.css"> <link rel="stylesheet" href="/static/bootstrap/dist/css/bootstrap.css"> <link rel="stylesheet" href="/static/bootstrap/dist/css/bootstrap-theme.css"> <link rel="stylesheet" href="/static/font-awesome/css/font-awesome.min.css"> <!--[if IE 7]> <link rel="stylesheet" href="/static/font-awesome/css/font-awesome-ie7.min.css"> <![endif]--> <link href='http://fonts.googleapis.com/css?family=Domine' rel='stylesheet' type='text/css'> <style type="text/css"> [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { display: none !important; } </style> <script type="text/javascript"> //           ,  Facebook API FB_APP_NAME = '<%= appName %>'; FB_APP_ID = '<%= appId %>' ; FB_APP_URL = '<%= appUrl %>'; FB_SCOPE = '<%= scope %>'; </script> </head> <body ng-class="{'page-loader': !$root.user}"> <!--     FB user'a,      --> <!-- body   <ng-view>  Angular     URL . --> <div ng-if="$root.user"> <%- include navigation %> <%- body %> <%- include footer %> </div> <!--       FB user'a --> <div ng-if="!$root.user" ng-cloak> <h2 class="text-center" ng-if="!auth.status"> <i class="icon-spin icon-spinner"></i> Loading... </h2> <!--   login with Facebook      auth.authResponseChange --> <a ng-if="auth.status && auth.status != 'connected'" class="fb-btn" ng-click="login()"></a> </div> </body> <script src="//connect.facebook.net/en_US/all.js"></script> <script src="/static/jquery/jquery.min.js"></script> <script src="/static/bootstrap/dist/js/bootstrap.min.js"></script> <script src="/static/underscore/underscore-min.js"></script> <script src="/static/angular/angular.js"></script> <script src="/static/angular-resource/angular-resource.min.js"></script> <script src="/static/angular-route/angular-route.min.js"></script> <script src="/static/js/services.js?<%= random %>"></script> <script src="/static/js/filters.js?<%= random %>"></script> <script src="/static/js/controllers.js?<%= random %>"></script> <script src="/static/js/directives.js?<%= random %>"></script> <script src="/static/js/modules/friends.js?<%= random %>"></script> <script src="/static/js/application.js?<%= random %>"></script> </html>
      
      







application.js
 (function() { 'use strict'; angular.module('angularfb', [ 'ngRoute', 'ngResource', 'angularfb.filters', 'angularfb.controllers', 'angularfb.services', 'angularfb.directives', 'angularfb.friends' ]) .config([ '$routeProvider', '$locationProvider', function($routeProvider, $locationProvider){ $locationProvider.html5Mode(true); $routeProvider .when('/', { templateUrl: '/partials/homepage', controller: 'HomePageCtrl' }) .when('/logout', { resolve: [ '$rootScope', 'API', '$location', function($rootScope, API, $location){ API.logout(function(){ console.log('Logout... redirecting...'); $location.path('/') $rootScope.$apply(); }); }] }) .otherwise(); } ]) .run(['$rootScope', '$location', 'API', function($rootScope, $location, API){ FB.init({ appId : FB_APP_ID, channelUrl : FB_APP_URL, status : true, xfbml : true, oauth : true }); console.log('RUN!'); //    Login With Facebook $rootScope.login = function(){ API.login(function(){ console.log("Logged in. Redirecting...") $location.path('/'); }, function(){ console.log("not logged in... error...") }); } //     API.getLoginStatus( function( response ){ console.info("Authorized:", response) }, function( response ){ console.error("Not authorized:", response) } ) FB.Event.subscribe('auth.authResponseChange', function(response) { console.log('got AuthResponseChange:', response); if (response.status === 'connected') { API.me().then( function(resp){ console.log('got user Info', resp); }, function(error){ console.log("got user Info error", error); } ); } else { $rootScope.user = null; //      - ng-if  (. layout.ejs) $location.path('/'); } $rootScope.auth = response; $rootScope.$$phase || $rootScope.$apply(); }); $rootScope.$on('$routeChangeStart', function(event, next, current){ if ( !next.$$route ) next.templateUrl = '/partials/error'; }) }]) .controller('HomePageCtrl', [ '$scope', function($scope){ /* .... */ }]) }());
      
      







services.js
 (function(){ 'use strict'; angular.module('angularfb.services', []) .factory("API", [ '$rootScope', '$q', '$location', '$exceptionHandler', function($rootScope, $q, $location, $exceptionHandler){ return { me: function(){ var def = $q.defer(); FB.api('/me', function(response){ def.resolve($rootScope.user = response); //       $root,     layout.ejs "  " }) return def.promise; }, getLoginStatus: function(successCallback, errorCallback){ var self = this; FB.getLoginStatus(function(response) { self._processAuthResponse( response, successCallback, errorCallback ) }); }, login: function(successCallback, errorCallback){ var self = this; FB.login(function(response){ self._processAuthResponse( response, successCallback, errorCallback ); }, { scope: FB_SCOPE } ) }, logout: function( logoutCallback ){ return FB.logout(function( response ){ if (_.isFunction( logoutCallback )) logoutCallback.call(this, arguments) else { $location.path('/') $rootScope.user = null; } $rootScope.auth = response; }) }, _processAuthResponse: function( response, successCb, errorCb ) { var self = this; if (response.authResponse) { if (_.isFunction(successCb)) successCb.call(this, response) else if(_.isUndefined(successCb)){ $location.path('/') } else throw new Error("Success callback should be a function") } else { if (_.isFunction(errorCb)) errorCb.call(this, response) else if(_.isUndefined(errorCb)){} else throw new Error("Error callback should be a function") } $rootScope.auth = response; self._applyScope(); }, _applyScope: function( cb ) { if (!$rootScope.$$phase) { try { $rootScope.$eval( cb ); } catch (e) { $exceptionHandler(e); } finally { try { $rootScope.$apply(); } catch (e) { $exceptionHandler(e); throw e; } } } else { $rootScope.$eval( cb ); } } } } ]) })();
      
      









successCallback、errorCallback、および_processAuthResponseのように、APIサービスでそのようなバイクをscるのは、それがはるかに簡単にできるからだけではないでしょうか。 私は自転車に乗りたかった、少し積み上げた...そのため、何とか何らかの形ですべてが認可自体の仕事で非常によく発達しているようだ。



Facebookログイン



しかし、Facebookはどうですか? タイトルで何度か言及しましたが、テキストでは何度か説明しましたが、説明はありません...謝罪します。



それでは、アプリケーションステータスを常にログイン状態に保つにはどうすればよいでしょうか

コードを部分的に分析しましょう:



 //   , application.js,   .run(), //   Login Status API.getLoginStatus() //    event "auth.authResponseChange",  //         . // response    {authResponse: Object, status: String } // ,   ,  response.status === 'connected' //  API    $rootScope.user  '/me' , //      response  $rootScope.auth //           //  ,   . FB.Event.subscribe('auth.authResponseChange', function(response) { if (response.status === 'connected'){ API.me() } else { $rootScope.user = null; $location.path('/'); } $rootScope.auth = response; $rootScope.$$phase || $rootScope.$apply(); });
      
      







$ root.userがない限り、アプリケーションはメインコンテンツを非表示にし、代わりに「Loading ..」を表示します。 そして、$ root.authがすでに到着しており、ステータスが== 'connected'の場合、「Facebookに接続」ボタンが表示されます。 ユーザーがボタンをクリックしてログインすると、ユーザーが取得され、layout.ejsのテンプレートがすぐに応答し、ヘッダー、フッター、および<ng-view>を含むメインコンテンツをそれぞれ表示し、ログインボタンを非表示にしてロードします。



そうそう..、Not Foundの取り扱いについて:


application.jsで$ routeProviderを記述するときに、.otherwise()メソッドにリダイレクトのパラメーターがないことに気づいた人がいるかもしれません。 すべての正当な理由。 エラーはタイプミスまたはユーザーがすぐに修正できる類似のものである可能性があり、必要に応じてページが表示されるので、どこかにリダイレクトする必要はありません。 、またはページをロードした場合、そのようなパスにルート(.where())が記述されているかどうかを確認しますか? そうでない場合は、ロードして、テンプレートを置き換えます。



 $rootScope.$on('$routeChangeStart', function(event, next, current){ if ( !next.$$route ) next.templateUrl = '/partials/error'; })
      
      







したがって、アドレスバーに間違ったリンクが残っています。 このパスが存在しないという情報。 ユーザーが見たところ、URLはすでに修正が容易であり、アプリケーションを引き続き使用できます。



一般的に



-ExpressがどのようにAngularを呼吸しやすくし、静的ファイルを除くすべてを個人的な推論のために提供するかを調べました。

-Anguar.jsアプリケーションファイルの整理に関するトピックに触れる

-私たちは、Angular'yが404エラー処理だけでなく、承認後、Facebookを介してクライアントで再度ユーザーと通信できることを確信しました(願っています)。



そして、あなたはすべてがここに住んでいるのを感じることができます:

- [http://angular-fb.herokuapp.com/]



もし興味があれば、このプロジェクトでgithubへのリンクを残すことができます。

読んでくれたみんなに感謝します。



UPD:Facebookログインに関する追加。 ありがとうTulov_Alex



All Articles