タグ「AngularJS」が付けられているもの

ブラウザ側に情報を取っておく手段としてlocalStorageがあります。 昔はcookieを使っていたことが多く、今でもまだよく使われているのですが、 モダンブラウザ対応ならlocalStorageの方が適しているのではないかと思います。

以前、JavaScript nativeでlocalStorageを使うコードは書いたことがあって、 別に不満はなかったのですが、AngularでlocalStorageを簡単に扱えるライブラリがあると 思って調べてみました。

このあたりがメジャーそうです。順当に行くとStar数が一番多いangular-local-storageを選ぶのですが、 じっくりREADMEを読んでみます。APIは自明で分かりやすいです。bindはangularならではのAPIで便利そうです。 localStorageが使えない場合はcookieにfallbackするそうです。APIは抽象的に作られているということですね。

さて、2つ目のangularLocalStorageはどうでしょう。名前が似ていますが別物です。 bindもあるしAPIもシンプルで分かりやすいです。cookieへのfallbackもあるそうです。 angular-local-storageと比較すると若い分、Star数が少ないだけでしょうか。issueも処理されて アクティブに見えます。でも違いがない分、ドキュメントが充実しているangular-local-storageを選びたくなります。

最後の、ngStorageは、in the Angular Wayということで少し変わった感じです。

No Cookie Fallback - With Web Storage being readily available in all the browsers AngularJS officially supports, such fallback is largely redundant.

だそうです。確かにangularを使うようなブラウザでは、localStorageをサポートしていないケースは無いですね。 これは思い切りがよいです。気に入りました。

$scope.$storage = $localStorage;

使い方もこれだけです。APIとかそういうレベルではない感じ。とてもいいです。

And use it like you-already-know

だそうです。気に入りました。 これはつまり、localStorageを加えた3-way data bindingということですね。 これは応用が広がりそうです。時間があったらコードも読んでみたいです。

というわけで、おすすめはngStorageでした。

ng-conf 2015の動画を見ていたら、angular meteor integrationの動画がありました。

https://www.youtube.com/watch?v=uFmf-DeCdEE

Meteorを試した時に、angularを利用する可能性は考えましたし、 そのような記事もどこかで読んだ気がします。 しかし、Meteorが元から持っているテンプレート機能と重複しますし、 Meteorの恩恵をあまり受けられないかと思っていました。

ところが、Angular-Meteorはそれなりにうまく作られているようです。 興味がある方は、チュートリアル を見てみるとよいでしょう。 話が脱線しますが、このチュートリアルとてもやりやすいです。 Meteor本体のチュートリアルもそうですが、チュートリアルなのにかなり本格的な アプリを作っています。 Famo.us universityを見たときも思いましたが、チュートリアル周りの ベストプラクティスがたまってきていますね。 自分でもうまく活用できればいいと思います。

Angular-Meteorのチュートリアルをやってみて一番気に入ったのは、 3-way data bindingです。 これができるということは、AngularFireの代替になるのではないでしょうか。 ちょっとなにか作ってみたくなりました。 逆に一番気になったのは、getReactively周りの処理です。 特に手動でunsubscribeしなければいけないのは面倒です。

Maybe in the future we will add an automatic way to open and close subscriptions in scope's load and destroy events.

とあるので、解決されることを期待します。

話はまた変わりますが、Firebaseのtalkもng-conf 2015にありました。

https://www.youtube.com/watch?v=4nD5fjpIesk

こちらは、本家本流なのですごいですね。v1.0がリリースされたそうです。 彼らが3-way data bindingの名付け親です、たぶん。

Meteorはとても魅力的なのですが、自分が本当に気に入るか分からずちょっと悩んでいます。 その理由は、Fiberを使っていることと、jQueryを使っていること。 callbackは結構好きなのでFiberに魅力をあまり感じません。 一応、Fiberの流儀を使わず、callbackでも書けるので プロジェクトのコーディング規約で気をつければいいのですが。 jQueryの方は、非依存にする話もあるようですが、コアが依存しているという記述もあり、 完全に非依存になるには時間がかかりそうです。

https://groups.google.com/forum/?fromgroups=#!topic/meteor-talk/21y9NbM9v90

別に、jQuery依存だからといって困ることはないのですが、単に気分の問題です。

いずれにしても、Angualr-Meteorにはちょっと興味を持ちました。

Angularのdependency injectionはminifyに弱いです。 angular.jsのチュートリアルにも書かれています。

https://code.angularjs.org/1.3.8/docs/tutorial/step_05

A Note on Minificationのセクションに、 annotationをつけると書いてあります。

しばらく悩まずこの書式で書いていましたが、これはとても気をつかいます。スペルミスや並び替えをするときなど。なによりも、同じ文字を2回タイプするのは効率が悪いです。

ngminというのは聞いたことがあったのですが、100%変換できるわけではないという噂を聞いて使っていませんでした。今ではng-annotateというツールがあります。

https://github.com/olov/ng-annotate

ngminとの違いも説明されています。うまく行かない場合のためにコメントで明示的に指示できるというのが安心感があります。

これで快適になりました。やはり、自動化できることを人がやるのは無駄ですね。

ng-annotateはminifyをしてくれないので、minifyツールと合わせて使うことになります。今回は、

https://github.com/mishoo/UglifyJS2

を使うことにしました。下記にpackage.jsonを載せます。

{
  "scripts": {
    "minify:main": "ng-annotate -a public/main.js | uglifyjs -c -m > public/main.min.js"
  },
  "devDependencies": {
    "ng-annotate": "*",
    "uglify-js": "*"
  }
}

これで、

npm run minify:main

で動きます。 grunt等を使いたい人は、対応しているプラグインを使えばできるでしょう。

AngularでBootstrapを使うライブラリとして、以前の記事では、UI Bootstrapを紹介しました。

最近、AngularStrapも使ってみたので感想を書いておきます。

AngularStrapは初期の頃に名前を聞いたことがあるような気がします。確か、bootstrap v2の時代に。今では、AngularStrapもv2になってjQuery依存がなくなりました。いい時代の流れです。

さて、AngularStrapはUI Bootstrapと比べると根本的な設計思想は似ています。どちらも、bootstrapのcssだけ再利用して、javascript部分は作り直し、jQueryには依存しない、Look&Feelのカスタマイズ性を考慮している、と言ったところです。

しかし、使ってみるとだいぶ考え方に違いがあるような気がします。 UI Bootstrapはできるだけシンプルにbootstrapのcssを使えるようにしている一方、AngularStrapはbootstrapのcssをベースにしてよりリッチな機能を提供しようとしているようです。最大の違いは、アニメーションのサポートでしょうか。

簡単な例として、Alertの違いを見てみます。最初は、UI Bootstrap。

<alert type="{{alert.type}}" close="closeAlert(alert)">{{alert.content}}</alert>

これだけです。ng-repeatを使う例がドキュメントに載っていますが、直感的です。ng-showとも組み合わせられます。

次に、AngularStrapです。いろいろあるようですが、attributeを使う例。

<button class="btn" title="{{alert.title}}" data-content="{{alert.content}}" data-type="{{alert.type}}" bs-alert>Show alert</button>

buttonではなくdivで直接表示しようと思いましたが、できませんでした。ドキュメントではできそうなので、何か間違えているのかもしれません。 ng-showは使わず、オプションか属性でshowフラグを指定します。このあたり思想が異なる感じです。durationとか指定できるのは便利です。 alertは単純な機能なので、そこまで違いませんが、他の機能はもっと違いがありそうです。

個人的には、ui-bootstrapの方が使いやすいです。しかし、AngularStrapでアニメーションが簡単に使えるのも魅力的です。使い分けることにします。

人に勧めるのはどちらがよいか迷います。勉強が目的ならui-bootstrapでしょうか、リッチな機能が欲しいならAngularStrapでしょうか。

GitHub便利です。その一つが、Compare ViewのDiffです。Pull Requestを確認するときもよく見ます。コマンドラインでgit diffと普通にやるより見やすいです。

さて、背景は省略しますが、HTMLでdiffを表示したいと思いました。AngularJSでの利用を想定しているので、JavaScriptのライブラリが欲しいです。

ところが探してもあまり見つかりません。よくまとまっているのはこちら。

http://stackoverflow.com/questions/3053587/javascript-based-diff-utility

ちょっと古い投稿ですが。一番人気なのは、これです。

https://github.com/cemerick/jsdifflib

オンラインデモはこちら。

http://cemerick.github.io/jsdifflib/demo.html

だいぶ求めているもののイメージに近いです。 コードも枯れているらしい。 HTMLが出力されるので、Angularで使うならdirectiveを書かなければいけないですが、それは仕方ないですね。

これで行こうかと思ったところでしたが、なにか物足りません。 なんというか、見た目がワクワクしないのです。 改めてGitHubのDiffと比較してみると、全体的に色が濃すぎる。 あと、単語の差分がハイライトされない。色については自分でcssを書いて調整すればよいでしょうが、単語のハイライトはそれでは済みません。

というわけで、もう一度検索しました。見つけたのがこちらです。

https://github.com/rtfpessoa/diff2html

オンラインデモはこちら。

http://rtfpessoa.github.io/diff2html/

おお、おしゃれ。これですよこれ。なんとStar数は3でした。まったく注目されていません。コードも枯れていないでしょう。まだ不具合もあります。でも、魅力的です。これを使いましょう。

ところが、使うには一つ問題がありました。diff2htmlはdiffの出力を入力としているのです。例えば、

diff --git a/foo.txt b/foo.txt
---
+++
@@ -1,4 +1,5 @@
+zero
 one
 two
-three
+tree
 four

このようなものです。つまり、次はこれを出力するライブラリを探しました。いくつかありそうでしたが、比較的すんなり結合できたのはこちらです。

https://github.com/qiao/difflib.js

入力の行が配列になることと、出力の一行目のヘッダが付かないことなど不足点はありまして、それらは自力で対応しました。

最後にこれらを全部つなげて、angular.jsのdirectiveを書いてできあがりです。下記のような感じで使えるようになりました。

<my-diff path="foo.txt" old-content="one\ntwo\nthree\nfour\n" new-content="zero\none\ntwo\n\tree\nfour\n">

便利です。ちょっと気になることと言えば、依存javascriptが3ファイルもあって、グローバルに関数を定義するようなものであることです。なにかうまく隠蔽する方法があるのでしょうか。

AngularJSの流れにちゃんとついていくためng-europeをyoutubeで見ようと思い始めて一ヶ月が過ぎてしまいました。 まだ全部は見ていませんが、今日は一本見ました。興味深かったので記事にします。

