I am creating a trivia game (purely for learning purposes) using AngularJS, NodeJS and MongoDB. My concern is related to the way I am organising the code in AngularJS. So far the game is functional, everything is working fine, but I know I am not doing it right. Directives should handle the DOM manipulation, but am doing everything in the controller.
My questions are:
- What exactly should I keep in the controller and what should I move to the directive?
- Is it OK if I inject my service into the directive so I can do the GET and POST from there?
This is just a summary of how the game works:
- when you start the game,
start.html
partial is loaded - you type in your name, choose the number of questions for that round and click START
- START takes you to main.html partial and calls
$scope.startGame()
defined in the controller - the first question is loaded and the question content and buttons are reset
- you choose an answer, the answer is validated, reset is applied again
- this continues until you run out of questions when you are taken to
endgame.html
partial - your current score is sent back to the database, your total all time score is retrieved and displayed; also, a list of top users is displayed.
These are the relevant files mentioned above:
index.html
//...
<div ng-controller="mainController">
//everything happens in this ng-view
<div ng-view></div>
</div>
start.html
<div class="text-center">
<div class="row">
<div> Enter your username: </div>
<input ng-model="player.name" />
</div>
<p>Number of questions: </p>
<div class="btn-group">
<button type="button" class="btn btn-default" ng-click="setNumberOfQuestions(5)">5</button>
<button type="button" class="btn btn-default" ng-click="setNumberOfQuestions(10)">10</button>
<button type="button" class="btn btn-default" ng-click="setNumberOfQuestions(15)">15</button>
</div>
<div>
<a href="#/main" class="btn btn-info btn-lg" ng-click='startGame()' ng-disabled="!numberOfQuestions">START</a>
</div>
</div>
mainDirective.html this is the directive template
<div class="text-center">
Question {{currentQuestionNo}} of {{numberOfQuestions}}
</div>
<div class="row biggerHeight vertical-align">
<div class="col-xs-8 col-xs-offset-2 text-center">
{{data.q}}</div>
</div>
<div class="row smallerHeight autoMargin">
<div class="col-sm-6 ">
<div ng-class="elementInfo.answerClass[0]" ng-click="validateAnswer(0)">
{{data.answers[0]}}</div>
</div>
<div class="col-sm-6 ">
<div ng-class="elementInfo.answerClass[1]" ng-click="validateAnswer(1)">
{{data.answers[1]}}</div>
</div>
<div class="col-sm-6 ">
<div ng-class="elementInfo.answerClass[2]" ng-click="validateAnswer(2)">
{{data.answers[2]}}</div>
</div>
<div class="col-sm-6 ">
<div ng-class="elementInfo.answerClass[3]" ng-click="validateAnswer(3)">
{{data.answers[3]}}</div>
</div>
</div>
<div class="row timer text-center" ng-show="showTimer">
{{timer}}
</div>
<div id="progressTimer"></div>
<div class="col-sm-6 col-sm-offset-3 text-center">
Score: {{currentScore}}
</div>
endgame.html
<div class="row">
<div class="col-sm-6 col-sm-offset-3 text-center">Congratulations, {{player.name}}!</div>
</div>
<div class="row">
<div class="col-sm-6 col-sm-offset-3 text-center"> You have aquired {{currentScore}} points!</div>
</div>
<div class="row text-center">
Your total score is: {{totalScore}}
</div>
<div class="row text-center">
<div><a href="#/main" class="btn btn-info btn-lg" ng-click='startGame()' ng-disabled="!numberOfQuestions">RESTART</a></div>
</div>
<div class="row text-center">
<table st-table="highScore" class="table table-striped">
<thead>
<tr class="text-center">
<th class="text-center">Name</th>
<th class="text-center">Score</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in highScore">
<td class="text-center">{{row.name}}</td>
<td class="text-center">{{row.totalScore}}</td>
</tr>
</tbody>
</table>
</div>
mainController.js
myApp.controller('mainController', ['$scope', 'DataService', 'Shuffle', '$timeout', '$interval', '$location', '$http', function ($scope, DataService, Shuffle, $timeout, $interval, $location, $http) {
$scope.player = {};
//getScore is used for retrieving a user's total score from all game plays
$scope.getScore = function() {
DataService.getUserScore($scope.player.name).then(function(result){
$scope.totalScore = result.data.totalScore;
});
}
//getHighScore retrieves the top X players from the database
$scope.getHighScore = function() {
DataService.getHighScore().then(function(result){
$scope.highScore = result.data;
});
}
//postScore sends the user's score to the database at the end of the round
$scope.postScore = function(name, score) {
DataService.postScore(name, score).then(function(result){
$scope.getScore();
});
}
//resets the question (such as question content, answers, colors, timer)
function resetQuestion() {
var correctAnswer,
myArray,
answerClass = " answerBtn btn btn-primary";
$scope.data = {};
$scope.elementInfo = {
answerClass: [answerClass, answerClass, answerClass, answerClass]
};
$scope.timer = 5;
$scope.seconds = 5;
}
//gets the questions from the database
function getQuestion() {
$scope.showTimer = true;
resetQuestion();
$scope.currentQuestionNo++;
//HTTP GET
DataService.getQuestion().then(function (result) {
//result contains the question obj {question, answer and three wrong choices}
var question = result.data;
//$scope.data.q holds the question for e.g: "what is the capital of France?"
$scope.data.q = question.q;
correctAnswer = question.a;
myArray = [question.a, question.v1, question.v2, question.v3];
myArray = Shuffle.randomizeArray(myArray);
$scope.data.answers = myArray;
});
//this is the countdown timer; it starts when the questions is loaded; it stops when the time runs out or when you pick an answer
myTimer = $interval(function () {
if ($scope.timer > 0) {
$scope.timer--;
} else {
$scope.validateAnswer(4);
}
}, 500);
};
//it validates your answer, makes your choice orange, makes the right one green, makes the wrong one red.
//the timer is cancelled when this function is called
$scope.validateAnswer = function (ans) {
$scope.showTimer = false;
$interval.cancel(myTimer);
$scope.elementInfo.answerClass[ans] += " btn-warning";
for (var i = 0; i < 4; i++) {
$scope.elementInfo.answerClass[i] += " disabled";
}
var timer = $timeout(function () {
for (var i = 0; i < 4; i++) {
if (ans == i) {
if ($scope.data.answers[ans] == correctAnswer) {
$scope.elementInfo.answerClass[ans] = " answerBtn btn btn-primary btn-success disabled ";
$scope.currentScore += 10;
} else {
$scope.elementInfo.answerClass[ans] += " btn-danger";
}
} else if ($scope.data.answers[i] == correctAnswer) {
$scope.elementInfo.answerClass[i] += " btn-success";
}
}
}, 500);
//checks if there are questions of left for e.g: question 1 of 5, 2 of 5, etc.
if ($scope.currentQuestionNo < $scope.numberOfQuestions) {
var timer2 = $timeout(function () {
getQuestion();
}, 500);
} else {
//if no questions left, then the game ends
$timeout(function(){
$location.path('/endgame');
$scope.postScore($scope.player.name, $scope.currentScore);
$scope.getHighScore();
}, 500);
}
};
//at the beginning of the game you pick how many questions you want the round to have
$scope.setNumberOfQuestions = function (no) {
$scope.numberOfQuestions = no;
}
//this gets fired when you press the START button
$scope.startGame = function () {
var myTimer;
$scope.currentScore = 0;
$scope.currentQuestionNo = 0;
getQuestion();
}
} ]);
mainDirective.js
myApp.directive('mainDirective', ['$interval', '$timeout', 'DataService', 'Shuffle', function ($interval, DataService, Shuffle) {
return {
restrict: 'EA',
templateUrl: 'pages/mainDirective.html',
link: function (scope, element, attrib) {
}
}
} ]);