不格好エンジニア

wordpress.comから引っ越しました。

【あの有名サービスが】コードリーディングで学ぶAngularJS【丸裸に】

コードリーディング

AngularJS初心者です。
コードリーディングという形で、AngularJSで実装されたWebサービスソースコードを題材に使って、その動きや機能がどのように実装されているのかを、ソースコードのレベルで解析していこうと思います。

※時間のある時に、随時追記していきます。

題材「FMTube 」について

コードリーディングの題材とするのはこちら。
ゆーすけべーさんが、AngularJSの勉強中に作成したサービスだそうです。

FMTube!

アーティストの名前を入れると、楽曲リストを順番に再生してくれます。
素晴らしいです。勉強させて頂きます。

ソースコード

ソースコードですが、Webサービスとして公開しているので、当然、Webブラウザから確認することができます。
メインロジックは、こちらに記述されています。

http://yusukebe.github.io/FMTube/js/app.js

参考資料

正直、全然わからない。。。この辺りを参考にしながら進めていきます。

初心者向けAngularJS - その1 - albatrosary's blog

Service, Factory, Providerが何の事か分からない方はこちら。

AngularJs Service,Factory,Providerなどなど - senta.me/blog

エントリポイント

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

ここでは、このアプリケーションを初期化して、依存するモジュールを登録します。

view

次に、view部分をざっと眺めます。
#content, #listあたりに注目すれば処理内容を把握できそうです。

