0

WARNING: Long description of what i'm actually doing.

I have a Single Page Application with the following layout:

enter image description here

As you can see, it's a layout comprised by tabs and each tab is a directive with it's own controller (and scope). All the form it's a directive itself, comprised by small directives, contained as tabs, as follows:

main.html

<div class="container-fluid">
  <div class="evaCont">
    <div class="eva_title">Form</div>
    <form id="mainForm" ng-submit="submit()">
      <div class="col-xs-12">
        <input type="submit" class="btn btn-primary" value="Save"/>
      </div>

      <div class="tab col-xs-12">
        <tabset justified="true">
          <tab heading="Data section 1">
            <ds1></ds1>
          </tab>
          <tab heading="Data section 2">
            <ds2></ds2>
          </tab>
          <tab heading="...">
            <ds3></ds3>
          </tab>
        </tabset>
      </div>
    </form>
  </div>
</div>

To fill the SPA with data i'm calling a REST API to get it (JSON). I've made a service for that purpose:

'use strict';


angular.module('frontendApp').factory('backendService', function($resource, $location){
    var objBackend = {};

    objBackend.query = function(id) {
        var data = $resource('http://localhost:9010/API/:dataID', {dataID: id}, {
            query: {
                method: 'GET',
                headers: {'token': '1588526404200542348177491835893601182229853'}
            }
        });

        var dataForFrontEnd = data.query().$promise.then(function(d){
            console.log(d);
            return d;
        },
        function(error){
            ...
        });

        return dataForFrontEnd;
    }

    return objBackend;
});

I have a customized version of query method of $resource for manage success and failure of the REST call (and for sending the custom 'token' header). My AngularJS app requires the data of the API and then i set it in the scope of the Main controller. I'm doing that because, if i inject the service and call the query() method Angular will call the API n times for each directive, returning exactlly the same object. Instead of that, i call it only one time in the application load and share the JSON data between all controllers (one controller for each tab).

Main Form Controller:

'use strict';

angular.module('frontendApp')
  .controller('MainCtrl', function ($scope, $routeParams, data, datepickerPopupConfig) {
    $scope.data = data;

    datepickerPopupConfig.showButtonBar = false;

    $scope.submit = function(){
      console.log($scope.data.val1);
    };
});

app.js:

'use strict';

angular
  .module('frontendApp', [
    'ngAnimate',
    'ngAria',
    'ngCookies',
    'ngMessages',
    'ngResource',
    'ngRoute',
    'ngSanitize',
    'ngTouch',
    'ui.bootstrap'
  ])
  .config(function ($routeProvider) {
    $routeProvider
      .when('/API/:dataID', {
        templateUrl: 'main/directives/main.html',
        controller: 'MainCtrl',
        resolve: {
          data: function(backendService, $route){
            var id = $route.current.params.dataID;
            return backendService.query(id);
          }
        }
      })
      .otherwise({
        redirectTo: '/notfound'
      });
  });

Directive 1 (shows how i am binding the data, based on the parent scope):

JS (Directive):

'use strict';

angular.module('frontendApp')
.directive('ds1', function($http){
    return {
        restrict: 'E',
        replace: true,
        scope: true,
        templateUrl: 'basic/directives/basic.html',
        controller: 'ds1'
    };
});

JS (Controller):

'use strict';


angular.module('frontendApp')
  .controller('ds1', function ($scope) {
    var data = $scope.data;

    $scope.val1 = data.val1;
    $scope.val2 = data.val2;
    $scope.val3 = data.val3;

    //Datepicker
    $scope.dpOpened = {
      val1: false,
      val2: false,
      val3: false
    };

    $scope.open = function($event, opened) {
      $event.preventDefault();
      $event.stopPropagation();
      $scope.dpOpened[opened] = true;
    };

    $scope.dateOptions = {
      formatYear: 'yy',
      startingDay: 1
    };

    $scope.format = 'yyyy-MM-dd';
    //Datepicker
});

HTML:

<div class="container-fluid">
    <h4>Data section 1</h4>
    <div class="form-group">
        <div class="col-xs-6 col-sm-3">
            <label for="value1">Value 1/label>
            <select id="value1" class="form-control" ng-model="val1" ng-required="true">
                <option value="1">X</option>
                <option value="2">Y</option>
                <option value="3">Z</option>
            </select>
        </div>

        <div class="col-xs-6 col-sm-3">
            <label for="value2">Val 2</label>
            <select id="value2" class="form-control" ng-model="val2" ng-required="true">
                <option value="A">A</option>
                <option value="B">B</option>
            </select>
        </div>

        <div class="col-xs-6 col-sm-3">
            <label for="value3">Value 3</label>
            <input id="value3" type="text" ng-model="val3" ng-required="true" disabled class="form-control"/>
        </div>
    </div>
</div>

PROBLEM (and question): When i click in the submit button to send the form data AngularJS doesn't recognize neither of changed values. I think that my problem is the scope. When i copy the values of my main object into values of the directive scope i cannot see the changes that the user made in the form.

Question: Based in my layout and in my description, what is the right way to share the data between all directives AND get the changes, knowing that i'm using ng-model?

Remember that i'm not injecting my service because i don't want to call my API every time for each directive, instead i want to have a single API-Call and the data returned in the parent controller, but sharing it in the child controllers.

Thanks in advance

Alejandro Echeverri
  • 1,328
  • 2
  • 19
  • 32
  • Use a service to hold the data, and inject it in everything that needs it – Fals Sep 20 '15 at 21:12
  • @Fals If i make a service that only hold the data, when child scopes makes any changes, the main scope (controller) can see all the changes made to the data? – Alejandro Echeverri Sep 20 '15 at 21:14

2 Answers2

1

The main controller does not see the changes because you are copying the data in your directive, e.g.

var data = scope.data;
$scope.val1 = data.val1;

This effectively breaks the relationship between your main controller data and directive data, hence the update is not working. In your directive just work directly with $scope.data.val1 e.g:

angular.module('frontendApp')
  .controller('ds1', function ($scope) {
    //Datepicker
    $scope.dpOpened = {
      $scope.data.val1: false,
      $scope.data.val2: false,
      $scope.data.val3: false
    };

    // ..... removed for brevity 
});

And then in your directive template:

<select id="value1" class="form-control" ng-model="data.val1" ng-required="true">

However, there is a problem with this approach of sharing the parent scope, it basically tightly couples your child directives to the parent controller/directive. The recommended way to achieve bidirectional data binding and make your directives not coupled with the parent controller is through using isolated scope along with the = bi-directional binding feature. As a high-level example, using isolated scope your directive would look something like this:

angular.module('frontendApp').directive('ds1', function($http){
    return {
        restrict: 'E',
        replace: true,
        scope: {
            data: '=data'
        },
        templateUrl: 'basic/directives/basic.html',
        controller: 'ds1'
    };
});

Now you can pass your main controller's data to your directive via a bi-directional isolated scope binding:

<ds1 data='data'></ds1>

This way your directive are completely self contained, it does not rely on any parent scope values or properties, and is re-usable. Some more info on isolated scopes here:

https://docs.angularjs.org/guide/directive

https://docs.angularjs.org/api/ng/service/$compile

Beyers
  • 8,968
  • 43
  • 56
0

You should have the state in a service and the you should keep an eye on the state from the directives using $watch.

Here is a short article about it: https://coderwall.com/p/dhgljg/angularjs-watch-for-changes-in-a-service

UPDATE: If you would like a more performant solution you could just give your service a callback which it calls on data changes. $watch is not very performant so you should use it sparingly. See: AngularJS : How to watch service variables?

Community
  • 1
  • 1
  • I don't know... i mean, i have 10 directives with their respective controllers. Does that mean that i must have 10 callbacks? Every directive has 9 fields as minimum. Implementing observer pattern 'by hand' in that situation doesn't seems straighforward. [In this answer](http://stackoverflow.com/a/17558885/478030) i see that the callback is implemented for each field, which make the situation even worse! – Alejandro Echeverri Sep 20 '15 at 22:46
  • Maybe Beyers answer is better then. – Benjamin Hammer Nørgaard Sep 21 '15 at 06:32
  • Beyers answer is that i have looking for. Thank you for your help – Alejandro Echeverri Sep 21 '15 at 13:15