https://www.youtube.com/watch?v=tfVA1syv-3o

AngularUIのui-bootstrapのお話です。 単にラップするわけではなくAngular流に作られているのと、カスタマイズ性を重視しているあたりは参考になります。

ui-bootstrapはBootstrap 3のcssのみを利用していてjavascriptの部分は全部書き直しているようです。つまり、jQuery依存もなしです。

ちょっと昔話を。当初、Bootstrap 2を使っていたのですが、クラス名などがどうも好きになれず、その後inkというcssフレームワークを見つけて 、しばらく使っていました。

その時から、inkのcssのみを使ってコーディングしていました。ロジックが必要な場合はAngular側で自前で作ってました。 例えば、カレンダーも当初はAngularUIのui-calendarを使っていましたが、 jQuery依存に抵抗があったため、カレンダーも単純な機能ならinkで自前で実装していました。

その後、Bootstrap 3が登場し、クラス名がinkのようにシンプルになったので、少し気に入ってBootstrap 3を使い始めました。でも、cssだけで、jsは使いません。例えば、alertもロジックはangularで自前で書いていました。と言っても、あまりコーディング量は多くなく、特に困っていません。

さて、話を戻しますと、ui-bootstrapは上記でやっていたようなことをちゃんとやっているような印象です。cssだけを使って、ロジックはangular用にdirectiveを設計してあるようです。 alertを見る限りでは単純なので自前で用意しても変わりませんが、他の複雑な機能はui-bootstrapのdirectiveを使うとだいぶ楽できそうです。

というわけで、jQuery非依存のui-bootstrapはおすすめです。

夏ごろに「暑さをみんなでふっとばせ!」というアプリを作りました。

1bitで使い捨てで匿名のコミュニケーションツールを作ってみた

に経緯が書いてあります。

当時は、Famo.usやF/Aのライブラリに手を入れないといけなかったのですが、その後バージョンアップして不具合が解消されました。 新しいバージョンでは読み込み速度なども改善しています。

せっかくなので、GitHubにソースをアップロードしました。 Famo.us/Angularとsocket.ioの組み合わせでこんなことができるというサンプルによいかと思います。

ついでに、「暑」を「寒」に変更する機能もつけました。タイトルはそのままですが。

GitHubのページはこちらです。

https://github.com/dai-shi/atsufut

実際に動作しているアプリのページはこちらです。

http://atsufut.axlight.com/#?color=blue&label=%E5%AF%92

AngularJSの1.3.0がリリースされました。

これまで、1.2系を使っていたのですが、これを機に1.3も試してみようと思いました。

1.0から1.2に移行したときは、はまったのですが、今回はなにも不都合がありませんでした。

ところが、e2eテストが動かなくなってしまいました。具体的には、下記のようなテストがエラーになりました。

expect(element(by.repeater('item in list').row(0).column('{{item.name}}')).getText()).toEqual('foo');

エラー文は、

NoSuchElementError: No element found using locator: ...

です。

https://github.com/angular/protractor/blob/master/docs/api.md

のドキュメントを見ても問題なさそうですし、StackOverflowなどでも話題になっているのは見つからず、途方にくれていました。

が、試しに下記のように変更したら動きました。

expect(element(by.repeater('item in list').row(0).column('item.name')).getText()).toEqual('foo');

これって正しい解決法なのでしょうか。

過去にKarma(Testacular)を使ったことはありますが、今回やっと時代に追いついてProtractorを使ってみました。特にPhantomJSを使ってheadlessでE2Eテストするときに気づいたことをまとめます。

Protractorは、 Seleniumの WebDriverJSのラッパーで、AngularJSのE2Eテストのためのフレームワークです。

https://docs.angularjs.org/guide/e2e-testing

がエントリーポイントになるでしょう。 しかし、これだけ読んでも最終的な実行方法は分かりづらいです。 今回の目的は、コマンド一つ、例えば、

$ npm run e2e-test

でE2Eテストを実行してするための環境セットを作ることです。また、headlessで動かしたかったので、典型的に使われるChromeではなく、PhantomJSを前提にしました。サーバはExpress/Node.jsの想定ですが、それ以外の場合にも応用できるかと思います。

初めによくある環境設定です。

$ npm install protractor
$ npm install phantomjs
$ ./node_modules/.bin/webdriver-manager update

この例ではローカルディレクトリにモジュールをインストールしていますが。グローバルにインストールしたい人はそのようにどうぞ。

まず初めにはまったことはwebdriverが起動しないことでした。

$ ./node_modules/.bin/webdriver-manager start
INFO - Launching a standalone server
WARN - null
java.net.SocketException: No such device
    at java.net.NetworkInterface.isLoopback0(Native Method)

このようなエラーがでました。ワーニングかと思って無視していたらいけませんでした。どうやら、IPv6のアドレスのみを使っていてうまく行かないようだったので、Javaのオプションを追加して次のようにするとうまく行くことが分かりました。

$ env _JAVA_OPTIONS=-Djava.net.preferIPv4Stack=true ./node_modules/.bin/webdriver-manager start

