2

I'm trying to migrate from jQuery Data Tables to Angular UI. My app uses the MySQL World schema and displays the data in 3 different tables. In jQuery, I'd 3 different pages, each launched from a home page. Here's a live example. In Angular, I created 3 tabs, clicking on each of which is supposed to reload the page with a new grid and populate it with data. Problem is, the grid gets displayed alright on page load with the content on the first tab. On clicking the other tabs, page goes empty and nothing is rendered. Now the data returned is not insignificant, sometimes around 4k rows. However, the problem isn't a latency issue as I've confirmed by waiting several minutes.

I'm not a JS/CSS/HTML guy so most likely I'm missing something. That's why this question.

Edit: Plnkr

Following is the code:

HTML:

<body>
<div id="selection-panel" class="selection-panel" ng-controller="HelloWorldCtrl">
    <div>
        <uib-tabset type="pills" justified="true">
            <uib-tab ng-repeat="tab in tabs" heading="{{tab.title}}" select="update(tab.title)">
                <div id="data-panel" class="data-panel" ui-grid="gridOptions"></div>
            </uib-tab>
        </uib-tabset>
    </div>
</div>
<script src="js/app.js"></script>
</body>

JS:

(function() {
    var app = angular.module('helloWorld', ['ui.bootstrap', 'ui.grid']);

    app.controller('HelloWorldCtrl', ['$http', '$scope', function ($http, $scope) {
        $scope.tabs = [
            { title: 'Countries' },
            { title: 'Cities' },
            { title: 'Languages' }
        ];

        $scope.gridOptions = {};
        $scope.gridOptions.data = [];

        $scope.gridOptionsCountries = {
            columnDefs: [
                { name: 'code'},
                { name: 'name'},
                { name: 'continent'},
                { name: 'region'},
                { name: 'population'}
            ]
        };

        $scope.gridOptionsCities = {
            columnDefs: [
                { name: 'id'},
                { name: 'name'},
                { name: 'country'},
                { name: 'population'}
            ]
        };

        $scope.gridOptionsLanguages = {
            columnDefs: [
                { name: 'country'},
                { name: 'language'}
            ]
        };

        $scope.update = function(title) {
            if (title === "Countries") {
                $scope.gridOptions = angular.copy($scope.gridOptionsCountries);
            } else if (title === "Cities") {
                $scope.gridOptions = angular.copy($scope.gridOptionsCities);
            } else if (title === "Languages") {
               $scope.gridOptions = angular.copy($scope.gridOptionsLanguages);
            }

            $http.get(title.toLowerCase()).success(function(data) {
                $scope.gridOptions.data = data;
            });
        };
    }]);
})();
Prashant Pokhriyal
  • 3,727
  • 4
  • 28
  • 40
Abhijit Sarkar
  • 21,927
  • 20
  • 110
  • 219
  • Could you supply a fiddler or some live example of your angular code? – Johan Kvint Dec 14 '15 at 07:40
  • Please make API available from plunker: "XMLHttpRequest cannot load http://nameless-island-5362.herokuapp.com/helloworld/countries. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://run.plnkr.co' is therefore not allowed access.". Do you try this the same way when you develop? – Johan Kvint Dec 14 '15 at 07:56

3 Answers3

1

I can not get this to work in tabs, my guess is because you first use ng-repeat which creates a scope for each iteration, and then maybe the tabs itself creates a new scope and this causes a lot of headache with updates.

The quickest solution is just to move the grid outside of the tabs.

Here is the updated html.

HTML

<body>
<div id="selection-panel" class="selection-panel" ng-controller="HelloWorldCtrl">
    <div>
        <uib-tabset type="pills" justified="true">
            <uib-tab ng-repeat="tab in tabs" heading="{{tab.title}}" select="update(tab.title)"></div>
            </uib-tab>
        </uib-tabset>
        <!-- This is moved outside -->
        <div id="data-panel" class="data-panel" ui-grid="gridOptions">
    </div>
