0

I have a problem with an AngularJS app I'm making. It shows a list of contacts, and for each contact there is a button whereupon clicking the button a modal pops up with a form. This form should show the existing contact information, and if you want to change something you type the new information and press submit.

The problem, however, is that the existing information is not shown in the form, hence editing it doesn't work. I imagine that the issue is that the modal does not inherit the scope from the parent page, but I don't know what to do in order to fix that. I have tried playing around with the attributes on the input fields (for example prepending ng-model by $parent. and defining an ng-init value), but to no avail, so I hope some of the experts here will be able to point me on the right track.

Thank you in advance.

Now let me show you my code, so you can see the context I'm talking about. Here is the html that displays the list of contacts:

<div class="panel panel-default" ng-controller="contactsController">
<div class="panel-body">
    <div id="gridContainer" ng-class="{'': state == 'list', 'none': state != 'list'}">
        <table class="table table-bordered table-striped">
            <thead>
            <tr>
                <th scope="col"><spring:message code="contacts.name"/></th>
                <th scope="col"><spring:message code="contacts.email"/></th>
                <th scope="col"><spring:message code="contacts.phone"/></th>
                <th scope="col"></th>
            </tr>
            </thead>
            <tbody>
            <tr ng-repeat="contact in page.source">
                <td class="tdContactsCentered">{{contact.name}}</td>
                <td class="tdContactsCentered">{{contact.email}}</td>
                <td class="tdContactsCentered">{{contact.phoneNumber}}</td>
                <td class="width15">
                    <div class="text-center">
                        <input type="hidden" value="{{contact.id}}"/>
                        <a ng-href="#updateContactsModal"
                           ng-click="selectedContact(contact);"
                           role="button"
                           title="<spring:message code="update"/>&nbsp;<spring:message code="contact"/>"
                           class="btn btn-sm btn-warning" data-toggle="modal">
                            <i class="icon-pencil"></i>
                        </a>
                        <a ng-href="#deleteContactsModal"
                           ng-click="selectedContact(contact);"
                           role="button"
                           title="<spring:message code="delete"/>&nbsp;<spring:message code="contact"/>"
                           class="btn btn-sm btn-danger" data-toggle="modal">
                            <em class="fa fa-trash"></em>
                        </a>
                    </div>
                </td>
            </tr>
            </tbody>
        </table>
    </div>
</div>

And the html that defines the modal and the form:

<div id="updateContactsModal"
 class="modal fade centering"
 role="dialog"
 aria-labelledby="updateContactsModalLabel"
 aria-hidden="true" style="display: none;">
<div class="modal-dialog" role="document">
    <div class="modal-content">
<div class="modal-header">
    <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
    <h3 id="updateContactsModalLabel" class="modal-title">
        <spring:message code="update"/>&nbsp;<spring:message code="contact"/>
    </h3>
</div>
<div class="modal-body" data-ng-controller="contactsController">
    <form name="updateContactForm" novalidate>
        <input type="hidden"
               required
             data-ng-model="contact.id"
               name="id"
               value="{{contact.id}}"/>
        <div>
            <div class="form-group">
                    <label>* <spring:message code="contacts.name"/>:</label>
                    <input type="text"
                           autofocus
                           required
                           class="form-control"
                           data-ng-model="contact.name"
                           name="name"
                           placeholder="<spring:message code='contact'/>&nbsp;<spring:message code='contacts.name'/> "/>
                    <div>
                            <span class="alert alert-error"
                                  ng-show="displayValidationError && updateContactForm.name.$error.required">
                                <spring:message code="required"/>
                            </span>
                    </div>
            </div>
            <div class="form-group">
                    <label>* <spring:message code="contacts.email"/>:</label>
                <div class="input-append">
                    <input type="text"
                           required
                           class="form-control"
                           ng-model="contact.email"
                           name="email"
                           placeholder="<spring:message code='sample.email'/> "/>
                </div>
                    <div>
                            <span class="alert alert-error"
                                  ng-show="displayValidationError && updateContactForm.email.$error.required">
                                <spring:message code="required"/>
                            </span>
                    </div>
            </div>
            <div class="form-group">
                    <label>* <spring:message code="contacts.phone"/>:</label>
                <div class="input-append">
                    <input type="text"
                           required
                           class="form-control"
                           ng-model="contact.phoneNumber"
                           name="phoneNumber"
                           placeholder="<spring:message code='sample.phone'/> "/>
                </div>
                    <div>
                            <span class="alert alert-error"
                                  ng-show="displayValidationError && updateContactForm.phoneNumber.$error.required">
                                <spring:message code="required"/>
                            </span>
                    </div>
            </div>

        </div>
    </form>
    <div class="modal-footer">
        <input type="submit"
               class="btn btn-primary"
               ng-click="updateContact(updateContactForm);"
               value='<spring:message code="update"/>'/>
        <button class="btn btn-default"
                data-dismiss="modal"
                ng-click="exit('#updateContactsModal');"
                aria-hidden="true">
            <spring:message code="cancel"/></button>
    </div>
</div>

<span class="alert alert-error dialogErrorMessage"
      ng-show="errorOnSubmit">
    <spring:message code="request.error"/>
</span>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->

And finally the relevant parts of the controller:

App.controller('contactsController', ["$scope", "$http", function($scope,$http) {

$scope.pageToGet = 0;

$scope.state = 'busy';

$scope.lastAction = '';

$scope.url = "/uaiContacts/protected/contacts/";

$scope.errorOnSubmit = true;
$scope.errorIllegalAccess = false;
$scope.displayMessageToUser = false;
$scope.displayValidationError = false;
$scope.displaySearchMessage = false;
$scope.displaySearchButton = false;
$scope.displayCreateContactButton = false;

$scope.contact = {};

$scope.searchFor = "";

$scope.getContactList = function () {
    var url = $scope.url;
    $scope.lastAction = 'list';

    $scope.startDialogAjaxRequest();

    var config = {params: {page: $scope.pageToGet}};

    $http.get(url, config)
        .success(function (data) {
            $scope.finishAjaxCallOnSuccess(data, null, false);
        })
        .error(function () {
            $scope.state = 'error';
            $scope.displayCreateContactButton = false;
        });
};

$scope.populateTable = function (data) {
    if (data.pagesCount > 0) {
        $scope.state = 'list';

        $scope.page = {source: data.contacts, currentPage: $scope.pageToGet, pagesCount: data.pagesCount, totalContacts : data.totalContacts};

        if($scope.page.pagesCount <= $scope.page.currentPage){
            $scope.pageToGet = $scope.page.pagesCount - 1;
            $scope.page.currentPage = $scope.page.pagesCount - 1;
        }

        $scope.displayCreateContactButton = true;
        $scope.displaySearchButton = true;
    } else {
        $scope.state = 'noresult';
        $scope.displayCreateContactButton = true;

        if(!$scope.searchFor){
            $scope.displaySearchButton = false;
        }
    }

    if (data.actionMessage || data.searchMessage) {
        $scope.displayMessageToUser = $scope.lastAction != 'search';

        $scope.page.actionMessage = data.actionMessage;
        $scope.page.searchMessage = data.searchMessage;
    } else {
        $scope.displayMessageToUser = false;
    }
};

$scope.exit = function (modalId) {
    $(modalId).modal('hide');

    $scope.contact = {};
    $scope.errorOnSubmit = false;
    $scope.errorIllegalAccess = false;
    $scope.displayValidationError = false;
};

$scope.finishAjaxCallOnSuccess = function (data, modalId, isPagination) {
    $scope.populateTable(data);
    $("#loadingModal").modal('hide');

    if(!isPagination){
        if(modalId){
            $scope.exit(modalId);
        }
    }

    $scope.lastAction = '';
};

$scope.startDialogAjaxRequest = function () {
    $scope.displayValidationError = false;
    $("#loadingModal").modal('show');
    $scope.previousState = $scope.state;
    $scope.state = 'busy';
};

$scope.handleErrorInDialogs = function (status) {
    $("#loadingModal").modal('hide');
    $scope.state = $scope.previousState;

    // illegal access
    if(status == 403){
        $scope.errorIllegalAccess = true;
        return;
    }

    $scope.errorOnSubmit = true;
    $scope.lastAction = '';
};

$scope.addSearchParametersIfNeeded = function(config, isPagination) {
    if(!config.params){
        config.params = {};
    }

    config.params.page = $scope.pageToGet;

    if($scope.searchFor){
        config.params.searchFor = $scope.searchFor;
    }
};

$scope.selectedContact = function (contact) {
    $scope.contact = angular.copy(contact);
    debugger;
};

$scope.updateContact = function (updateContactForm) {
    if (!updateContactForm.$valid) {
        debugger;
        $scope.displayValidationError = true;
        return;
    }

    $scope.lastAction = 'update';

    var url = $scope.url + $scope.contact.id;

    $scope.startDialogAjaxRequest();

    var config = {};

    $scope.addSearchParametersIfNeeded(config, false);

    $http.put(url, $scope.contact, config)
        .success(function (data) {
            $scope.finishAjaxCallOnSuccess(data, "#updateContactsModal", false);
        })
        .error(function(data, status, headers, config) {
            $scope.handleErrorInDialogs(status);
        });
};

$scope.getContactList();
}]);

1 Answers1

0

The modal doesn't share the same scope as the contacts table because each time Angular finds another ng-controller directive, it creates a new scope.

You're declaring ng-scope in both the contacts table and the modal, which causes angular to create different scopes.

See this answer for more details:
https://stackoverflow.com/a/14462341/4938335

There are a few ways to solve this...

1) Put the modal HTML inside the parent element where you're already declaring ng-controller the first time - that way it will be part of the same scope

2) Use UI Bootstrap's modal directive to generate the modal with its own controller and pass in $scope.contact from your contactsController. See example here
https://angular-ui.github.io/bootstrap/#/modal

3) Create a service that stores $scope.contact and inject that into a separate controller that you create for the modal. Here's someone else's fiddle that shows this:
http://jsfiddle.net/whnSs/

Community
  • 1
  • 1
jordajm
  • 764
  • 5
  • 11
  • Thank you very much for this! It helps greatly. As a quick (and possibly dirty) fix I have assigned `contact` to `$rootScope`, but long term I'd rather implement 2) or 3) from your list. – Sigmund Vestergaard Jul 02 '15 at 15:17