さて、Protractorのチュートリアルには、webdriverをコマンドラインで手動で実行するように書いてありますが、個人的にはこれは面倒に感じます。時間はかかりますが、毎回自動で起動して欲しいです。これは簡単で、protractor.conf.jsにseleniumAddressを書かなければよいだけでした。下記に、protractor.confを載せます。

exports.config = {
  specs: [
    '*-spec.js'
  ],
  capabilities: {
    'browserName': 'phantomjs',
    'phantomjs.binary.path': './node_modules/.bin/phantomjs'
  },
  baseUrl: 'http://localhost:' + (process.env.PORT || 3000) + '/'
};

これだけで大丈夫でした。baseUrlは、specにURLをフルパスで書かなくてもよいようにするためで、expressが下記のように起動されている想定です。

app.listen(process.env.PORT || 3000);

specの細かい内容は今回は書きませんが、もっとも単純なものは例えば次のようになります。

describe('tutorial', function() {
  it('should have a title', function() {
    browser.get('index.html');
    expect(browser.getTitle()).toEqual('The Title');
  });
});

ところで、Protractorはテストのフレームワークであるもののアプリの起動には関与せず、アプリは自分で起動しなければいけません。また、テスト終了後はアプリも終了したいです。今回はお手軽にシェルスクリプトを書きました。

#!/bin/sh

export _JAVA_OPTIONS=-Djava.net.preferIPv4Stack=true
export PORT=12345
node ./app.js &
PID=$!
./node_modules/.bin/protractor ./e2e-test/protractor.conf.js
kill $PID

このスクリプトをpackage.jsonのscriptsに書いておけば、当初の目的であったnpmで一発でE2Eテストを走らせることができました。

Happy e2e testing!

自分が興味あるものの一つにコミュニケーションツールがあります。 と言っても普通のコミュニケーションではないです。 例えば、見知らぬ人と一時的なつながりを感じられるようなそんなツールがいいと思います。言語も使わず、世界のどこかの誰かと。

FacebookのPokeはよかったです。1bitで行うコミュニケーションとでも呼びましょうか。日本では「あいさつ」となり、意味が分からなくなりました。 ちなみに、Pokeは一度機能が隠れてしまったのですが、また復活しました。 最近は、Yoが流行っているらしいです。これも1bitコミュニケーションだと思います。どこまで流行るのか、気になるところです。

流行っているついでに、Snapchatについても気になるところです。 実は使ったことありませんが、これは使い捨てコミュニケーションだと思っています。 使い捨てコミュニケーションができたら面白いだろうなぁとは思っていましたが、 まさかそんなものが流行るとは想像していませんでした。 写真だからうまくいったのでしょうか。

あとは、匿名コミュニケーションにも興味あります。 最近は実名指向で、あまり匿名を重視したコミュニケーションツールは登場していないのでしょうか。

と言うわけで、前置きが長くなりましたが、1bitで使い捨てで匿名のコミュニケーションツールを作ってみようと思いました。 1bitで伝えたいことはなんだろう、と考えたその日が暑い日だったので、「暑い」にしました。 「暑い」を伝え合うだけでは、どんどん暑苦しくなるだけですので、 ストレス発散をできるような「吹き飛ばし」機能をつけることにしました。

atsufut_title.png

これがタイトル画面です。

atsufut_main.png

これがメイン画面です。これだけです。 画面の上半分をクリック(タップ)すると、丸が落ちてきます。 何度も押すと、丸がたまってきます。 次に画面の下半分をクリックすると、丸を吹き飛ばします。 それだけです。

それでは、やってみましょう。

http://atsufut.axlight.com/

このURLをスマホで開きましょう。PCでも大丈夫です。モダンブラウザ必須です。

一人でやると、すぐに飽きます。 複数人で同時にやるとまあまあ面白いとは思います。 しかし、たまたま同時に開いている人がいることは稀です。 ここをどうするかが悩みどころです。

今回、開発に利用したライブラリは、 node.js, socket.io, angular.js, famo.us あたりです。

angular.jsの話です。突然ですが、ng-clickというのはクリックが完了したときに実行され、ng-mousedownというのは押しただけで実行されます。しかし、ng-mousedownはタッチパネルでは発火しません。mousedownイベントはマウスだけであり、タブレットやスマートフォンのブラウザではtouchstartイベントがあります。なので、てっきりng-touchstartというのが用意されているものと思っていました。

が、それは勘違いでした。少なくとも現時点では。 ng-clickより細かい制御をしたかったので、ng-touchstartはどうしても欲しいです。

ところで、angular.jsのdirectiveはとても協力ですが、アプリケーション実装するときにdirective開発するのは無駄だと思っています。標準で用意されたdirectiveとcontrollerで済むことが多いです。directiveは再利用性が高いものを開発するときに使うものでしょう。アプリではなくライブラリ開発するときですね。

ところが、アプリ開発しているときにどうしてもng-touchstartが欲しくなってしまったので、directive書いてみました。今まで食わず嫌いでしたが、簡単なdirectiveなら書いてみるとすっきりすることもあるな、とちょっと考え直しました。

というわけで、開発したdirectiveを貼り付けておきます。

angular.module('MyModule').directive('myTouchstart', ['$parse', '$swipe',
  function($parse, $swipe) {
    return {
      restrict: 'A',
      link: function(scope, element, attrs) {
        var handler = $parse(attrs.myTouchstart);
        $swipe.bind(element, {
          start: function() {
            scope.$apply(function() {
              handler(scope);
            });
          }
        });
      }
    };
  }
]);

