memorandums

日々の生活で問題解決したこと、知ってよかったことなどを自分が思い出すために記録しています。

ng-modelの値をfunctionに引数渡しするときにうまくいかないケースとその対処

Ionicframework勉強中です。AngularJSもチュートリアルを理解した程度です。

あまりに初歩的で役立つ人は少ないかもしれません。。。がメモとして残しておきたいと思います。

ここ数日悩んでたコードが以下です。長ったらしいですが。。。コードはこちらにあります。

  • html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
    <link href="http://code.ionicframework.com/1.0.0/css/ionic.min.css" rel="stylesheet">
    <script src="http://code.ionicframework.com/1.0.0/js/ionic.bundle.js"></script>
  </head>
    <body ng-app="app">

    <ion-pane>
      <ion-nav-bar class="bar-stable"></ion-nav-bar>
      <ion-nav-view></ion-nav-view>
    </ion-pane>
    
    <script id="templates/players.html" type="text/ng-template">
    <ion-view title="Players">
  	  <ion-nav-buttons side="right">
        <button class="right button button-icon icon ion-plus" ng-click="openModal()"></button>
      </ion-nav-buttons>
      <ion-content>
        <ion-list>
          <ion-item ng-repeat="player in players">
            {{player.player_name}}
          </ion-item>
        </ion-list>
      </ion-content>
    </ion-view>
    </script>
    
    <script id="templates/playerInputForm.html" type="text/ng-template">
      <ion-modal-view>
    		<ion-header-bar>
    			<a class="button" ng-click="closeModal()">Cancel</a>
    			<button ng-click="createPlayer(newPlayer)">Add2</button>
    			<h1 class="title">Player Information</h1>
    		</ion-header-bar>
    		<ion-content>
    			<div class="row">
    				<div class="col">
        			<button ng-click="createPlayer(newPlayer)">Add1</button>
    					<label class="item item-input large">
    						<input type="text" placeholder="Player Name" ng-model="newPlayer.player_name">
    					</label>
    				</div>
    			</div>
    		</ion-content>
      </ion-modal-view>
    </script>
  </body>
</html>
  • app.js
var app = angular.module('app', ['ionic']);

app.config(function($stateProvider, $urlRouterProvider) {
  $stateProvider
    .state('players', {
      url: '/players',
      templateUrl: 'templates/players.html',
      controller: 'PlayersController'
    });
  $urlRouterProvider.otherwise('/players');
});

app.controller("PlayersController", function($scope, $ionicModal) {
    $scope.players = [];

    $scope.createPlayer = function(u) {
        $scope.players.push({player_name: u.player_name})
        $scope.modal.hide();
    }
    $ionicModal.fromTemplateUrl('templates/playerInputForm.html', {
      scope: $scope,
      animation: 'slide-in-up'
    }).then(function(modal) {
      $scope.modal = modal;
    });
    $scope.openModal = function(){
        $scope.modal.show();
    }
    $scope.closeModal = function(){
        $scope.modal.hide();
    }
});

悩んでたのは、以下のモーダル画面で新規登録したいPlayerの名前を入力してADD2ボタンを押すとメインのリストに追加される。。。はずなのですが、それがされない。。。

f:id:ke_takahashi:20150913154117p:plain

ng-model="newPlayer.player_name"で定義したnewPlayerがcreatePlayer関数の引数にセットされてJSのコントローラで利用できるはず。。。と想定していたのですが、JS側のuはundefinedだって怒られます。ちなみにADD1ボタンを押すと動作します。

ADD1ボタンはng-modelと同一のion-contentディレクティブの中にあります。一方、ADD2ボタンはion-header-barディレクティブの中にあるためng-modelとは別です。スコープかバインディングの動作を勉強していないのではっきりは言えませんが、同じPlayersControllerコントローラ内であってもディレクティブ間はng-modelのデータは共有できないように設計されていると見えます。モジュール設計の思想的には当然といえば当然で。

見つけた一つの解法が以下でした。というかたぶん何の変哲もない普通の使い方なのだと思いますが。。。いかんせん勉強中なのでオーダー感がわかりません。