</div>
<script src="js/app.js"></script>
</body>
Johan Kvint
  • 907
  • 1
  • 8
  • 17
  • I'd tried that without success but seems to be working now. I must've made changes to the JS after that. Two questions: 1) The data seem to be loaded one column at a time. Why is that? 2) Is there a way to eliminate the `if-else` block by writing something as follows: `$scope.gridOptions = angular.copy($scope.eval(gridOptions + title));` – Abhijit Sarkar Dec 14 '15 at 09:13
  • Answer to your question: 1) I never got this behaviour using ui-grid. But it may be related to uib-tabs rendering. Do you get this even using the handleWindowResize in my answer? 2) You could get rid of the if by managing everithing by numerical index and using that index to access gridOptions inside the array (see my answer) but would it be as readable? I don't think so – imbalind Dec 14 '15 at 09:22
1

I see 2 problems here:

  1. You are creating/changing gridOptions dinamically. This is not the usual way of doing things and can bring many problems.

  2. You are using grids inside of uib-tabs and this, like uib-modals, can have some annoying side effects.

I'd suggest you to address the first issue using different gridOptions (as you do when you create them) and then putting them inside your tabs array children, this way you can refer them from the html this whay:

<uib-tab ng-repeat="tab in tabs" heading="{{tab.title}}" select="update(tab.title)">
  <div id="data-panel" class="data-panel" ui-grid="tab.gridOptions"></div>
</uib-tab>

The second problem is quite known and inside this tutorial they explain how to address it: you should add a $interval instruction to refresh the grid for some time after it's updated in order to let the tab take its time to load and render.

The code should be as follows:

$scope.tabs[0].gridOptions.data = data; 
$interval( function() {
  $scope.gridCountriesApi.core.handleWindowResize();
}, 10, 500);

Where gridCountriesApi are created inside of a regular onRegisterApi method.

I edited your plunkr, so you can see the whole code.

imbalind
  • 1,182
  • 6
  • 13
  • Thank you. I asked 2 questions in response to @korven's response. Do you mind taking a look at those? – Abhijit Sarkar Dec 14 '15 at 09:16
  • I've accepted your answer. However, I tried to reduce the duplication in the code but am getting an error `cannot read property 'core' of undefined` of [this Plunker](http://plnkr.co/edit/QATClXO8URKh7wKI9EUI?p=preview). If you'd like me to ask in a separate question, I can, but to me, this is necessary code cleanup. – Abhijit Sarkar Dec 15 '15 at 00:45
  • What happens here is that you have a closure inside a for loop, which is reeeeal bad :( have a look at [this other](http://stackoverflow.com/a/750506/2453383) question for a better explanation. PS if you want a proof of that, put a breakpoint at the line where you set `tab.gridApi = gridApi` and see as it loops through, the tab will always be the same object (the last one) – imbalind Dec 15 '15 at 14:54
0

In my situation tab contents consume important time to be loaded. So if your case is that you don't want to update tab content everytime the tab is clicked, you can use this workaround:

In the HTML part I use select property to indicate which tab is pressed:

<tabset justified="true">
    <tab heading="Tab 1" select="setFlag('showTab1')">
        <div ng-if="showTab1">
        ...
        </div>
    </tab>
</tabset>

In the tab (*) container I used a switch to recognize which tab is pressed and broadcast the press action:

case 'showTab1':
    $scope.$broadcast('tab1Selected');
    break;

And in the controller part I listen the event and handle resizing:

// The timeout 0 function ensures that the code is run zero milliseconds after angular has finished processing the document.
$scope.$on('tab1Selected', function () {

      $timeout(function() {

          $scope.gridApi1.core.handleWindowResize();
      }, 0);
});

Here is my Plunkr. Hope it helps.

(*) For current bootstrap version you should use and

Aquiles
  • 857
  • 1
  • 10
  • 19