これだと、touchstartとmousedown両方に反応するのですが、ちゃんとやるならそのあたりも制御した方がいいですね。あと、touchstartだけでなく、touchmoveとtouchendもあったほうがいいでしょう。そこまでやったら、pullreqできそうです。というか、もう誰かがやっているのではないでしょうか。

これが一番近そうです。

https://github.com/angular/angular.js/issues/5334

まだ解決していない模様。確かに、ng-mousedownをoverrideしてもいいかもしれません。名前が直感的ではないですが。

famo.usは描画エンジンです。 先日紹介したときに言及したデモは下記にありました。

こちら

このfamo.usをAngularと統合した人たちがいます。

http://famo.us/integrations/angular/

これがなかなかいいのです。famo.usはJavaScriptですべて書くのですが、ちょっと手続き的な感じになります。それをAngularのdirectiveに書けるようにするのです。ちゃんと、bindingも動くので、更新系がなんとも簡単なのです。Angularの経験があれば普通なので、特に驚きもないと言えばそれまでなのですが、これに慣れると、生のfamo.usを書くのは面倒くさく感じるようになるかもしれません。

というわけで、サンプルを作ってみました。 1つのHTMLにすべて詰め込みたかったので、single fileのfamous-angularを使いました。通常は、bowerでインストールする方法が紹介されています。

single fileのものはcdnにないので、http://rawgit.com/を使いました。ところで、rawgitはcdn版(beta)もあってすばらしいです。これは続いて欲しいものです。

さて、出来上がったサンプルは

こちら

です。ソースコードはgistにありますが、下記にも貼り付けます。

<html ng-app="MyApp">
<head>
  <title>famo.us angular sample</title>
  <link type="text/css" href="//code.famo.us/famous/0.2/famous.css" rel="stylesheet" />
  <link type="text/css" href="//cdn.rawgit.com/Famous/famous-angular/fee2b717a53ad762c9e3157580ce255901f4ccad/dist/famous-angular.min.css" rel="stylesheet" />
  <script type="text/javascript" src="//code.famo.us/lib/require.js"></script>
  <script type="text/javascript" src="//code.famo.us/famous/0.2/famous.js"></script>
  <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.18/angular.min.js"></script>
  <script type="text/javascript" src="//cdn.rawgit.com/Famous/famous-angular/fee2b717a53ad762c9e3157580ce255901f4ccad/dist/famous-angular.min.js"></script>
  <script type="text/javascript">
angular.module('MyApp', ['famous.angular'])
.controller('MyController', ['$scope', function($scope) {
  $scope.items = [];
  for (var y = 0; y < 1; y += 0.1) {
    for (var x = 0; x < 1; x += 0.1) {
      $scope.items.push({x: x, y: y, color: 'yellow'});
    }
  }
}]);
  </script>
</head>
<body ng-controller="MyController">
  <fa-app>
    <fa-modifier ng-repeat="item in items" fa-origin="[item.x, item.y]">
      <fa-surface fa-size="[30, 20]" fa-click="item.color = 'red'" fa-background-color="item.color" fa-properties="{textAlign: 'center', lineHeight: '20px'}">{{$index}}</fa-surface>
    </fa-modifier>
  </fa-app>
</body>
</html>

Famo.us/Angularはまだ発展途上ですべての機能がdirective化されていませんが、今後に期待しています。

ng-conf 2014っていうイベントが1月にあったようですね。

YouTubeで動画が公開されている(でもすべてではない)ので助かります。とても勉強になりますね。ほんと、いい時代になったものです。

さて、Daniel ZenのTalkを見ました。AngularJSでモバイルアプリを作るために使えるライブラリがいっぱい紹介されていたのでメモしておきます。

PhoneGap

まず、PhoneGap。 今では、Open Sourceなんですね。

http://cordova.apache.org/

使ったことはないですが興味はあります。 一部ネイティブ関数にもアクセスできるようです。

http://en.wikipedia.org/wiki/Phonegap#Supported_platforms

APNも使えるようですね。個人的にはこれが一番大きいかな。

ngTouch

次は、ngTouch。 これは、Angularのモジュールです。タッチイベントを扱えるそうです。 一方、300ms問題だけなら、fastclickでよさそうです。

angular-mobile-nav

続いて、angular-mobile-nav。iOS5くらいのLook&Feelが簡単に作れるライブラリのようですが、READMEによるとangular-1.2だったらもっと簡単にできるということなので、今後使う機会はなさそうです。

angular-gestures

angular-gesturesは、その名の通りジェスチャーを使うためのディレクティブを提供してくれるようです。基本的には、hammer.jsのラッパーなのでしょうか。

angular-jqm

angular-jqmはjQuery Mobileのラッパーディレクティブです。jQueryなんて使わないぜ、と思っていたら、なんとこのライブラリ、jQueryとjQuery MobileのJSには非依存だそうです。つまり、jQuery MobileのCSSを使っているだけ。一気に気に入りました。そのセンスがいいです。機会があれば使ってみたいと思います。

AngularJS Native

これは、PhoneGapのネイティブ関数を呼ぶためのラッパーのようなものでしょうか。それぞれモジュールごとに分かれているようです。

angular-carousel

