简体   繁体   中英

scope is undefined in AngularJS directive with ajax request in controller

I am working with AngularJS for about one week and I have a problem with a custom directive that I can't really understand. My main goal is to make a html table in a directive with json data loaded in a controller with the $http service.

I have a template for my view. If I use Angular directive like ng-repeat or expressions it seems data are correctly loaded and bound to the scope, and I can render my view.

But if I use a custom directive at the document root, it is fired before the controller send the request. So when I use the scope in the link function scope.myData is undefined. But if I use a custom directive inside a ng-repeat I can access local scope. I don't understand why Angular directive fire after data loading, and why mine fire before. Am I missing somehing?

Actually, my data are really more complex than the sample, and I have to analyse them in a custom directive to generate my html table : make rowspan or colspan for certains attribute like group name in the sample data.

Any help will be useful, thanks a lot in advance.

Here my sample code.

app.js

    angular.module('myApp', [
  'ngRoute',
  'myApp.filters',
  'myApp.services',
  'myApp.directives',
  'myApp.controllers'
]).
config(['$routeProvider', function($routeProvider) {
  $routeProvider.when('/view1', {templateUrl: 'partials/partial1.html', controller: 'MyCtrl1'});
  $routeProvider.when('/test', {templateUrl: 'partials/test.html', controller: 'TestCtrl'});
  $routeProvider.otherwise({redirectTo: '/view1'});
}]);

controllers.js

angular.module('myApp.controllers', []).

  controller('TestCtrl',['$scope', '$http',function($scope, $http){
            $http.get('data/test.json').success(function(data) {
                    $scope.myData = data;
            }); 

  }])   ;

test.html

<!-- this works perfectly -->
<h3>{{myData.title}}</h3>
<table class="table table-condensed table-bordered">
    <thead>
        <tr>
            <th ng-repeat="col in myData.cols">{{col.title}}</th>
        </tr>
    </thead>
    <tbody>
        <tr ng-repeat="row in myData.rows">
                    <td>{{row.group}}</td>
                    <td ng-repeat="col in row.cols">{{col}}</td>
        </tr>
    </tbody>
</table>
<!-- does not work ! -->
<table test-table></table>

directives.js

angular.module('myApp.directives', []).
        .directive('testTable', ['$compile', function(compile){
                return{
                    link: function(scope,elm,attrs,ctrl){
                    for(var i = 0, l= scope.myData.rows.length; i<l; i++){
                        var tr = angular.element(['<tr>', '</tr>'].join(''));
                        console.log(tr);
                        for(var j = 0; j<scope.myData.cols.length; j++){                          
                           var td = angular.element(['<td>', String(scope.myData.rows[i].cols[j]), '</td>'].join(''));
                           tr.append(td);  
                        }
                        elm.append(tr);

                    }
                    compile(elm.contents())(scope);
                }
                }
        }]);

test.json

{
    "title" : "This is a simple sample data.",
    "cols" : [{"title":"Group Name"},{"title":"Col1"},{"title":"Col2"},{"title":"Col3"}],
    "rows" : [
        {"group" : "group A","cols":["Something","Something else","Other stuff"]},
        {"group" : "group A","cols":["Something","Something else","Other stuff"]},
        {"group" : "group A","cols":["Something","Something else","Other stuff"]},
        {"group" : "group B","cols":["Something","Something else","Other stuff"]},
        {"group" : "group B","cols":["Something","Something else","Other stuff"]}
                ]

}

The link function is run only once. It DOES NOT wait for your ajax requests to fetch data and DOES NOT update when the scope changes.

This is in contrast to {{databinding}} in your templates, which DOES update whenever the scope changes.

You could have a look at $scope.$watch which allows you to run a callback whenever a specific scope attribute changes, like so:

scope.$watch('myData', function (newMyData) {
  //Build the table and insert it into DOM
});

Using {{databinding}} and ng-repeat would be the more Angularic approach though.

There are a few ways to do this:

  1. You can try the resolve route parameter (find it in the docs ). This will delay loading a page until a promise is resolved.

  2. You are using Angular like jQuery - don't do that. (It is a common problem with Angular newcommers - read this great answer .) For your question about how ng-repeat sees the change: In short, ng-repeat watches the scope variable and takes action on change. You could do something similar as:

    scope.$watch("myData", function(newval) { ... });

If you want to set rowspan-colspan, maybe the expanded syntax of ng-repeat can help:

<header ng-repeat-start="item in items">
    ...
<footer ng-repeat-end>

(check the long example here )

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM