Charlie Stanard Charlie Stanard - 2 months ago 55
Javascript Question

Angular view not updating on scope change after form submit in ngDialog

I'm new to Angular and I'm attempting to build a robust todo app for learning purposes. There's probably something basic I'm doing wrong, but can't figure it out. I assume it's something with the way I have my controllers and templates configured. I've tried some of the recommendations found in similar questions but none that solve my issue. I've tried using

$scope.$apply()
but I get the "apply/digest already in progress" error.

I'm using ngRoute so when someone goes to
/profile
it calls the
profileController
and loads in
templates/profile.html
. This controller doesn't really do anything other than setup the current user and attach to
$rootScope
. Inside of the profile view I have a nested controller called
listController
. I'm passing in the
List
resource to
listController
to make http requests. Everything is working correctly as far as routing and the get/post requests.

Here's a simplified example of my controller:

var myApp = angular.module('todo', ['ngRoute', 'ngResource'])
// config stuff...
.controller('listController', ['$scope', 'List', 'ngDialog', function ($scope, List, ngDialog) {
// This correctly returns all lists for the current user
$scope.lists = List.query();

// Open modal form function
$scope.openModal = function () {
ngDialog.open({
template: 'templates/partials/new-list.html',
className: 'ngdialog-theme-default'
});
};

$scope.createList = function () {
List.save({}, {
name: $scope.list.name,
description: $scope.list.description
}).$promise.then(function(result) {
$scope.lists.push(result);
});
};
}]);


Here's the relevant part of
templates/profile.html
which correctly displays all lists retrieved from
List.query()
:

<div class="todo-sidebar" ng-controller="listController">
<h3>Your lists <a href="#" ng-click="openModal()"><span>New list</span></a></h3>
<ul>
<li ng-repeat="list in lists">
<a href="#"><span class="list-name">{{list.name}}</span></a>
</li>
</ul>
</div>


The problem is when I call the
createList
function from inside my
templates/partials/new-list.html
partial:

<div class="dialog-contents" ng-controller="listController">
<h3>New list</h3>
<form ng-submit="createList()">
<div class="form-group">
<label>Name</label>
<input type="text" name="name" ng-model="list.name">
<textarea name="description" ng-model="list.description" rows="10"></textarea>
<button type="submit" class="button">Create list</button>
</div>
</form>
</div>


The form posts correctly and I can see the new list in my database, but the view does not update in real-time. When I
console.log($scope)
inside the promise it appears that my new list has been added to the scope.

I have a feeling it's not best practice to attach a controller to multiple elements/templates, but I'm not sure how else to structure this.

Answer

Okay so after a little research I think I have a solution for you. So what is happening is that you're not resolving the close of the dialog box correctly.

For reference here is the link I used:

https://github.com/likeastore/ngDialog#api

Scope

What you're dealing with here is a scope issue. When the dialog box is opened up, it is generating a separate module with it's own controller. This controller's scope is a one-way binding, and will not be seen by the parent scope upon resolution. So this is the why to the reason that your scope is not updating on the parent scope level.

For the how, please see the next section regarding promises.

Promises

To manage the scope at the parent level you're going to need something called a promise function, or in simpler terms a function that happens after the close of the dialog resolves fully. When dealing with the promise function there will be some kind of initial callback that allows you to pass data to the promise, and when you initialize the promise this is when you'll be able to access that data, which will then cooperate with the parent-level scope variables.

For this solution I have provided you a bit of updated code that might be helpful for you.

updated Code

If I'm understanding how your code works along with how the module works your updated functions should look something like the following. I would give this a shot:

var myApp = angular.module('todo', ['ngRoute', 'ngResource'])
// config stuff...
.controller('listController', ['$scope', 'List', 'ngDialog', function     ($scope, List, ngDialog) { 
    // This correctly returns all lists for the current user
    $scope.lists = List.query();

    // Open modal form function
    $scope.openModal = function () {
        var dialog = ngDialog.open({
            template: 'templates/partials/new-list.html',
            className: 'ngdialog-theme-default',
            controller : function($scope){  
                $scope.createList = function(){
                    dialog.close($scope.list); // where data is the list data
                };
            }
        });
        dialog.closePromise.then(function(data){
            // at this point data is your list and then you can update the value as you see with like normal
            List.save({}, {
               name: $scope.list.name,
                description: $scope.list.description
            }).$promise.then(function(result) {
               $scope.lists.push(result);
            });
        });
    };
}]);

Again this is just a rough template for what you'd need to do, but this is how you would implement it nonetheless.

I hope this helps, please ask if you have any more questions.