angular.js/docs/content/cookbook/mvc.ngdoc

125 lines
4.3 KiB
Text
Raw Normal View History

2011-02-01 18:01:02 +00:00
@ngdoc overview
@name Cookbook: MVC
@description
2011-02-03 01:46:01 +00:00
2011-02-03 23:21:34 +00:00
MVC allows for a clean an 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.
2011-02-03 01:46:01 +00:00
2011-02-03 23:21:34 +00:00
The model is simply the controller's this. 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.
2011-02-03 01:46:01 +00:00
<doc:example>
2011-06-07 21:13:44 +00:00
<doc:source>
2011-02-03 01:46:01 +00:00
<script>
2011-06-06 15:50:35 +00:00
function TicTacToeCntl($location){
this.$location = $location;
2011-02-03 01:46:01 +00:00
this.cellStyle= {
'height': '20px',
'width': '20px',
'border': '1px solid black',
'text-align': 'center',
'vertical-align': 'middle',
'cursor': 'pointer'
};
this.reset();
this.$watch('$location.search().board', this.readUrl);
2011-02-03 01:46:01 +00:00
}
TicTacToeCntl.prototype = {
dropPiece: function(row, col) {
2011-06-07 21:13:44 +00:00
if (!this.winner && !this.board[row][col]) {
this.board[row][col] = this.nextMove;
2011-02-03 01:46:01 +00:00
this.nextMove = this.nextMove == 'X' ? 'O' : 'X';
this.setUrl();
}
},
reset: function() {
2011-02-03 01:46:01 +00:00
this.board = [
['', '', ''],
['', '', ''],
['', '', '']
];
this.nextMove = 'X';
this.winner = '';
this.setUrl();
},
grade: function() {
2011-06-07 21:13:44 +00:00
var b = this.board;
this.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 : '';};
2011-02-03 01:46:01 +00:00
},
setUrl: function() {
2011-02-03 01:46:01 +00:00
var rows = [];
2011-02-03 23:21:34 +00:00
angular.forEach(this.board, function(row){
2011-02-03 01:46:01 +00:00
rows.push(row.join(','));
});
this.$location.search({board: rows.join(';') + '/' + this.nextMove});
2011-02-03 01:46:01 +00:00
},
readUrl: function(scope, value) {
2011-02-03 01:46:01 +00:00
if (value) {
value = value.split('/');
2011-06-07 21:13:44 +00:00
this.nextMove = value[1];
angular.forEach(value[0].split(';'), function(row, col){
this.board[col] = row.split(',');
2011-02-03 01:46:01 +00:00
}, this);
this.grade();
}
}
};
</script>
2011-06-07 21:13:44 +00:00
2011-02-03 01:46:01 +00:00
<h3>Tic-Tac-Toe</h3>
<div ng:controller="TicTacToeCntl">
Next Player: {{nextMove}}
2011-02-03 23:21:34 +00:00
<div class="winner" ng:show="winner">Player {{winner}} has won!</div>
2011-06-07 21:13:44 +00:00
<table class="board">
<tr ng:repeat="row in board" style="height:15px;">
<td ng:repeat="cell in row" ng:style="cellStyle"
ng:click="dropPiece($parent.$index, $index)">{{cell}}</td>
</tr>
</table>
<button ng:click="reset()">reset board</button>
2011-02-03 01:46:01 +00:00
</div>
2011-06-07 21:13:44 +00:00
</doc:source>
<doc:scenario>
it('should play a game', function() {
2011-06-07 21:13:44 +00:00
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();
}
</doc:scenario>
2011-02-03 01:46:01 +00:00
</doc:example>
2011-02-03 23:21:34 +00:00
# Things to notice
2011-02-03 01:46:01 +00:00
2011-02-03 23:21:34 +00:00
* The controller is defined in JavaScript and has no reference to the rendering logic.
* The controller is instantiated by <angular/> and injected into the view.
* The controller can be instantiated in isolation (without a view) and the code will still execute.
2011-06-07 21:13:44 +00:00
This makes it very testable.
2011-02-03 23:21:34 +00:00
* The HTML view is a projection of the model. In the above example, the model is stored in the
2011-06-07 21:13:44 +00:00
board variable.
2011-02-03 23:21:34 +00:00
* All of the controller's properties (such as board and nextMove) are available to the view.
* Changing the model changes the view.
* The view can call any controller function.
* In this example, the `setUrl()` and `readUrl()` functions copy the game state to/from the URL's
2011-06-07 21:13:44 +00:00
hash so the browser's back button will undo game steps. See deep-linking. This example calls {@link
api/angular.module.ng.$rootScope.Scope#$watch $watch()} to set up a listener that invokes `readUrl()` when needed.