angular-carouselは、carouselを簡単に実現するためのライブラリです。 carouselってなんて訳すのでしょう?カルーセル? デモを見れば一発で分かると思います。

angular-snap

angular-snapは、snap.jsのラッパーディレクティブです。最近のモバイルアプリでよくある画面全体がスライドしてメニューがでるUIができるようです。snap.jsは依存ライブラリもなく、サポートブラウザも多く、設定も柔軟なようで魅力的です。

Ionic Framework

Ionic Frameworkは、PhoneGap用のフレームワークです。AngularJSも使われているとのことです。このフレームワークの範囲内でできることをやる分には簡単にできそうです。


その他、Tips等も紹介されていますので、興味がある方は直接スライドを見るとよいでしょう。

http://bit.ly/zen-ng-phonegap

BMEANスタックというのは、BreezeJS, MongoDB, Express, AngularJS, Node.jsを使ったソフトエアスタックのことです。

Breezeを簡単に使いたいと思って、social-cms-backendを大幅に改修しました。 APIは変わっていないのですが、breeze-mongodbを使えるように工夫しました。反面、処理が複雑になりオーバヘッドが増えています。

その新しいsocial-cms-backendを使う段階で一つ気づいたことがあるので、メモ代わりに書いておきます。詳細は省きますが、クライアントサイドのコードでpromiseを使いたくなりました。そもそも、BreezeのAPIがpromiseを返します。Angularにも$qというのがあってpromiseが使えます。

ところが、Breezeのpromiseのメソッドが、

promise.then()
promise.fail()
promise.fin()

なのに対して、Angularの方は、

promise.then()
promise.catch()
promise.finally()

になっているのです。ちなみに、catch,finallyはjsBeautifierでは予約語とみなされて不便です。

wrapすればいいだけですが、なんとなく不満でした。それだけです。

Angularは、https://github.com/kriskowal/qをもとにしているとドキュメントに書いてありますが、どうしてメソッド名を変えたのでしょう。きっと、なにか理由があるのだとは思いますが、読み取れなかったです。

タイトルにある「困った」はこの事実にしばらく気づかず、コーディングではまってしまった、ということでした。


2/16追記。

q.jsのソースコードを読んでいたら、catch,finallyも一応サポートされていました。いずれにしても常に、thenだけを使っておくほうが安心な気がします。

オフラインファーストという言葉があるようです。言葉の存在こそ知りませんでしたが、以前からオフラインアプリを推進したいと思っていました。モバイルファーストなら、HTML5アプリといってもオフラインで使えるべきでしょう、と思います。これには賛否両論あるみたいですが。

http://blog.joelambert.co.uk/2012/11/26/offline-first-a-better-html5-user-experience/

2012年にこんな記事があったのですね。

さて、このオフラインファーストの文脈で、最近注目しているライブラリがあります。Breezeです。 まさにオフラインアプリを作るためのクライアントサイドデータ処理ライブラリです。

stackoverflowでBreezeの代替はないのかという質問がありましたが、

http://stackoverflow.com/questions/15938866/alternative-to-breeze-js

今のところ、ぱっとしたものはなさそうです。今後の登場にも期待しましょう。

Breezeはだいぶ強力なようですが、それを理解するのはなかなか骨が折れます。基本的なデータベースの概念が理解できていないとつらいのかしら。ドキュメントもあるのですが、どこから読んでいいのか分かりにくいです。stackoverflowで聞いてね、って感じです。それはそれで、いいアプローチだとは思います。 ちなみに、Angularのドキュメントは読みやすいと感じます。

今日の本題。MEANスタックって知っていますか? ちらほら記事があるので、知名度はそこそこでしょうか。 MongoDB, Express, AngularJS, Node.jsの頭文字をとっているのですが、個人的には順序が気になって仕方がないです。まあ、語呂合わせなんでしょうけど。

http://mean.io/

こんなサイトがあるのですね。

http://jp.blog.mongodb.org/post/49262866911/the-mean-stack-mongodb-expressjs-angularjs-and

なるほど、MongoDBの人たちが呼び始めたのでしょうか。

さて、本当の本題。BMEANスタックというのをご存知の人は少ないのではないでしょうか。まぁ、Breezeの人が呼んでいるだけだと思いますが、最近の一押しです。

BMEANのBはもちろんBreezeです。BreezeのバックエンドとしてMongoDBを使います。AngularJSも実はオフラインアプリと相性がいいと思っています。ExpressとNode.jsはバックエンドのフレームワークですね。

BMEANを見つけたのは、Breezeのサンプルアプリです。

http://www.breezejs.com/samples/zza

これもまたドキュメントが少なくて、ソースコード読めっていうスタンスなのですが、がんばって読んでみています。そこまで完全なオフラインファーストは意識されていないようで、基本的にはオンラインで動かすことを前提に、一時的にオフラインになっても大丈夫なようにできる(そのようにコーディングすれば)というところでしょうか。

しばらくはBMEANスタックを考えてみようと思います。 そのうち、オフラインファーストに求められるライブラリの形が見えてくるかもしれません。


2/8追記。

Breezeのチュートリアルは面白いです。

http://learn.breezejs.com/

インタラクティブに実行できるので、勉強になりますね。もっと内容が増えてくれたらいいのですが。

小ネタです。

http://docs.angularjs.org/api/ng.directive:form