<html ng-app="fmtube" ng-controller="controller" class="ng-scope"><head><style type="text/css">@charset "UTF-8";[ng\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide{display:none !important;}ng\:form{display:block;}.ng-animate-start{border-spacing:1px 1px;-ms-zoom:1.0001;}.ng-animate-active{border-spacing:0px 0px;-ms-zoom:1;}</style>
    <meta charset="utf-8">
    <title ng-bind="title" class="ng-binding">Eis essen by GReeen - FMTube!</title>
    <link href="css/bootstrap.min.css" rel="stylesheet">
    <link href="css/typeahead.js-bootstrap.css" rel="stylesheet">
    <link href="css/app.css" rel="stylesheet">
    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
      <script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
    <![endif]-->
    <script id="www-widgetapi-script" src="https://s.ytimg.com/yts/jsbin/www-widgetapi-vflAiKzF-/www-widgetapi.js" async=""></script><script src="http://www.youtube.com/iframe_api"></script><script id="twitter-wjs" src="http://platform.twitter.com/widgets.js"></script><script async="" src="//www.google-analytics.com/analytics.js"></script><script src="js/jquery.js"></script>
    <script src="js/typeahead.min.js"></script>
    <script src="js/bootstrap.min.js"></script>
    <script src="js/angular.min.js"></script>
    <script src="js/angular-resource.min.js"></script>
    <script src="js/app.js"></script>
    <script>
      (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
      (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
      m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
      })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
      ga('create', 'UA-492497-67', 'yusukebe.github.io');
      ga('send', 'pageview');
    </script>
  </head>
  <body style="" data-twttr-rendered="true">
    <div class="container">
      <div id="header" class="clearfix">
        <h1><a href="http://yusukebe.github.io/FMTube/">FMTube!</a></h1>
      </div>
      <!-- /header -->
      <div class="row">
        <div id="form" class="col-md-4">
          <form role="form" ng-submit="submit(true)" class="ng-valid ng-dirty">
            <div class="form-group">
              <span class="twitter-typeahead ng-valid ng-dirty" style="position: relative; display: inline-block; direction: ltr;"><input class="tt-hint" type="text" autocomplete="off" spellcheck="off" disabled="" style="position: absolute; top: 0px; left: 0px; border-color: transparent; box-shadow: none; background: none 0% 0% / auto repeat scroll padding-box border-box rgb(255, 255, 255);"><input type="text" class="form-control typeahead tt-query" placeholder="Artist name" ng-model="artist" autocomplete="off" spellcheck="false" dir="auto" style="position: relative; vertical-align: top; background-color: transparent;"><span style="position: absolute; left: -9999px; visibility: hidden; white-space: nowrap; font-family: 'Alegreya Sans', sans-serif; font-size: 14.2857141494751px; font-style: normal; font-variant: normal; font-weight: 400; word-spacing: 0px; letter-spacing: 0px; text-indent: 0px; text-rendering: auto; text-transform: none;">GReeen</span><span class="tt-dropdown-menu" style="position: absolute; top: 100%; left: 0px; z-index: 100; display: none; right: auto;"><div class="tt-dataset-artist" style="display: block;"><span class="tt-suggestions" style="display: block;"><div class="tt-suggestion" style="white-space: nowrap; cursor: pointer;"><p style="white-space: normal;">Greeen Linez</p></div><div class="tt-suggestion" style="white-space: nowrap; cursor: pointer;"><p style="white-space: normal;">GReeen</p></div><div class="tt-suggestion" style="white-space: nowrap; cursor: pointer;"><p style="white-space: normal;">Greeen</p></div><div class="tt-suggestion" style="white-space: nowrap; cursor: pointer;"><p style="white-space: normal;">Dr.Greeen</p></div><div class="tt-suggestion" style="white-space: nowrap; cursor: pointer;"><p style="white-space: normal;">Al Greeen</p></div></span></div></span></span>
              <button type="submit" class="btn btn-default"><b>Play!</b></button>
            </div>
          </form>
        </div>
      </div>
      <div id="social-buttons">
        <span style="margin-right:4px;">
          <iframe class="hatena-bookmark-button-frame" title="このエントリーをはてなブックマークに追加" frameborder="0" scrolling="no" width="130" height="20" src="javascript:false" style="width: 111px; height: 20px;"></iframe><script type="text/javascript" src="http://b.st-hatena.com/js/bookmark_button.js" charset="utf-8" async="async"></script>
        </span>
        <iframe id="twitter-widget-0" scrolling="no" frameborder="0" allowtransparency="true" src="http://platform.twitter.com/widgets/tweet_button.d58098f8a7f0ff5a206e7f15442a6b30.en.html#_=1416154421769&amp;count=horizontal&amp;id=twitter-widget-0&amp;lang=en&amp;original_referer=http%3A%2F%2Fyusukebe.github.io%2FFMTube%2Findex.html&amp;size=m&amp;text=FMTube!&amp;url=http%3A%2F%2Fyusukebe.github.io%2FFMTube%2F" class="twitter-share-button twitter-tweet-button twitter-share-button twitter-count-horizontal" title="Twitter Tweet Button" data-twttr-rendered="true" style="width: 109px; height: 20px;"></iframe>
        <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
        <span style="margin-left:-20px;">
          <iframe src="//www.facebook.com/plugins/like.php?href=http%3A%2F%2Fyusukebe.github.io%2FFMTube%2F&amp;width=150&amp;layout=button_count&amp;action=like&amp;show_faces=true&amp;share=true&amp;height=21&amp;appId=640667512644377" scrolling="no" frameborder="0" style="border:none; overflow:hidden; height:21px; width:150px;" allowtransparency="true"></iframe>
        </span>
      </div>

      <hr>

      <div class="row">
        <div id="content">
          <iframe id="player" frameborder="0" allowfullscreen="1" title="YouTube video player" width="600" height="400" src="https://www.youtube.com/embed/5sveP1gSXGA?autoplay=1&amp;rel=0&amp;enablejsapi=1&amp;origin=http%3A%2F%2Fyusukebe.github.io"></iframe>
        </div>
        <div id="list">
          
          <!-- ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix list-active" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">Eis essen</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="http://userserve-ak.last.fm/serve/34s/97541019.jpg" class="pull-left" src="http://userserve-ak.last.fm/serve/34s/97541019.jpg">
              <b class="pull-left ng-binding">Gesundes Ego</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">Eis essen - JuliensBlogContest</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="http://userserve-ak.last.fm/serve/34s/92538915.jpg" class="pull-left" src="http://userserve-ak.last.fm/serve/34s/92538915.jpg">
              <b class="pull-left ng-binding">Ein gesundes Ego</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="http://userserve-ak.last.fm/serve/34s/92538915.jpg" class="pull-left" src="http://userserve-ak.last.fm/serve/34s/92538915.jpg">
              <b class="pull-left ng-binding">Nur das Beste</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="http://userserve-ak.last.fm/serve/34s/92538915.jpg" class="pull-left" src="http://userserve-ak.last.fm/serve/34s/92538915.jpg">
              <b class="pull-left ng-binding">Die Blüte meiner Selbst</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="http://userserve-ak.last.fm/serve/34s/92538915.jpg" class="pull-left" src="http://userserve-ak.last.fm/serve/34s/92538915.jpg">
              <b class="pull-left ng-binding">Blauer Planet</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">JottBeBe Qualifikation</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="http://userserve-ak.last.fm/serve/34s/92538915.jpg" class="pull-left" src="http://userserve-ak.last.fm/serve/34s/92538915.jpg">
              <b class="pull-left ng-binding">Hovercraft</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">Lächeln Exklusiv</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">Hippie 2.0</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">Scheiss doch einfach drauf (feat. Der Rote)</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">Hab Wörter für dich</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">JottBeBe Winin Battle</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">Vaubetee Quali</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">Pinselstrich für die Ohren</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">Abseits der Norm</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">Ich bin hier</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">Frauen</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">VauBeTee Qualifikation - Instrumental</b>
            </a>
          </div><!-- end ngRepeat: track in tracks -->
        </div>
      </div>
      
      <hr>
      <div id="footer">
        <address>
          © <a href="http://yusukebe.github.io/">yusukebe</a>,
          <b>FMTube</b> using Last.fm Web API, YouTube API, jQuery, AngularJS, Twitter Bootstrap and GitHub!
        </address>
      </div>
    </div>
</body></html>

run

またjs/app.jsに戻って、処理内容を確認します。

app.run(function(){
  var tag = document.createElement('script');
  tag.src = "http://www.youtube.com/iframe_api";
  var first_tag = document.getElementsByTagName('script')[0];
  first_tag.parentNode.insertBefore(tag, first_tag);
});

runって何ですか。。。?
StackOverflowさんによると、constantsやinstanceはrunブロックでしか注入できないらしい。

[参考]

breeze - AngularJS app.run() documentation? - Stack Overflow

Factory, Service, Providerについて

Service,Value,Factory,Provider ってなんぞ?そこについて前提知識がないと、以降のコードを理解するのは難しそうです。

先ず、サービスとは…
Angularサービスはシングルトンオブジェクト、またはWebアプリケーション共通の特定のタスクを実行する関数です。 Angularは、サーバにリクエストを送るブラウザのXMLHttpRequestオブジェクトにアクセスする$httpサービスのような、 いくつかの組み込みサービスを持ちます。 他のコアなAngular変数と識別子と同様に、組み込みのサービスは常に$から始まります。(前述した$httpのように) また、独自のカスタムサービスを作ることも可能です。
とのこと。 (http://js.studio-kingdom.com/angularjs/guide/understanding_services)
このサービスをDIに登録する方法が複数あり、大分とまどいました。

[参考]
AngularJs Service,Factory,Providerなどなど - senta.me/blog

DI

この際、ぶっちゃけると「DIとか何それおいしいの?」な人もたくさんいらっしゃる事でしょう。
僕もその1人です。こちらを読むと何となくわかった気になれます。


要するに DI って何なのという話 - 猫型の蓄音機は 1 分間に 45 回にゃあと鳴く


明日から、ドヤ顔で新人君に説明できますね。どや!

今日はここまで。また追記します。。。