如何在Angular中跨控制器共享数据以更新Chart.js图表?
Hey there! Let's work through how to share your fetched sensor data between myCtrl and LineCtrl in Angular. The key issue here is that each controller has its own isolated $scope, so you can’t just push data directly from one to the other. Let’s go over the best solutions for this.
The Recommended Approach: Use a Shared Service
Angular services are singleton objects, which makes them perfect for sharing data across controllers. This keeps your code decoupled and follows Angular's best practices.
Step 1: Create a Data Sharing Service
First, define a service to store and retrieve your sensor data. Make sure to define your module only once (avoid redefining it in multiple files!).
// Define your module once in one of your files var app = angular.module('statisticsApp', ['chart.js']); // Create a singleton service for shared sensor data app.service('SensorDataService', function() { var storedData = {}; return { // Temperature data handlers setTemperature: function(data) { storedData.temperature = data; }, getTemperature: function() { return storedData.temperature; }, // pH data handlers setPh: function(data) { storedData.ph = data; }, getPh: function() { return storedData.ph; }, // Humidity data handlers setHumidity: function(data) { storedData.humidity = data; }, getHumidity: function() { return storedData.humidity; } }; });
Step 2: Update myCtrl to Store Data in the Service
Modify your Data_Controller.js to inject the service, fix data type issues (you were pushing arrays instead of numbers), and save parsed data after the API call.
// Retrieve the existing module (don't redefine it!) var app = angular.module('statisticsApp'); app.controller('myCtrl', function ($scope, $http, SensorDataService) { "use strict"; $http({ method : "POST", url : "GatewayAPI.php", }).then(function mySuccess(response) { $scope.records = response.data; var chartResultTemp = []; var chartResultph = []; var chartResultHum = []; for(var i=0; i<72; i++) { var mydata = $scope.records.data[i]; var resultType = mydata.data.substring(3, 4); // Convert string value to number for Chart.js compatibility var sensorValue = parseFloat(mydata.data.substring(6,9)); switch(resultType) { case "A": chartResultTemp.push(sensorValue); break; case "B": chartResultph.push(sensorValue); break; default: chartResultHum.push(sensorValue); } } // Save parsed data to the shared service SensorDataService.setTemperature(chartResultTemp); SensorDataService.setPh(chartResultph); SensorDataService.setHumidity(chartResultHum); // Keep your test variables if needed for debugging $scope.test2 = chartResultTemp; $scope.test4 = chartResultph; $scope.test5 = chartResultHum; }); });
Step 3: Update LineCtrl to Fetch Data from the Service
Since your API call is asynchronous, use $watch to detect when the data is ready in the service. Note that your fetched data has 72 entries but your chart only has 12 labels—you’ll need to process the data (e.g., average every 6 entries) to match the label count.
// Retrieve the existing module var app = angular.module('statisticsApp'); app.controller("LineCtrl", function ($scope, SensorDataService) { "use strict"; $scope.labels = ["0200", "0400", "0600", "0800", "1000", "1200", "1400", "1600", "1800", "2000", "2200", "0000"]; $scope.series = ['Temperature (℃)']; // Initialize with mock data as fallback $scope.data = [ [26.5, 26.8, 26.3, 25.8, 29.4, 30.2, 31.5, 31.0, 28.4, 27.6, 26.3, 25.7] ]; // Watch for changes in the shared temperature data $scope.$watch( function() { return SensorDataService.getTemperature(); }, function(newTempData) { if(newTempData && newTempData.length > 0) { // Example: Downsample 72 entries to 12 by averaging every 6 values var processedData = []; for(var i=0; i<newTempData.length; i+=6) { var chunk = newTempData.slice(i, i+6); var average = chunk.reduce((sum, val) => sum + val, 0) / chunk.length; processedData.push(average.toFixed(1)); // Round to 1 decimal place } $scope.data[0] = processedData; } } ); $scope.onClick = function (points, evt) { console.log(points, evt); }; });
Alternative (Not Recommended): Use $rootScope
If you need a quick fix (though it’s not ideal for large apps due to global scope pollution), you can use $rootScope:
- Inject
$rootScopeintomyCtrland save data there:$rootScope.chartResultTemp = chartResultTemp; - Watch for changes in
LineCtrl:$scope.$watch('$root.chartResultTemp', function(newData) { if(newData) { $scope.data[0] = newData; } });
Key Fixes for Your Original Code
- Avoid redefining the module: In your original code, you redefined
angular.module('statisticsApp', ['chart.js'])in both files, which resets the module. Only define it once, then retrieve it withangular.module('statisticsApp')in other files. - Data type conversion: Your original code pushed arrays (e.g.,
[mydata.data.substring(6,9)]) into result arrays. UseparseFloat()to convert these strings to numbers so Chart.js can render them correctly. - Data length matching: Your fetched data has 72 entries, but your chart labels only have 12. Process the data (average, downsample) to match the label count for proper rendering.
内容的提问来源于stack exchange,提问作者Daine MacTavish

