5
\$\begingroup\$

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>

enter image description here

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>

enter image description here

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>

enter image description here

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) {

        }
    }
} ]);
\$\endgroup\$
1
\$\begingroup\$
  • What exactly should I keep in the controller and what should I move to the directive?

Well, your can make every part of your program a directive; StartDirective, MainDirective, EndDirective. That would be my recommendation.

The directives have controllers of their own, so you would still keep your controller functions, but just put them with their corresponding directive.

  • Is it OK if I inject my service into the directive so I can do the GET and POST from there?

No. Inject it into the directive's controller function instead. Avoid using the link function unless you need to do changes to the DOM.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.