によるとこのディレクティブは普通に<form>とすれば使えるのですが、 ネストするときは、<ng-form>とする、と書かれています。

これを利用したシーンがあったのでメモしておきます。

フォームをバリデーションする機能があります。例えば、次のように使います。

<form name="myForm">
  <input required ng-model="text"/>
  <button ng-disabled="!myForm.$valid" ng-click="submit()"/>
</form>

このようにすると、テキストフィールドが空のときはボタンが押せないようにできます。

さて、テキストフィールドをng-repeatで生成したい場合はどうしましょう。特に、個々のテキストフィールドごとに例えばアスタリスクマークをつけて、空であることを表示したい場合です。

こういうときにネストしたformを使うとよいようです。

<form name="myForm">
  <ul>
    <li ng-repeat="item in items">
      <ng-form name="itemForm">
        <input required ng-model="item.text"/>
        <span ng-show="!itemForm.$valid">*</span>
      </ng-form>
    </li>
  </ul>
  <button ng-disabled="!myForm.$valid" ng-click="submit()"/>
</form>

個人的にはitemFormのスコープが分かりにくいのですが、そういうものみたいです。nameでinterpolationできないからだとか。

これは使いこなせば便利そうですね。requiredだけでなく、正規表現でパターンを限定したりもできます。

詳しくはこちら。

http://docs.angularjs.org/api/ng.directive:input.text

type="email", type="url", type="number"もあります。

angular.jsからmongodbへのクエリを投げるときに困ったことです。 mongodbのupdate操作ではupdate operatorsというものがあります。 例えば、$setがそれです。 ところで、angular.jsでは$で始まるものは予約語のようにみなされています。

どういうことが起こるかというと、angular.jsの$httpで、

{$set: {title: 'News'}}

のようなJSONを送ろうとすると、$setが消えてしまいます。 この現象に気づくまでにだいぶ時間がかかりました。

解決法は、 http://angularjscorner.blogspot.jp/2012/09/angularjs-and-mongodb-trick.html くらいしかなさそうです。

以前の記事で書いたように、しばらくAngularUIのui-calendarを使っていたのですが、依存ライブラリが多い割りにあまり機能を使ってないので不満でした。

また、昨日の記事で書いたように、jQuery依存はやめたいと思ってました。

他にもいいライブラリはないのかと思って調べたのですが、見つかりません。というのも、どれもUI(CSS)とロジック(JS)がセットになっていてリッチなのです。もっと手軽にできないものでしょうか。

そこで、まずはカレンダーを表示するだけでいいと思って、以前紹介したinkを使って自力でやってみることにしました。

ロジックは、moment.jsを使います。このライブラリは単機能でAPIがきれいでいいです。

最初に完成系のスクリーンショットがこちら。

calscn.png

続いて、コードスニペットを貼り付けておきます。 まず、jadeファイルは次のような感じです。

.ink-grid(ng-controller="CalendarCtrl")
  .column-group.top-space.horizontal-space
    .large-20.content-left
      a(ng-click="updateCalendar(calTable.prevMonth)")
        i.icon-caret-left.icon-large
    .large-60.content-center
      .calendar-title {{calTable.title}}
    .large-20.content-right
      a(ng-click="updateCalendar(calTable.nextMonth)")
        i.icon-caret-right.icon-large
  .column-group.content-center.horizontal-space
    .large-100
      table.ink-table.bordered
        tr
          th æ—¥
          th 月
          th 火
          th æ°´
          th 木
          th 金
          th 土
        tr(ng-repeat="row in calTable.data")
          td(ng-repeat="cell in row", ng-class="{selected: cell.selected}", ng-click="selectCalendar(cell)") {{cell.display}}

ちょっと長いですね。HTMLに変換してなくてすみません。

次にコントローラです、下記。

angular.module('MyApp', []).controller('CalendarCtrl',function($scope, CalendarUtil) {
  $scope.updateCalendar = function(date) {
    $scope.calTable = CalendarUtil.getTableData(date);
  };
  $scope.updateCalendar(new Date());
  $scope.selectCalendar = function(cell) {
    if (cell.date) {
      $scope.calTable.selectedCell.selected = false;
      $scope.calTable.selectedCell = cell;
      $scope.calTable.selectedCell.selected = true;
    }
  };
});

最後に、CalendarUtilです。これも長い。もっと機能が充実したらモジュールとして切り出してもいいかもしれません。

angular.module('MyApp', []).factory('CalendarUtil', function() {
  var dowList = [0, 1, 2, 3, 4, 5, 6];
  function getTableData(selectedDate) {
    var selected = moment(selectedDate);
    var start = selected.clone().startOf('month');
    var end = selected.clone().endOf('month');
    var curr = null;  
    var selectedCell; 
    var data = [];
    for (var j = 0; j < 5; j++) {
      var row = [];   
      for (var i = 0; i < dowList.length; i++) {
        var dow = dowList[i];
        if (end) {
          if (!curr && dow === start.day()) {
            curr = start;
          }
          if (curr) { 
            var cell = {
              display: curr.date(),
              date: curr.clone().toDate(),
              selected: curr.isSame(selected, 'day')
            };
            row.push(cell);
            if (cell.selected) {
              selectedCell = cell;
            }
            if (curr.isSame(end, 'day')) {
              end = null;
            } else {  
              curr.add(1, 'days');
            }
          } else {
            row.push({});
          }
        } else {
          row.push({});
        }
      }
      data.push(row); 
    }
    return {
      title: selected.format('YYYY年MM月'),
      data: data,
      selectedCell: selectedCell,
      nextMonth: selected.clone().startOf('month').add(1, 'months').toDate(),
      prevMonth: selected.clone().startOf('month').subtract(1, 'months').toDate()
    };
  }
  return {
    getTableData: getTableData
  };
});

