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

突然ですが、angular.jsで$routeProviderを使うとき、次のように書くと思います。

angular.module('MyModule', []).config(function($routeProvider) {
  $routeProvider.when('/foo', {
    templateUrl: 'foo.html'
  }).
  when('/bar', {
    templateUrl: 'bar.html'
  });
});

ところで、2つならいいですが場合よっては多いこともあると思います。 そこで汎用的なルートを書いてファイルを置けばそのルートが使えるようにしたいと思います。もちろんファイルがないときはエラーになります。

ドキュメントを参照すると、

http://docs.angularjs.org/api/ngRoute.$routeProvider

正規表現は使えないのですが、named groupは使えます。つまり、

when('/:name', {

のように書くことができます。さてこの場合、 $routeParams.nameで実際の値にアクセスできるのですが、 $routeParamsはルートが切り替わるまで使えません。 つまり、moduleのconfigでは使えないのです。

検索したところ、これを見つけました。

http://stackoverflow.com/questions/11534710/angularjs-how-to-use-routeparams-in-generating-the-templateurl

ところで、常々思うのですが、stackoverflowを参照するときは注意する必要があります。回答を鵜呑みにしてはいけません。

今回のケースでは、現時点では1番初めの回答は、「できない」というものです。この回答が最もvoteされ、acceptもされています。 しかし、現時点での2番目の回答を見ると、angular-1.1.3以降では「できる」となっています。

まあ、このケースはそれほどひどくはなくて、おそらく質問時点では、できなかったのでしょう。

というわけで、現時点のangular-1.2では下記でできます。

angular.module('MyModule', []).config(function($routeProvider) {
  $routeProvider.when('/:name', {
    templateUrl: function(params) {
      return params.name + '.html';
    }
  });
});

ついでにcontrollerもnameを使って動的に設定できたらよいと思うのですが、それはまたの機会にでも調べることにします。

AngularJS 1.2.0がリリースされたので、 今までangular-1.0.8を使っていたプロジェクトをアップデートしてみました。

そのときに困ったこと、修正したことをメモしておきます。

まず初めに、読み込むスクリプトを変えたのですが、エラーになりました。

Error: [$injector:modulerr] http://errors.angularjs.org/undefined/$injector/modulerr?...

のようなエラーです。何かが悪いのは分かりますが、エラーの内容が分かりません。ここが、一番はまったところです。実は、minifyしたスクリプトだとこのようなエラーになるようです。angular.min.jsの代わりにangular.jsを読み込むようにしたら、エラーの原因が分かるようになりました。

一つ目のエラーはngRouteが別ファイルに分かれていることでした。これは、angular-route.jsを読み込むようにして、

angular.module('myApp', ['ngRoute']);

のようにすることで解決しました。

二つ目のエラーはng-bind-html-unsafeが使えないことでした。ng-bind-htmlに統合されて賢くなっています。angular-sanitize.jsを読み込んで同様に設定するだけで、サニタイズされるようになりました。ついでに、linkyというフィルターも入ったのでこれまで自作していたフィルターが不要になりました。詳しくは、

http://code.angularjs.org/1.2.0/docs/api/ngSanitize

のドキュメントを読んでみてください。linkyはアンカーのtarget属性も指定可能で、例えば、

<p ng-bind-html="x | linky:'_blank'" />

のように使います。

今回のケースはこの二つだけの修正で移行が完了しました。 angular-1.2.0ではその他変更点も多数あるようなので、これだけでは済まないケースもあるとは思いますが、一例として参考になればと。

今後、1.2.0の新しい機能にも挑戦していきたいと思います。


11/13追記。

もう一点修正が必要なものがありました。

Error: [$parse:isecprv] Referencing private fields in Angular expressions is disallowed!

というエラーがでていました。 オブジェクトの中のフィールドにアクセスできなくなったようですね。private fieldってなんだろう。まさか、単にアンダーバーのprefixが付いているというだけかしら。いずれにしても、オブジェクトごと関数に渡すようにして解決しました。

http://docs.angularjs.org/error/$parse:isecprv

に書いてありました。アンダーバーで始まるか終わるかするとプライベートとみなされるみたいです。

Angularの初期のチュートリアルでは、コントローラをグローバル関数として定義する例が載っています。

http://code.angularjs.org/1.0.8/docs/tutorial/step_02

function PhoneListCtrl($scope) {
  ...
}

といった感じで。これを参照してか、さまざまなところでAngularのサンプルコードが、グローバル関数のコントローラを使っています。

どこだか忘れましたが、よくドキュメントを読むと、production時はこれは好ましくないと書いてあります。ちゃんとDependency Injectionを使って依存解決するようにと。

最新のチュートリアルでは、上記コードは廃止されたようです。

http://docs.angularjs.org/tutorial/step_02

angular.module('MyApp', [])
.controller('PhoneListCtrl', function($scope) {
  ...
});

のようにします。これで名前の衝突が起こりません。 おそらく唯一の手間は、MyAppをちゃんと指定しないといけないことです。

<html ng-app="MyApp">

これは、ほとんどのケースで行われるので問題ないでしょう。

例によって、ちゃんと探せばこういう指針を書いたドキュメントもあるのかもしれませんが。ぜひとも正しい方法を広めていきたいものです。

backbone.jsのemulateHTTPというのは、PUTやDELETEをPOSTに変換するものです。なぜそんなことが必要かというと、PUTやDELETEが通らないHTTP Proxyがあるからです。

emulateHTTPでは、X-HTTP-Method-Overrideというヘッダーを使います。正確には、_methodというパラメータも追加されるのですが、それは対応しないものとします。X-HTTP-Method-Overrideはnode.jsのexpressやconnectでサポートされます。

app.use(express.methodOverride());

とすれば有効になります。

さて、こんな機能は当然angular.jsで用意されていて一行くらいの設定で使えるようになるだろうと思っていたら、そんなことはありませんでした。 検索すると、

http://manikanta.com/blog/2013/07/25/decorate-angularjs-http-service-to-convert-put/

がヒットしました。 これでできそうかと思ったのですが、実装が中途半端です。 具体的には、

$http.put({...});

のときは想定どおりに動くのですが、

$http({method: 'PUT', ...});

のような使い方ではうまく動きません。 そこで書き直してみることにします。

var App = angular.module('App');
App.config(['$provide', function($provide) {
  $provide.decorator('$http', ['$delegate', function($delegate) {
    var $http = function(config) {
      var method = config.method.toUpperCase();
      if (method === 'DELETE' || method === 'PUT') {
        config.headers || (config.headers = {});    
        config.headers['X-HTTP-Method-Override'] = method;
        config.method = 'POST';
      }
      return $delegate(config);
    };
    angular.extend($http, $delegate);
    $http.delete = function(url, config) {
      return $http(angular.extend(config || {}, {method: 'DELETE', url: url}));
    };
    $http.put = function(url, data, config) {
      return $http(angular.extend(config || {}, {method: 'PUT', url: url, data: data}));
    };
    return $http;
  }]);
}]);

こんな感じになりました。 もう少しスマートな方法はないかと、思ってしまいますね。 しかし、おかげで$provide.decoratorの使い方を覚えました。

上記コードは動作確認してないので、使用レポートをお待ちします。

昨日、 NodeとAngularを使ってTwitterクローンを15分で作るスクリーンキャスト を書いたのですが、ライブラリの紹介をし損ねたので紹介します。

そもそもの動機は、Twitterクローンを簡単に作りたいと思ったことが発端です。 そこで、なにかいいライブラリはないかと色々調べたら、Railsもどきのフルスタックフレームワークがいくつか見つかりました。

他にもいっぱいあるのですが、気になったのがこのあたりです。

ところが、なんと言うかAngularJSを使おうとするとこれらのライブラリはオーバースペックな感じがするのです。Ruby on Railsのようにデファクトが一つある状態であればいいですが、もどきがいっぱいある状態ではコンベンションも統一感がなさそうです。

Angularを前提とするのであれば、フレームワークではなくもっとシンプルなライブラリがあればいいのではないかと思いました。それでもって、面倒なことはそのライブラリが引き受けてくれるような。

そこで、TwitterやFacebookのようなSNSのサイトを作ることに限定したライブラリを作りました。node.jsではexpress.jsがほぼデファクトなのでexpressのmiddlewareとして作りました。middlewareにすることで、単一のフレームワークに依存することなく、他のアプリにアドオンする形で導入できます。

面倒な、認証や権限管理の機能はライブラリが面倒みてくれます。 現状では、Facebook認証と、標準的な権限管理(ownership based)の機能が用意されているだけですが、プラグインとして他の認証や権限管理の機能を追加できるようになっています。 また、通知(メールアラートなど)の機能も今後追加してきたいと考えています。

このライブラリを使うと、フロントエンドを作るだけですぐサービスできます。Railsのようにデフォルトがないので作らないとなにもできないのですが、Angularに慣れていればコンベンションを意識してコーディングするよりもずっと楽かもしれません。

まずは、 こちら からスクリーンキャストを見てみると雰囲気が分かるかもしれません。 もしかしたら、分からないかもしれません。

ソースコードは、

https://github.com/dai-shi/social-cms-backend

にあります。


8/7追記。

最近知ったのですが、http://sproute.io/というフレームワークもあるようですね。 こちらは比較的SNSに特化したようなフレームワークであるようです。 ほとんどコーディングなしでTwitterクローンが作れるようです。 でも、$99って。

みなさん、AngularJS使ってますか?

Angularを使うと、お手軽にWebアプリ(と言っていいのかな)が作れます。

そのお手軽さをさらに助けるのが、Node.jsとそのライブラリ達です。(いや、Nodeに限った話でもないですが。) 細かい説明は他にお任せしますが、Connect/Expressやその様々なmiddlewareを使うと機能を色々追加できます。 今回、そのmiddlewareの一つとして、SNSのバックエンドライブラリを作りました。 これを使うと、フロントエンドを作るだけで簡単にSNSのサイトができあがります。

試しに、Twitterクローンを作ってみました。今回はフォロー機能などなしの単機能版です。ちなみに、フォローやグループの機能はライブラリには入ってます。

Ruby on Railsのまねをして15分でコーディングするスクリーンキャストを作りました。NodeやAngularの知識を前提としているので、見るだけそれらが理解できるようになるわけではありませんが、どうぞご覧ください。音もテロップもなし、それどころかマウスポインタもなしです。シンプルに。

スクリーンキャストを別ウインドウで開く

コーディングした結果のソースコードはこちらに置いてあります。

https://github.com/dai-shi/twitter-clone-sample/tree/20130804_recorded

実は一文字だけ打ち間違いがあってあとから修正しました。

せっかくなので作ったTwitterクローンを動くようにアップしました。

http://twitterclonesample-nodeangularapp.rhcloud.com/

もしよろしければ試してみてください。

connect-prerendererのIssue #3を解決

  • 投稿日:
  • by

DailyJSに取り上げてもらって、 一気にStar数が増えたconnect-prerendererですが、 Issueが一つ残ってました。

https://github.com/dai-shi/connect-prerenderer/issues/3

jsdomがらみなので、難しいなぁと思っていたのですが、 こっちの方 でも使ってくれそうな様子なので、ちょっとがんばってみました。

ヒントは、Issue #3で報告してくれた、

http://stackoverflow.com/questions/10054071/jsdom-hashchange-event

と、jsdomの古いIssueの

https://github.com/tmpvar/jsdom/issues/433

でした。

そもそも、hashchangeイベントの発火が実装されていないとのことでした。 みようみまねでjsdomに手を加えて、コードを追加しました。

結果、動くようになりました。まだ簡単なe2eテストが動いただけですが、 不具合があれば誰か報告してくれることでしょう。それまで、待つ。

AngularJSがnode.jsと通信するときは、RESTでJSONを返すのが便利です。標準的なやり方にしておくと、コードを書く量が減らせます。

node.jsとexpress.jsでRESTを書くときは、

res.json({foo: 'hoge', bar: 123});

のようにします。

これまで、真偽値を返したい場合は、

res.json(true);

としていました。node.jsはこれで正しいです。しかし、これだとAngular的にうまくありません。

Angularはプリミティブな値が返ってくることは想定していないようで、オブジェクトかオブジェクトの配列の形で返してあげる必要があります。文字列の配列もダメです。

今回の例では、

res.json({result: true});

とすることで解決できました。

実は、この話は半年くらい前にも気づいて調べたことがあったのですが、すっかり忘れていました。今日も全く同じことをして1時間ほどはまりました。忘れないように、ということでメモでした。

AngularJSにはフィルタという便利な機能があります。

チュートリアルの3で解説されています。

http://docs.angularjs.org/tutorial/step_03

動作するデモもあります。

http://angular.github.io/angular-phonecat/step-3/app

試してみたことない人はぜひどうぞ。

さて、このフィルタですが、およそ次のように使います。

<input ng-model="query">
<ul>
  <li ng-repeat="item in items | filter:query">
    {{item.title}}
  </li>
</ul>

この場合、queryが空の場合は全件が出力され、queryを入力し始めると件数が絞られていきます。 これを、queryが空の場合は全件ではなく例えば、10件にしたいと思いました。

AngularJSにはlimitToというフィルタもありますので、それを使うことでできました。

<li ng-repeat="item in items | filter:query | limitTo:limit">

のように変更します。 さて、このlimit変数はどうしましょう。 あまりうまい方法は思いつかなかったのですが、JSで次のようにしました。

$scope.updateLimit = function() {
  if(query.length) {
    limit = 99999;
  } else {
    limit = 10;
  }
};
$scope.updateLimit();

さらに、HTML側を次のようにします。

<input ng-model="query" ng-change="updateLimit()">

これでqueryが空の場合はlimitが10になります。そうでない場合は99999にすることで全件表示しようとしてますが、当然この数字より大きい配列がきたら切れてしまいます。nullとかNaNとか指定できないのかと思いましたが、angular.jsのソースを読んでもそんな例外扱いはありませんでした。 フィルタを条件付にできないのかとも思いましたが、ドキュメントを読む限りでは分かりません。

とりあえず、これでよしとします。トップ10件にする場合は、limitを呼び出す前に並び替えればよいでしょう。

<li ng-repeat="item in items | filter:query | orderBy:orderFunc | limitTo:limit">

それで、同じようにorderFuncを条件に応じて変更するということです。まあ、ここまでするくらいならカスタムフィルタを書いたほうが早いかもしれませんね。

おまけ。

filterやorderByやlimitToなどのことを総称して「フィルタ」と呼ぶのですが、filterも「フィルタ」です。正確には「フィルタのフィルタ」でしょうか。ややこしい。

最近、ページ内リンクというのはあまり使われないと思いますが、使いたいケースがあったので、調べました。備忘メモです。

ページ内リンクというのは、リンク先に

<a name="id001">

とか

<div id="id001">

とか書いておいて、リンク元を

<a href="#id001">

とすることで、このリンクをクリックするとリンク先にスクロールしてくれるものです。

ところが、AngularJSではこれはルートとみなされ、ご丁寧に#/id001に変換して、$routeProviderの処理がされてしまいます。当然、そのようなルートは用意していないので、予期せぬ動作になります。例えば、otherwiseにredirectToを設定していたりすると、トップページに戻ります。

これは困ったということで、いろいろ調べました。はじめに見つけたのがこれ。

Angular JS - Scrolling To An Element By Id

AngularJSには$anchorScroll()というのが用意されていてその使い方が説明されています。具体的には、$rootScopeの$routeChangeSuccessイベントをフックするのですが、詳しくは、上記サイトをご覧ください。

さて、これを試しましたが、うまくありませんでした。 新しいページを表示してスクロールする分にはいいのですが、同一ページでもリフレッシュされてしまいます。つまり、$routeChangeSccessイベントはルートが新しくなってから呼ばれるものなのです。AngularJSのドキュメントを読むと、$routeChangeStartというイベントもあるのですが、結局、そのイベント処理のあとに、ルートを書き換えてしまいました。

さらに調べると、次のIssueを見つけました。

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

$locationのhashやpathを変更しても、ルートを書き換えない方法を用意してほしいという要望です。この中に書いてある方法では解決しませんでしたが、最後にPRを見つけました。

https://github.com/angular/angular.js/pull/2555

蛇足ですが、このPRは分かりやすく書かれてますね、Basic Usageとか。 でも、結局このPRもリジェクトされてました。 これを許すと、URLの状態とルートの状態が整合しないことができてしまうので避けたいそうです。

悩んだあげく、次の方法で解決しました。

まず、$routeProviderを次のようにします。

$routeProvider.when('/home', {
  templateUrl: 'partials/home.html',
  controller: HomeCtrl,
  reloadOnSearch: false
}).
otherwise({
  redirectTo: '/home'
});

ポイントは、reloadOnSearchです。OnSearchとありますが、hashの変更にも有効のようでした。

その上で、

<a href="#id001">

の代わりに、

<a href="#/home#id001">

とします。まあなんとも普通の方法ですが、これで目的は達成できました。 /homeがハードコードされているのが気になる方は、$locationが使える状況なら、次のようにしましょう。

'<a href="#' + $location.path() + '#id001">'

いかがでしょうか。AngularJSを使いつつ、レガシーなことをやろうとすると大変だ、という例でした。

ちなみに、hrefを使わなくて良いのなら、

Angular JS - Scrolling To An Element By Id

の2つ目の方法がよさそうです。試していませんが。

connect-prerendererのgooglebot対応

  • 投稿日:
  • by

connect-prerendererのStart数が20に到達。すごいのかすごくないのかよく分かりませんが。

今回、Issueが報告されたので、対応しました。

https://github.com/dai-shi/connect-prerenderer/issues/6

AJAXページをクロールするための、Googleの仕様があるらしいです。

https://developers.google.com/webmasters/ajax-crawling/docs/getting-started

仕様は簡単で、hashのすぐあとにエクスクラメーションマークをつけておくと、_escaped_fragment_というクエリパラメータをつけてクロールしてくれるということです。

つまり、

http://aaa.bbb/ccc#!ddd

が

http://aaa.bbb/ccc?_escaped_fragment_=ddd

に変換されて、googlebotからアクセスされることになります。

これは、connect-prerendererの出番です。この変換スキームに対応するように修正しました。詳しくはソースコードを参照することになりますが、使い方は下記のようになります。

var prerenderer = require('connect-prerenderer');
app.use(prerenderer({
  targetGenerator: 'googlebot'
});

この使い方は、まだドキュメントに書いていません。テストができていないからです。

興味ある人いませんか~?

AngularUIのui-calendarを使ってみた

  • 投稿日:
  • by

AngularUIにCalendarというサブプロジェクトがあるので、使ってみました。

https://github.com/angular-ui/ui-calendar

これは、FullCalendarというjQueryプラグインをAngularのディレクティブ化するもののようです。 ちょっと依存ライブラリが多くて使うのをためらったのですが、他に良い候補も見つからず、AngularUIに入っているのだからうまくラップされているのだろうということで。

READMEを読むと、bowerを使って依存関係を管理せよと書いてあるのですが、まだ理解していないものを使いたくなかったので、手動で設定しました。

分かってしまえばそれほど難しいものではありません。次のものを読み込めばいいだけです。

  • fullcalendar.css
  • jquery.min.js
  • jquery-ui.min.js
  • jquery-ui-i18n.min.js
  • angular.min.js
  • angular-ui.min.js
  • fullcalendar.js
  • calendar.js

必要であればjQueryUIのテーマのcssも読み込みましょう。

さて、ui-calendarを使うとng-modelが使えるようです。

<div ui-calendar="calendarOptions" ng-model="eventSources">

READMEにはこのように書いてありましたが、ここで、はまりました。ng-modelはeventSourceオブジェクトを指定するのではなく、events配列を指定するようです。READMEがソースの改変に追いついていないようです。もっと言うと、READMEではng-modelはオプション扱いですが、書かないとエラーになりました。

実際にevents配列を使ってみるのはこれからです。


追記。

ng-modelに指定するのは、events配列ではなく、eventSources配列であってました。でも、eventSourceオブジェクトの配列ではありません。READMEはeventSourceオブジェクトのリンクを指しているので分かりにくいです。

最近、connect-prerendererのIssueを報告してもらって、少しずつ不具合が見つかっては修正しています。

うまく解決したのは、

の二つ。前者は、

<div>
  count={{count}}
  <span>hoge</span>
</div>

のように、TextNodeとHTMLElementが並んでいるときに、 ng-bind-templateで上書きしてしまう問題でした。

<div>
  <span>count={{count}}</span>
  <span>hoge</span>
</div>

とコンパイル時になるように、Angular.jsをいじりました。 ただ、CSSがそれを想定しない書き方だと、表示に不具合がでるかもしれません。

後者は、doctypeがjsdomで消えている問題でした。こっちはすぐに直りました。

https://github.com/dai-shi/connect-prerenderer/issues/3 がなかなか手ごわく、まだ解決の糸口が見つかりません。 以前書いた、jsdomからAngularJSの不具合二つ目の問題です。

なんにしても、興味を持ってくれる人が増えるのはうれしいことです。 connect-prerendererはほとんど宣伝していないのに、Star数が増えてきました。それはそれで不思議。

RSS Pipesは、JavaScriptでフィルターを書く、RSSアグリゲーターです。

RSSの登録数が増えてきたら検索機能を作ろうと思っていたのですが、 先に作ってしまいました。検索と言っても、表示されているものを絞り込むだけです。

AngularJSには標準でフィルターというものが組み込まれているので非常に簡単にできました。ソースコードに検索ボックス配置の1行と、フィルター追記の1行で済んでしまいました。Bootstrapのレイアウトでもう数行いじりましたが、トータルで5行の改変のみです。

興味ある方は、diffを見てみてください。

https://github.com/dai-shi/rss-pipes/commit/c250c8a7d9b03476503c78fda5a2f0ab5cef5894#views/partials/home.jade

お手軽です。詳しくは、AngularJSのチュートリアルを参照しましょう。

http://docs.angularjs.org/tutorial/step_03

RSS PipesはJavaScriptでフィルターを書くRSSアグリゲーターです。

既に存在するアイテムをコピーして新しく登録する「複製」機能が欲しいと思って実装方法を考えていたのですが、思いのほか簡単にできました。

備忘メモしておきます。

$routeProviderのコードは、

$routeProvider.when('/home', {...});
$routeProvider.when('/edit', {...});

のようになっています。/homeに複製ボタンがあり、それを押すと/editに遷移します。つまり、/homeから/editに複製元のデータを渡したいということです。

$rootScopeを使いました。

$rootScope.saved = {};

としておいて、/homeのコントローラで、

$scope.saved.data = data;

として保存し、/editのコントローラで、

$scope.data = $scope.saved.data;

として復元します。場合によりますが、今回は消したかったので、さらに、

delete $scope.saved.data;

としました。

もっとスマートな方法があるのかもしれませんが、今回はこれでよしとします。

JQueryやAngularJSはGoogle Hosted Librariesが配信してくれて、 BootstrapはBootstrapCDNというのもあって 便利です。

しかし、マイナーなライブラリになるとどうしたものかと思っていました。手を入れないライブラリは自分でホスティングする理由もないので、CDNがないかなと思って探したところ、

を見つけました。cdnjsはGitHubのPull Requestsでライブラリの登録を依頼できるというのが面白いです。

しかし、どちらもcssファイルは登録されていない様子なので、結局すべてを外部CDNに頼ることはできないと。探せば他にもありそうですね。

angular.jsはサーバとの通信にJSONを使うことが典型的ですが、別にテキストでもXMLでも大丈夫のようです。今回、XMLを取得してng-repeatで表示するサンプルを作ったので簡単に紹介します。

まず、コントローラ内で次のようにします。

  $scope.jQuery = jQuery;
  $scope.getXmlContent = function() {
    $http.get($scope.xmlUrl).success(function(data) {
      $scope.xmlDoc = jQuery(jQuery.parseXML(data));
    });
  };
  $scope.getXmlContent();

jQueryはロードされているものとします。getXmlContentが$scopeに入っているのは、ng-clickからも呼べるようにするためです。取得先のURLは同一ホストでないとブラウザにはじかれると思います。

次に、html側で次のようにします。

<div ng-show="xmlDoc">
  <ul>
    <li ng-repeat="itemEle in xmlDoc.find('item').get()">
      <span ng-bind="jQuery(item.Ele).find('content').text()" />
    </li>
  </ul>
</div>

ng-repeatのところでget()を使ってリストにすることがポイントです。

あまり利用シーンはないかもしれませんが、参考にどうぞ。

connect-prerendererが一応完成

  • 投稿日:
  • by

AngularJSのマイルストーンにserver-side prerenderingというのがあるのですが、当分できそうにないので自分で作ってしまっています。

https://github.com/dai-shi/connect-prerenderer

express.jsのmiddlewareで使うことを想定していますが、express.jsには依存しないようにしたので、connect-prerendererです。expressとconnectの違いは、他のブログなどでも説明されていることでしょう。

何をやっているかを一言で言うと、サーバ側でjsdomを使ってJavaScriptを走らせて、レンダリング後のHTMLページをクライアントに返すというものです。

基本的なところはしばらく前にできていて、簡単なテストケースは動いていたのですが、AngularJSを動かすのがなかなか大変でした。結局、angular.jsのソースに手を入れることになりました。

AngularJSを使った簡単なテストケースも動いて、満足です。 もってきてすぐ使えるようなライブラリではないですが、たぶん世界初の取り組みでしょう。

あとははAngulaJS側でもっとうまく取り込んでもらわないとつらいところです。今は決めうち(ng-repeat="..."はOKだが、class="ng-repeat:..."は動かないとか)でやっている部分があるため。

あまり説明できずに意味不明かと思いますが、これにて。

AngularJSのangularInitを読みました。

<html ng-app="<mymodule>" class="ng-app:<mymodule>">

と書くことは、

angular.bootstrap(document, ["<mymodule>"]);

とたぶん同等です。 より正確には、readyで呼ばれるので、

angular.element(document).ready(function() {
  angular.bootstrap(document, ["<mymodule>"]);
});

となります。angular.elementはJQueryかJQLiteです。

分かったことは、それだけです。


追記。

よく読んだら、ドキュメントにちゃんと書いてありました。

http://docs.angularjs.org/guide/bootstrap

jsdomからAngularJSの不具合二つ目

  • 投稿日:
  • by

jsdomからAngularJSで作ったページにアクセスしたら、こんなエラーがでました。

Error: 10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: [["fn: $locationWatch; newVal:18; oldVal: 17"],["fn: $locationWatch; newVal: 19; oldVal:18"],["fn: $locationWatch; newVal: 20; oldVal: 19"],["fn:$locationWatch; newVal: 21; oldVal: 20"],["fn: $locationWatch;newVal: 22; oldVal: 21"]]

このエラーは"familiar"らしいです。一番該当しそうなのが、

angular.js Issue #1417

これです。現時点ではまだ解決されていない様子です。

果たして、$routeProviderでredirectToを使うのを止めてみたらエラーが出なくなりました。んー、不便です。