52

目前,我的應用程序有一個控制器,它接受一個JSON文件,然後使用「ng-repeat」遍歷它們。這一切都很好,但我也有一個指令,需要遍歷相同的JSON文件。這是一個問題,因爲我無法在一個頁面上請求兩次相同的JSON文件(我也不想這樣做,因爲效率不高)。如果我更改其中一個JSON文件的文件名,指令和控制器請求並遍歷JSON數據就好了。在Angular中,如何將JSON對象/數組傳遞給指令?

我想知道的是:什麼是最好的方式去將從我的控制器的JSON請求形成的數組傳遞到指令?如何將數組傳遞到我的指令中,並在通過控制器訪問時反覆遍歷?

控制器

appControllers.controller('dummyCtrl', function ($scope, $http) { 
    $http.get('locations/locations.json').success(function(data) { 
     $scope.locations = data; 
    }); 
}); 

HTML

<ul class="list"> 
    <li ng-repeat="location in locations"> 
     <a href="#">{{location.id}}. {{location.name}}</a> 
    </li> 
</ul> 
<map></map> //executes a js library 

指令(工作時,我使用的文件名之外locations.json,因爲我已經申請了一次

.directive('map', function($http) { 
    return { 
    restrict: 'E', 
    replace: true, 
    template: '<div></div>', 
    link: function(scope, element, attrs) { 

$http.get('locations/locations.json').success(function(data) { 
    angular.forEach(data.locations, function(location, key){ 
    //do something 
    }); 
}); 
+2

您應該使用服務的第一件事情,而不是使用控制器來獲取您的位置數據。在此之後,您可以通過在控制器和您的指令中依賴注入您的服務中的數據。 –

回答

91

如果要遵循所有的「最佳實踐」,有幾件事情,我建議,一些這些問題在其他答案和評論中都有涉及。


首先,雖然它並沒有太多的關於你問的具體問題的影響,你沒有提到效率,並在你的應用程序來處理共享數據因素它變成最好的辦法是服務。

我個人會推薦擁抱AngularJS的promise system,與原始回調相比,這將使您的異步服務更加可組合。幸運的是,Angular的$http服務已經在引擎蓋下使用它們。這是一個服務,它將返回一個解析爲來自JSON文件的數據的承諾;多次調用該服務不會導致第二個HTTP請求。

app.factory('locations', function($http) { 
    var promise = null; 

    return function() { 
    if (promise) { 
     // If we've already asked for this data once, 
     // return the promise that already exists. 
     return promise; 
    } else { 
     promise = $http.get('locations/locations.json'); 
     return promise; 
    } 
    }; 
}); 

至於獲取數據到你的指令,需要記住的是指令的目的是抽象的通用DOM操作是很重要的;你應該而不是注入應用程序特定的服務。在這種情況下,簡單地將locations服務注入到指令中會很誘人,但這會將指令耦合到該服務。

的代碼模塊的簡要旁白:一個指令的功能應該永遠負責獲取或格式化自己的數據。沒有什麼可以阻止你在指令中使用$ http服務,但這幾乎總是做錯了。編寫一個控制器來使用$ http是正確的方法。指令已經觸及DOM元素,DOM元素是一個非常複雜的對象,並且很難用於測試。將網絡I/O添加到組合中會使您的代碼更加難以理解,而且測試起來更加困難。另外,網絡I/O會以您的指令獲取數據的方式鎖定 - 可能在其他地方您希望使用此指令從套接字接收數據或採用預加載的數據。您的指令應該通過作用域將數據作爲屬性獲取。$ eval和/或擁有一個控制器來處理數據的獲取和存儲。

- The 80/20 Guide to Writing AngularJS Directives

在這種特定的情況下,你應該把你的控制器的範圍內適當的數據,並通過一個屬性的指令共享。

app.controller('SomeController', function($scope, locations) { 
    locations().success(function(data) { 
    $scope.locations = data; 
    }); 
}); 
<ul class="list"> 
    <li ng-repeat="location in locations"> 
     <a href="#">{{location.id}}. {{location.name}}</a> 
    </li> 
</ul> 
<map locations='locations'></map> 
app.directive('map', function() { 
    return { 
    restrict: 'E', 
    replace: true, 
    template: '<div></div>', 
    scope: { 
     // creates a scope variable in your directive 
     // called `locations` bound to whatever was passed 
     // in via the `locations` attribute in the DOM 
     locations: '=locations' 
    }, 
    link: function(scope, element, attrs) { 
     scope.$watch('locations', function(locations) { 
     angular.forEach(locations, function(location, key) { 
      // do something 
     }); 
     }); 
    } 
    }; 
}); 

這種方式,map指令可以任何組位置數據的使用 - 該指令不硬編碼爲使用特定的數據集,並簡單地通過將該指令包含在DOM中來鏈接該指令將不會觸發隨機HTTP請求。

+0

我選擇這是最好的答案,因爲它是最全面的。謝謝布蘭登。有一件事要注意,我可以得到這個工作:我必須註冊一個監聽器回調($ watch)到指令中的foreach函數。在我這樣做之前,指令並沒有將scope.boulders註冊爲定義的對象,但是當我檢查scope對象時,我會看到數組存儲。我發現這是因爲承諾異步運行 - 這意味着我的指令在可以註冊到範圍更改之前正在運行。 – Omegalen

+0

沒問題。你的'看'問題聽起來很怪異;如果您有興趣,我很樂意看一看 - 您可以在我的個人資料中找到我的聯繫信息。 –

+0

@Omegalen想到這個之後,你當然是對的。範圍的'locations'屬性是異步創建的,因此指令必須異步獲取它們。我會用必要的代碼更新答案。 –

11

正如你所說,你不需要兩次請求這個文件。將它從您的控制器傳遞到您的指令。假設您使用控制器範圍內的指令:

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

然後在您的HTML(調用指令的地方)。
注意:locations是您的控制器$scope.locations的參考。

<div my-directive location-data="locations"></div> 

最後,在你的指導

... 
scope: { 
    locationData: '=locationData' 
}, 
controller: ['$scope', function($scope){ 
    // And here you can access your data 
    $scope.locationData 
}] 
... 

這只是一個大綱你指出正確的方向,所以它是不完整的,沒有經過測試。

+0

使用控制器重用並從服務器獲取數據?你認真認爲這是控制器的工作嗎? –

+0

我使用我的控制器與我的指令分開。通過您的推薦,我還能完成我想要的嗎? – Omegalen

+0

我也使用了一個與我的指令分開的控制器(是的,有特定的情況下,你會這樣做),但是當你想在你的應用程序的其他幾個地方使用相同的數據時,這沒有任何意義。 –

5

你需要的是正確的服務:

.factory('DataLayer', ['$http', 

    function($http) { 

     var factory = {}; 
     var locations; 

     factory.getLocations = function(success) { 
      if(locations){ 
       success(locations); 
       return; 
      } 
      $http.get('locations/locations.json').success(function(data) { 
       locations = data; 
       success(locations); 
      }); 
     }; 

     return factory; 
    } 
]); 

locations將在其中擔任單模式的服務進行緩存。這是獲取數據的正確方法。

使用此服務DataLayer在你的控制器和指示是確定如下:

appControllers.controller('dummyCtrl', function ($scope, DataLayer) { 
    DataLayer.getLocations(function(data){ 
     $scope.locations = data; 
    }); 
}); 

.directive('map', function(DataLayer) { 
    return { 
     restrict: 'E', 
     replace: true, 
     template: '<div></div>', 
     link: function(scope, element, attrs) { 

      DataLayer.getLocations(function(data) { 
       angular.forEach(data, function(location, key){ 
        //do something 
       }); 
      }); 
     } 
    }; 
}); 
相關問題