コードは以下です。またこちらで実行できます。変更はわずかです。htmlはcreatePlayer()の引数を削除しています。app.jsでは追加または変更箇所にコメントを入れています。参考にしたのはこちらの記事です。

  • html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
    <link href="http://code.ionicframework.com/1.0.0/css/ionic.min.css" rel="stylesheet">
    <script src="http://code.ionicframework.com/1.0.0/js/ionic.bundle.js"></script>
  </head>
    <body ng-app="app">

    <ion-pane>
      <ion-nav-bar class="bar-stable"></ion-nav-bar>
      <ion-nav-view></ion-nav-view>
    </ion-pane>
    
    <script id="templates/players.html" type="text/ng-template">
    <ion-view title="Players">
  	  <ion-nav-buttons side="right">
        <button class="right button button-icon icon ion-plus" ng-click="openModal()"></button>
      </ion-nav-buttons>
      <ion-content>
        <ion-list>
          <ion-item ng-repeat="player in players">
            {{player.player_name}}
          </ion-item>
        </ion-list>
      </ion-content>
    </ion-view>
    </script>
    
    <script id="templates/playerInputForm.html" type="text/ng-template">
      <ion-modal-view>
    		<ion-header-bar>
    			<a class="button" ng-click="closeModal()">Cancel</a>
    			<button ng-click="createPlayer()">Add2</button>
    			<h1 class="title">Player Information</h1>
    		</ion-header-bar>
    		<ion-content>
    			<div class="row">
    				<div class="col">
        			<button ng-click="createPlayer()">Add1</button>
    					<label class="item item-input large">
    						<input type="text" placeholder="Player Name" ng-model="newPlayer.player_name">
    					</label>
    				</div>
    			</div>
    		</ion-content>
      </ion-modal-view>
    </script>
  </body>
</html>
  • app.js
var app = angular.module('app', ['ionic']);

app.config(function($stateProvider, $urlRouterProvider) {
  $stateProvider
    .state('players', {
      url: '/players',
      templateUrl: 'templates/players.html',
      controller: 'PlayersController'
    });
  $urlRouterProvider.otherwise('/players');
});

app.factory('newPlayer', function() { /* 追加 */
  return {
    player_name: ''
  };
});

app.controller("PlayersController", function($scope, $ionicModal, newPlayer /* 追加 */) {
    $scope.players = [];
    $scope.newPlayer = newPlayer;  /* 追加 */

    $scope.createPlayer = function() {
        $scope.players.push({player_name: $scope.newPlayer.player_name  /* 変更 */})
        $scope.modal.hide();
    }
    $ionicModal.fromTemplateUrl('templates/playerInputForm.html', {
      scope: $scope,
      animation: 'slide-in-up'
    }).then(function(modal) {
      $scope.modal = modal;
    });
    $scope.openModal = function(){
        $scope.modal.show();
    }
    $scope.closeModal = function(){
        $scope.modal.hide();
    }
});

言葉の理解が曖昧なので、かなり怪しいですが、とりあえず説明してみます。ディレクティブ間でデータを共有するためにfactoryを宣言します。factoryが何者か。。。わかりません。第一引数で与えた文字列をオブジェクト名として第二引数の実行結果を受け取れるようです。この場合はnewPlayerというオブジェクトを参照するとその中にplayer_nameというプロパティが定義されていてその値を出し入れできる、そんなイメージです。ここではng-modelにfactoryのnewPlayerを指定することでモーダルウィンドウで入力された値を一時的に保存し、ボタンを押下したときに呼び出されるcreatePlayer内でfactoryで定義したnewPlayerオブジェクトを参照できるようにした。。。という感じです。まどろっこしい説明です。

ただ。。。この解法ってなんか間違っているような気もします。

モーダルウィンドウで入力した値をコントローラ内の関数で参照したいがためにfactoryをいちいち定義していたら。。。factoryだらけになってしまうような気がしてなりません。

やはりAngularJSをある程度しっかり勉強しないとな。。。と思いました。

急がば回れです>私