なんか読みにくいコードですね。もっとシンプルにしたいです。条件分岐が多すぎるような気がします。

こんな感じでできました。同様の機能をjQueryでやったらどうなるのかはちょっと気になります。あまりコード量は変わらないような気もしますね。でも、全体をangularで作るならこの方がいろいろ拡張しやすいと思います。

突然ですが、angular.jsを使うときは、jquery.jsを読み込まないことをおすすめします。

angular.jsとjquery.jsはコンフリクトはしません。ライブラリ的には共存します。しかし、簡単に言えば、angular.jsを使う場合は、jquery.jsを使う必要がないのです。jquery.jsを読み込まない場合、angular.jsはjqLiteというサブセットを使います。ちゃんと検証したわけではありませんが、コードを眺める限りではjqLiteは軽量そうです。

jquery.jsを読み込まないほうがいい理由は軽量化よりも、マインドの問題の方が大きいです。angular.jsのコーディングスタイルとDOM操作は合いません。angular.jsでは、$scopeを使ってDOMをコンパイル(という用語でいいのかな)させればいいのです。こっちの方がパワフルでシンプルです。angular.jsを使いつつDOM操作するというのは、車に乗っているのに自転車を漕いでいるような感じです。ちょっと違うか。

というわけで、jquery.jsを読み込むからDOM操作をしたくなるわけで、それなら読み込まなければいいのです。と思っていつつ、ところが、既存ライブラリの多くはjquery.jsに依存していたりするのです。angular.jsでは、そういうときは、directiveにしてwarpするようにということのようですが、そうすると、jquery.jsは読み込むことになってしまいます。それでもいいのですが、なんか負けた気がします。

jquery.jsを使わないほうがいいと思っている人は、きっと他にもいるはず、ということで検索しました。

http://joelhooks.com/blog/2013/07/27/using-angularjs-stop-using-jquery-as-a-crutch/

そうですよ、乗り換えるならすっぱりと乗り換えましょうよ。

stackoverflowへのリンクがありました。

http://stackoverflow.com/questions/14994391/how-do-i-think-in-angularjs-emberjsor-other-client-mvc-framework-if-i-have-a/15012542#15012542

Voteが3200を超えています。初めて見ました、そんな投稿。

Don't even use jQuery. Don't even include it. It will hold you back.

いいこと書いてあります。

同じ人のメールでの書き込み(上記ブログでも言及されている)では、

You can wire up some callbacks and $apply calls to make a jQuery plugin work but as Pawel said, rewriting something in AngularJS often takes less work. jQuery doesn't have any of the binding or scope magic. When we cut out all of the jQuery code that makes up for that, we're often left with very little code. And when we put those few lines of code in an AngularJS directive, everything will work out of the box. So in balancing levels of effort, rewriting makes sense more often than it doesn't.

と、あります。書き直しちゃったほうが結果的に簡単ということですね。同じことをやるのに大したコーディングの量はないということと、テストできるコードになるということがメリットのようです。トータルでみたら生産性があがるということでしょう。

これからは、angular.jsを読み込むときは、jquery.jsを読み込まないようにしようと思います。これまでもそうしてきたのですが、さらにがんばって、jqueryライブラリを移植するとかですね。CSSは再利用できるでしょう。

ところで、jqueryベースのライブラリ資産ってどれくらいあるんでしょうか。それと同レベルのangularライブラリをそろえるのは至難の業でしょうね。

http://ngmodules.org/

というサイトがあるようです。今後の発展に期待します。

mobile-bookmark-bubbleをangular.jsで使う

  • 投稿日:
  • by

mobile-bookmark-bubbleというのはiPhoneなどのMobile Safariで「ホーム画面に追加」を促すポップアップを出すライブラリです。

依存ライブラリもなく比較的ポータブルに実装されているのですが、 angular.jsで使おうとすると、デフォルトの#ハッシュで抑制する機構がうまくありません。

解決法は色々あると思いますが、おそらく最も手軽な$rootScopeを使う方法を実装してみました。$rootScopeなのでリロードするとリセットされますが、それはそれでいいケースもあるでしょう。

コードはこちら。

angular.module('MyModule', []).run(['$rootScope', '$timeout', function($rootScope, $timeout) {
  $timeout(function() {
    var bubble = new google.bookmarkbubble.Bubble();
    bubble.hasHashParameter = function() {
      return $rootScope.bookmarkbubble_shown;
    };
    bubble.setHashParameter = function() {
      $rootScope.bookmarkbubble_shown = true;
    };
    bubble.showIfAllowed();
  }, 1000);
}]);

(よく考えたら、$rootScopeに入れる必要はなくて、$timeoutの外でフラグ変数を持っていればいいだけか。$rootScopeに入れておくと、HTML側からフラグを操作できることがメリットになるのか、ならないのか。)

あまり参考にならないかもしれませんが、備忘メモでした。