AngularJSを1.2系から1.3.0にあげたらrequireした親のコントローラを使っているdirectiveでエラーが出た
コードはこんな
"use strict" #parent #controller angular.module("myApp") .controller("ParentItemCtrl", ($scope,Child) -> #....... coffeeなので自動で波括弧をつけてくれるが明示的につけておいた ............ { removeChildItem: (childId, index)-> Child.destroy({childId: childId}, -> $scope.parent.children.splice(index, 1) ) } ) #directive angular.module("myApp") .directive("parentItem", -> restrict: "EA" templateUrl: "templates/parent_item.html" controller: "ParentItemCtrl" scope: parent: "=item" ) #child directive angular.module("myApp") .directive("childItem", ($modal)-> restrict: "EA" templateUrl: "templates/child_item.html" require: "^parentItem" scope: child: "=item" link: (scope, element, attrs, parentItemCtrl) -> scope.open = ()-> modalInstance = $modal.open({ templateUrl: "/remplates/modal_confirm.html" }) modalInstance.result.then(-> parentItemCtrl.removeChildItem(scope.child.id, scope.$index) # ↑ ここでremoveChildItemがないとエラー ) )
controllerの関数で公開したいものだけ(この場合はremoveChildItem)をモジュールパターンを使って返していて、1.2系では動いていたけど、1.3では動かなくなった。 chromeのDeveloper Toolsで確認すると、parentItemCtrlは、1.2系では期待通りオブジェクトがわたってきていたが、1.3では$get.Constructorとなっていた。
↓1.2
↓1.3
ので、コントローラを以下のように変更した。
# parent_item.coffee angular.module("myApp") .controller("ParentItemCtrl", ($scope, Child) -> @removeChildItem = (childId, index)-> Child.destroy({childId: childId}, -> $scope.parent.cildren.splice(index, 1) ) )
AngularJSでネストしたコントローラをテストする
前提
AngularJSでコントローラをネストして階層化すると、子コントローラ側から親コントローラのスコープを利用可能になる。
問題
子コントローラのユニットテストを書こうとした場合に、子コントローラ側で親コントローラのプロパティやメソッドを使用していると、そのままではテストが書けない。
解決
以下のように記述した。
ReportShowCtrlが親コントローラ、CommentNewCtrlが子コントローラ。
# comment_new_spec.coffee "use strict" describe "Controller: CommentNewCtrl", -> beforeEach module "myApp" CommentNewCtrl = {} scope = {} Comment = {} beforeEach inject ($controller, $rootScope) -> $controller("ReportShowCtrl", { $scope: $rootScope report: {id: 1} }) scope = $rootScope.$new() CommentNewCtrl = $controller "CommentNewCtrl", { $scope: scope } it "some test", -> # do something
そもそも論としてネストしたコントローラで、親のプロパティやメソッドが〜、というのは可読性やテスタビリティが下がるのでアレではある。
派生したスコープでのハマリもあるし。
AngularJSのフォームで投稿後に再利用する際手付かずの状態に戻したい
例えば記事へのコメント投稿後に、そのフォームを再利用する際に、データを初期化しただけだとエラー表示が出てしまう。
ので、$dirtyがfalse($pristineがtrue)の状態に戻したい。
いわゆるブログのコメント投稿とかのよくあるやつ。
form.FormControllerの$setPristine()を使う。
# comment_new.html <div ng-controller="CommentNewCtrl"> <form name="commentNewForm"> <div class="form-group" ng-class="{'has-error': commentNewForm.content.$dirty && commentNewForm.content.$invalid }"> <textarea class="form-control" ng-model="comment.content" placeholder="" tabindex="1" name="content" required></textarea> <span ng-show="commentNewForm.content.$dirty&&commentNewForm.content.$error.required"> 必須です </span> </div> <button ng-disabled="!commentNewForm.$valid" ng-click="save()">コメントを投稿</button> </form> </div>
# comment_new.coffee angular.module('myApp') .controller 'CommentNewCtrl', ($scope, Comment) -> # ......................... $scope.save = -> $scope.comment.$create((data)-> # ......................... $scope.comment = new Comment(report_id: reportId) #<- 初期化 $scope.commentNewForm.$setPristine() # <- コレ )
OK.
RailsのResourcesでNestしてshallow: trueしたresourcesをAngularJSの$resourceで扱う
例えばこういう感じのresourcesを定義したとする。
APIの例。newとeditは省いた。
# routes.rb namespace :api, defaults: { format: :json } do resources :reports, shallow: true do resources :comments, except: [:new, :edit] end end
定義されるルーティングはこうなる。reportsのは話に関係ないので省略
GET /api/reports/:report_id/comments(.:format) api/comments#index {:format=>:json} POST /api/reports/:report_id/comments(.:format) api/comments#create {:format=>:json} GET /api/comments/:id(.:format) api/comments#show {:format=>:json} PATCH /api/comments/:id(.:format) api/comments#update {:format=>:json} PUT /api/comments/:id(.:format) api/comments#update {:format=>:json} DELETE /api/comments/:id(.:format) api/comments#destroy {:format=>:json}
Railsではよくあるパターンだと思う。
このコメントのresourceをAngularJSの$resourceで使いたい。
$resourceでよく見かけるサンプルだとシンプルなものが多い.
こういったことは出来ないんだろうかと、AngularJSのリファレンスで$resourceの項目を見てみたら、ちゃんとやり方が書いてあった。
url – {string} – action specific url override. The url templating is supported just like for the resource-level urls.
$resourceの第3引数のactionsでurlをoverrideすればよいと。
やってみた。
下記はqueryとかsaveとか自動のやつは無視してRails的なactionを定義
# Comment.coffee 'use strict' angular.module('myApp') .factory 'Comment', ($resource) -> $resource("/api/comments/:commentId", {commentId: "@id", reportId: "@report_id"}, { index: {method: "GET", url: "/api/reports/:reportId/comments", isArray: true} create: {method: "POST", url: "/api/reports/:reportId/comments"} update: {method: "PATCH",params: {commentId: "@id"}} show: { method: "GET",params: {commentId: "@id"}} destroy: {method: "DELETE",params: {commentId: "@id"}} })
使う例
# sample.coffee angular.module('myApp') .controller "SampleCtrl", ($scope, $stateParams, Comment) -> $scope.comments = Comment.index(reportId: $stateParams.reportId) $scope.comment = new Comment(report_id: $stateParams.reportId) $scope.save = -> $scope.comment.$create((comment)-> # do something
上記の場合だと、report_idとreportIdでもにょってしまうので、サービス側でもう少しラップしてオブジェクトの引数ではなく、パラメータで渡した方が幸せになれるかもしれない。
AngularJSで$sceを使ってるフィルタなんかのテスト
$sceをDIしてるやつ、例えばこちらのようなののテストを書く場合。
# newlines_spec.coffee "use strict" describe "Filter: newlines", -> beforeEach module "myApp" newlines = {} beforeEach inject ($filter) -> newlines = $filter "newlines" it "\nを<br />に変換して返すこと", -> text = "angularjs\nnewlines" expect(newlines(text)).toBe ("angularjs<br />newlines")
単純にこんな感じに書くと、下記のようなメッセージで失敗する
Expected { $$unwrapTrustedValue : Function } to be 'angularjs<br />newlines'.
ので、$$unwrapTrustedValueでexpectしてみた。
# newlines_spec.coffee it "\nを<br />に変換して返すこと", -> text = "angularjs\nnewlines" expect(newlines(text).$$unwrapTrustedValue()).toBe ("angularjs<br />newlines")
テスト通ったけど、$sceProviderをテストの時は無効化するとか、他のやり方のほうがいいんだろうか。
beforeEach module 'myApp',($sceProvider)-> $sceProvider.enabled(false) return
RailsでつくるAPIサーバのドキュメントを自動生成してくれるAutodocを使っていて気をつけること
Autodocとはなんぞやという方はこちらの記事を参照。 公式はこちら
で、大変便利なAutodocだけど、注意点が2つ。
まず一点目はRspc3で動かない点。プルリクが上がってるが、取り込まれてない。
なので、forkして使ってる人が結構いる感じ。
2点目、通信が発生しているところでマークダウンが生成されるので、shared_exampleとか使って、そこで実際の通信が発生するようなものは、ドキュメントが全てshared_exampleがあるファイル名で生成されてしまう。
具体的に言うと、例えばこちらの記事のようなヘルパーメソッドを用意して、これでAUTODOC=1でAPIのテストを全て行った場合、shared_exmpleを呼び出している個別のspecファイルではなく、doc/support/api_helper.mdというドキュメントが生成されてしまう。
こうなるのは仕方がない気がするので、autodocを使ってドキュメントを生成する場合は、Autodocで記録する個別のexampleの中でgetやpostを投げる感じで。