Search code examples
javascriptangularjsoopangularjs-scopeangularjs-http

View No Longer Updating Immediately in Efforts to Clean Up Scope Soup


I've fallen victim to self-imposed $scope soup and am taking steps to correct this. I'm adopting some of the methods written about by Josh Carroll in his article here:
http://www.technofattie.com/2014/03/21/five-guidelines-for-avoiding-scope-soup-in-angular.html.

More information on this approach can be found here as well:
https://johnpapa.net/do-you-like-your-angular-controllers-with-or-without-sugar/

This approach has already been a breath of fresh air with its more object oriented style and brings with it a much cleaner and far more manageable approach.

However, I am hitting a wall with one specific issue: the view is not updating immediately as it once was. I suspect it has something to do with what _this is referencing: $scope vs controller vs window.

When ctrl.addTask() is called, a new task record is successfully inserted into the DB. However, the new task does not immediately appear in the view via ng-repeat (being called via a new getTasks() call) and only shows up after a manual page refresh. My confusion is furthered by having placed another button on the page, just for testing purposes, that specifically calls getTasks(), and it updates the view immediately with the new task every time.

index.html:

<section ng-controller="TaskCtrl as ctrl">
    <div class="container">
        <form name="addTaskForm">
            <div class="form-group">
                <input type="text" class="form-control" name="taskName" ng-model="ctrl.addTaskData.taskName" placeholder="New Task Name" ng-focus="addNewClicked">
                <div class="form-group-btn">
                    <button class="btn btn-default" type="submit" ng-click="ctrl.addTask();"><i class="glyphicon glyphicon-plus"></i>&nbsp;Add New Task</button>
                </div>
        </form>
    </div>
</section>
<section ng-controller="TaskCtrl as ctrl">
    <div class="row" ng-repeat="task in ctrl.tasksData" repeatdirective>
        <div class="col-xs-12 vcenter">
            <a href="/task/{{ task.id }}">{{ task.name || "Error - Task Names Should Not be Empty" }}</a>
        </div>
    </div>
</section>

app.js

var app = angular.module('tasksApp', []);

var TasksCtrl = function($filter, $scope, $window, $http, $location) {

    var _this = this;

    _this.getTasks = function() {
        var request = $http({
            method      : 'POST',
            url         : '/handler.php',
            data        : {
                'action' : 'getAllTasks'
            },
            headers : {
                'Content-Type': 'application/x-www-form-urlencoded'
            },
            responseType: 'json'
        }).then(function successCallback(response) {
            _this.tasksData = response.data;
            console.log( "getTasks() successCallback" );
        }, function errorCallback(response) {
            console.log( "getTasks() errorCallback" );
        });
    };
    _this.getTasks();

    _this.addTaskData = {};
    _this.addTask = function() {
        var request = $http({
            method      : 'POST',
            url         : '/handler.php',
            data        : {
                'action'                : 'addTask',
                'taskName'              : _this.addTaskData.taskName,
                'startDate'             : '',
                'endDate'               : ''
            },
            headers : {
                'Content-Type': 'application/x-www-form-urlencoded' 
            },
            responseType: 'json'
        }).then(function successCallback(response) {
            console.log("addTask() successCallback");
            _this.getTasks();
            _this.addTaskForm.$setPristine();
            _this.addTaskData = {};
        }, function errorCallback(response) {
            console.log("addTask() errorCallback");
        });         
    };
}

TaskCtrl.$inject = ['$filter', '$scope', '$window', '$http', '$location'];
app.controller('TaskCtrl', TaskCtrl);

I'm aware that it's recommended to remove the http to its own service that is tied back in to the controller. This is one of my next steps in my efforts to further clean up my $scope soup.

Attempted remedies to resolve issue(s):

  • Using the .bind() method to "pass" value of _this to still reference the controller
  • Using a closure created by the function call, so "_this" still references the controller
  • Using $scope.$apply() — every attempt I made to integrate this always returned error that $apply() was undefined.

Please note it's entirely possible that any of the three approaches listed above may not have been implemented properly. Please also note that much of the code has been trimmed to focus in on the specific issue.

Any and all guidance or feedback is greatly appreciated!


Solution

  • Check your html - you have 2 <section> tags with 2 ng-controller=TaskCtrl as ctrl so you have 2 instances of TaskCtrl. First that is handling adding new task, and another that is responsible for printing them.

    When you call addtask, getTask is called from this first controller and in this first controller taskData is updated.

    When you will have only one ng-controller it will work.

    <section ng-controller="TaskCtrl as ctrl">
        <div class="container">
            <form name="addTaskForm">
                <div class="form-group">
                    <input type="text" class="form-control" name="taskName" ng-model="ctrl.addTaskData.taskName" placeholder="New Task Name" ng-focus="addNewClicked">
                    <div class="form-group-btn">
                        <button class="btn btn-default" type="submit" ng-click="ctrl.addTask();"><i class="glyphicon glyphicon-plus"></i>&nbsp;Add New Task</button>
                    </div>
            </form>
        </div>
        <div class="row" ng-repeat="task in ctrl.tasksData" repeatdirective>
            <div class="col-xs-12 vcenter">
                <a href="/task/{{ task.id }}">{{ task.name || "Error - Task Names Should Not be Empty" }}</a>
            </div>
        </div>
    </section>
    

    You can also use a service to store the data and inject it to both controllers so data will be stored only in this service and can be shared.