AngularJS

AngularJS 1.xにおけるフォームのリセット

はじめに

本記事はAngular(バージョン2以降)の記事ではなく、昔ながらのAngularJS 1.xの記事になります。ご注意ください。

AngularJS 1.xでは、フォームや入力項目の状態に基づいてDOM要素のclass属性やAngularJS独自プロパティの値を自動的に変化させる機能があります。この機能を利用すれば、ユーザーの入力に合わせて入力項目の背景色や入力検証のエラー・メッセージをリアルタイムに切り替えることができるのですが、HTMLのリセットボタン(type属性がresetの<input>要素や<button>要素)が押されたときにAngularJSと同期が行われず状態がもとに戻りません。
そこで、リセットボタンがクリックされたときに手動で状態を初期化するための処理を記述する必要があるのですが、今回はその方法についてご紹介します。
なお、対象となる環境は下記の通りです。

  • AngularJS 1.6.5
  • Chrome 59

ちなみに、本来ならばフォーム自体をコンポーネント化するべきですが関連オブジェクトとメソッドに注目していただくために簡略化(という名の手抜き?)しています。ご了承ください。

サンプル・コード

index.html
<!DOCTYPE html>
<html ng-app="sample">
    <head>
        <meta charset="UTF-8">
        <title>フォームのリセット</title>
    </head>
    <body ng-controller="InputController as ctrl">
        <form name="inputForm">
            <input type="text" name="name" ng-model="ctrl.name" required placeholder="名前">
            <input type="email" name="email" ng-model="ctrl.email" required placeholder="メール・アドレス">
            <button type="submit">登録</button>
            <!-- 
                リセットボタンがクリックされたときにコントローラーのresetメソッドを呼び出す
                その際、form.FormControllerオブジェクト(<form>のname属性の値で参照可能)を引数に渡す
             -->
            <button type="reset" ng-click="ctrl.reset(inputForm)">リセット</button>
        </form>
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.5/angular.min.js"></script>
        <script src="./app.js"></script>
    </body>
</html>
app.js
let app = angular.module('sample', []);

class InputController {
    // 引数にform.FormControllerオブジェクトを受け取る
    reset(form) {
        // 入力項目の内容を強制的にnullに設定する
        //(このタイミングで再検証が行われ$valid/$invalidが初期化される)
        // 各入力項目のオブジェクトはname属性の値のプロパティ名で参照可能
        form.name.$setViewValue(null);
        form.email.$setViewValue(null);
        // フォームのすべての項目をクリーンな状態(pristine & untouched)にする
        // $setViewValueを実行するとdirtyになってしまうため$setPristineは最後に呼び出す
        form.$setUntouched();
        form.$setPristine();
    }
}
app.controller('InputController', ['$log', InputController]);

解説

入力フォームの状態はform.FormControllerと呼ばれるオブジェクトによって管理されています。また、入力フォームに含まれる各入力項目はngModel.NgModelControllerと呼ばれるオブジェクトによって管理されています。これらのオブジェクトには、状態を強制的に変更できるメソッドが備わっており、それを呼び出すことにより初期状態にリセットすることができます。
そこで、リセットボタンにngClickディレクティブを指定し、コントローラーのリセット処理を呼び出すようにします。その際、form.FormControllerをリセット処理に渡す必要があるのですが、このオブジェクトは<form>のname属性の名前でスコープに公開されているので、その名前を引数に指定しています。
コントローラーのリセット処理では、各項目の検証エラー状態を初期化するためにngModel.NgModelControllerの\$setViewValueメソッドにnullを渡して強制的にリセットしています。この処理はフォームに含まれるすべての項目に対して行う必要があります。
また、\$setViewValueメソッドを呼び出すと、ユーザーが手動で値を入力したのと同じように扱われるため、入力項目の状態がdirtyに設定されます。これをpristineに戻すためにはform.FormControllerの\$setPristineメソッドを呼び出す必要があります。\$setPristineメソッドは\$setViewValueメソッドと異なり、フォーム内の各入力項目を含むフォーム全体に適用されるため、1回の呼び出しで構いません。
最後にtouchedをuntouchedに変更する\$setUntouchedメソッドを呼び出せば完成です。

おわりに

Angular 2.0がリリースされてからもうすぐ1年になろうとしている(しかも5.0.0-beta.0がリリースされている)このタイミングで、AngularJS 1.xの記事を書くのもいまさら感がありますが、研修で説明する際にいつも調べなおしている気がするので、メモ代わりに書いてみました。
AngularJSはそれなりに普及していると思いますし、諸々の事情によりAngularに移行できない方々もいらっしゃるかと思いますので、少しでもお役に立てることを願っています。