136 lines
5.7 KiB
HTML
Executable file
136 lines
5.7 KiB
HTML
Executable file
<a href="http://github.com/angular/angular.js/edit/master/docs/content/cookbook/mvc.ngdoc" class="improve-docs btn btn-primary"><i class="icon-edit"> </i> Improve this doc</a><h1><code ng:non-bindable=""></code>
|
|
<div><span class="hint"></span>
|
|
</div>
|
|
</h1>
|
|
<div><div class="cookbook-page cookbook-mvc-page"><p>MVC allows for a clean and testable separation between the behavior (controller) and the view
|
|
(HTML template). A Controller is just a JavaScript class which is grafted onto the scope of the
|
|
view. This makes it very easy for the controller and the view to share the model.</p>
|
|
<p>The model is a set of objects and primitives that are referenced from the Scope ($scope) object.
|
|
This makes it very easy to test the controller in isolation since one can simply instantiate the
|
|
controller and test without a view, because there is no connection between the controller and the
|
|
view.</p>
|
|
<h3>Source</h2>
|
|
<div source-edit="" source-edit-deps="angular.js script.js" source-edit-html="index.html-177" source-edit-css="" source-edit-js="script.js-176" source-edit-json="" source-edit-unit="" source-edit-scenario="scenario.js-178"></div>
|
|
<div class="tabbable"><div class="tab-pane" title="index.html">
|
|
<pre class="prettyprint linenums" ng-set-text="index.html-177" ng-html-wrap=" angular.js script.js"></pre>
|
|
<script type="text/ng-template" id="index.html-177">
|
|
|
|
|
|
<h4>Tic-Tac-Toe</h4>
|
|
<div ng-controller="TicTacToeCntl">
|
|
Next Player: {{nextMove}}
|
|
<div class="winner" ng-show="winner">Player {{winner}} has won!</div>
|
|
<table class="board">
|
|
<tr ng-repeat="row in board track by $index" style="height:15px;">
|
|
<td ng-repeat="cell in row track by $index" ng-style="cellStyle"
|
|
ng-click="dropPiece($parent.$index, $index)">{{cell}}</td>
|
|
</tr>
|
|
</table>
|
|
<button ng-click="reset()">reset board</button>
|
|
</div>
|
|
</script>
|
|
</div>
|
|
<div class="tab-pane" title="script.js">
|
|
<pre class="prettyprint linenums" ng-set-text="script.js-176"></pre>
|
|
<script type="text/ng-template" id="script.js-176">
|
|
function TicTacToeCntl($scope, $location) {
|
|
$scope.cellStyle= {
|
|
'height': '20px',
|
|
'width': '20px',
|
|
'border': '1px solid black',
|
|
'text-align': 'center',
|
|
'vertical-align': 'middle',
|
|
'cursor': 'pointer'
|
|
};
|
|
|
|
$scope.reset = function() {
|
|
$scope.board = [
|
|
['', '', ''],
|
|
['', '', ''],
|
|
['', '', '']
|
|
];
|
|
$scope.nextMove = 'X';
|
|
$scope.winner = '';
|
|
setUrl();
|
|
};
|
|
|
|
$scope.dropPiece = function(row, col) {
|
|
if (!$scope.winner && !$scope.board[row][col]) {
|
|
$scope.board[row][col] = $scope.nextMove;
|
|
$scope.nextMove = $scope.nextMove == 'X' ? 'O' : 'X';
|
|
setUrl();
|
|
}
|
|
};
|
|
|
|
$scope.reset();
|
|
$scope.$watch(function() { return $location.search().board;}, readUrl);
|
|
|
|
function setUrl() {
|
|
var rows = [];
|
|
angular.forEach($scope.board, function(row) {
|
|
rows.push(row.join(','));
|
|
});
|
|
$location.search({board: rows.join(';') + '/' + $scope.nextMove});
|
|
}
|
|
|
|
function grade() {
|
|
var b = $scope.board;
|
|
$scope.winner =
|
|
row(0) || row(1) || row(2) ||
|
|
col(0) || col(1) || col(2) ||
|
|
diagonal(-1) || diagonal(1);
|
|
function row(row) { return same(b[row][0], b[row][1], b[row][2]);}
|
|
function col(col) { return same(b[0][col], b[1][col], b[2][col]);}
|
|
function diagonal(i) { return same(b[0][1-i], b[1][1], b[2][1+i]);}
|
|
function same(a, b, c) { return (a==b && b==c) ? a : '';};
|
|
}
|
|
|
|
function readUrl(value) {
|
|
if (value) {
|
|
value = value.split('/');
|
|
$scope.nextMove = value[1];
|
|
angular.forEach(value[0].split(';'), function(row, col){
|
|
$scope.board[col] = row.split(',');
|
|
});
|
|
grade();
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
</div>
|
|
<div class="tab-pane" title="End to end test">
|
|
<pre class="prettyprint linenums" ng-set-text="scenario.js-178"></pre>
|
|
<script type="text/ng-template" id="scenario.js-178">
|
|
it('should play a game', function() {
|
|
piece(1, 1);
|
|
expect(binding('nextMove')).toEqual('O');
|
|
piece(3, 1);
|
|
expect(binding('nextMove')).toEqual('X');
|
|
piece(1, 2);
|
|
piece(3, 2);
|
|
piece(1, 3);
|
|
expect(element('.winner').text()).toEqual('Player X has won!');
|
|
});
|
|
|
|
function piece(row, col) {
|
|
element('.board tr:nth-child('+row+') td:nth-child('+col+')').click();
|
|
}
|
|
</script>
|
|
</div>
|
|
</div><h2>Demo</h3>
|
|
<div class="well doc-example-live animate-container" ng-embed-app="" ng-set-html="index.html-177" ng-eval-javascript="script.js-176"></div>
|
|
<h2>Things to notice</h2>
|
|
<ul>
|
|
<li>The controller is defined in JavaScript and has no reference to the rendering logic.</li>
|
|
<li>The controller is instantiated by Angular and injected into the view.</li>
|
|
<li>The controller can be instantiated in isolation (without a view) and the code will still execute.
|
|
This makes it very testable.</li>
|
|
<li>The HTML view is a projection of the model. In the above example, the model is stored in the
|
|
board variable.</li>
|
|
<li>All of the controller's properties (such as board and nextMove) are available to the view.</li>
|
|
<li>Changing the model changes the view.</li>
|
|
<li>The view can call any controller function.</li>
|
|
<li>In this example, the <code>setUrl()</code> and <code>readUrl()</code> functions copy the game state to/from the URL's
|
|
hash so the browser's back button will undo game steps. See deep-linking. This example calls <a href="api/ng.$rootScope.Scope#$watch"><code>$watch()</code></a> to set up a listener that invokes <code>readUrl()</code> when needed.</li>
|
|
</ul>
|
|
</div></div>
|