1

I am a novice with Angular. I have a project that a friend and I were working on that acts as a "Reef Tank Controller". It is an arduino/mysql database/angularJS page. My buddy was working on the front end but had to drop out due to work and now I have a semi completed website. From the perspective of the back-end it all works. On the front end I wanted to add a section to control the lighting. I wanted to start out by simply displaying the values of each LED color that is stored in the database. I created a new controller for each LED string I want to display the value of:

'use strict';
/* Controllers */
angular.module('reefController.controllers', ['uiSlider'])
// Main Dashboard Controller
.controller('dashboardController', ['$scope', function($scope) {
   $.ajax({
        url: 'php/reef_controller_selectVals.php',
        type: 'json',
        success: function(data) {

            reefController.config.data = data;

            // Draw Charts

            $.each(data.charts, function(name, chartData) {

                $(chartData.series).each(function(idx, val){
                    // Reformat sensor names to their Human readable versions
                    this.name = reefController.labels[name].sensors[idx];
                });

                var options = $.extend(reefController.config.chartOptions, { 
                    chart: {
                        events: {
                            load: function() {
                                var chart = this;
                                setInterval(function() {                                    
                                    $.ajax({
                                    url: 'php/reef_controller_selectVals.php?incremental=' + chart.options.name,
                                    success: function(data) {  
                                        // Only add data points if the latest date in the DB is different
                                        if (!(chart.xAxis[0].dataMax == data.timestamp)) {
                                            $(chart.series).each(function(i, series) {
                                                series.addPoint([data.timestamp, data[series.options.id]], true, true);
                                            }); 
                                        } 
                                    }
                                });
                                }, reefController.config.timers.chartUpdateInterval);
                            }
                        },
                        renderTo: name
                    },
                    name: name,
                    series: chartData.series,
                    title: { text: reefController.labels[name].chart.title }
                });


                var chart = Highcharts.StockChart(options);
                reefController.config.charts[name] = chart;
            });

            //Set LED Value Labels



            // Set outlets
            var i = 0;
            $('.outlet').each(function(){
                if (!$(this).hasClass('constant')) {
                    $(this).attr('data-outlet-id', data.outlets[i].id)
                            .attr('data-reveal-id', 'date-time-modal')
                            .attr('data-on-time', data.outlets[i].on_time)
                            .attr('data-off-time', data.outlets[i].off_time);

                    if (data.outlets[i].active) {
                        $(this).addClass('active');
                    } else {
                        $(this).removeClass('active');
                    }
                    i++;
                    // Bind click event to outlets
                    $(this).click(function(){
                        var outlet = $(this);
                        var id = outlet.attr('data-outlet-id');
                        var newOutletState = (outlet.hasClass('active')) ? 0 : 1;

                        // Set datepickers to currently defined DB times
                        $('#on_time').val(outlet.attr('data-on-time'));
                        $('#off_time').val(outlet.attr('data-off-time'));

                        // Formatter function for date time pickers
                        var dateFormatter = function(elem, current_time) {
                            elem.html(moment(current_time).format('ddd M/D/YY HH:mm'));
                        };

                        $('#on_time').datetimepicker({
                            format: 'Y-d-m H:i:s',
                            inline: true,
                            defaultDate: outlet.attr('data-on-time'),
                            onChangeDateTime: function(current_time) { dateFormatter($('#on_time_display'), current_time) },
                            onGenerate: function(current_time) { dateFormatter($('#on_time_display'), current_time) }
                        });

                        $('#off_time').datetimepicker({
                            format: 'Y-d-m H:i:s',
                            inline: true,
                            defaultDate: outlet.attr('data-off-time'),
                            onChangeDateTime: function(current_time) { dateFormatter($('#off_time_display'), current_time) },
                            onGenerate: function(current_time) { dateFormatter($('#off_time_display'), current_time) }
                        });

                        // Submit Button
                        $('#submit-btn').click(function() {
                            data = {
                                'id': id,
                                'on_time': $('#on_time').val(),
                                'off_time': $('#off_time').val(),
                                'active': newOutletState
                            };

                            $.ajax("php/reef_controller_loadOutlet.php", {
                                type: 'POST',
                                data: data,
                                dataType: 'json',
                                success: function(data) {
                                    outlet.toggleClass('active');
                                }
                            });

                            $('#date-time-modal').foundation('reveal', 'close');
                        });
                        // Cancel Button
                        $('#cancel-btn').click(function(){
                            $('#date-time-modal').foundation('reveal', 'close');
                        });
                    });
                }
            });

        }
    });
}])

.controller('outletController', ['$scope', function($scope) {
    $.ajax({
        url: 'img/outlet.svg',
        success: function(svg) {
            var svgDoc = document.importNode(svg.documentElement, true);    
            $('.outlet').append(svgDoc);
        }
    });
}])
.controller('temperatureController', ['$scope', function($scope) { }])
    .controller('phController', ['$scope', function($scope) { }])

.controller('whiteLedCtrl', ['$scope', function($scope) {}])
.controller('blueLedCtrl', ['$scope', function($scope) {}])
.controller('variousColorLedCtrl', ['$scope', function($scope) {}]);

In my dashboard.html file I have:

<table style="width: 100%;">
    <tr>
        <td>
            {{ ledValues }}
        </td>
    </tr>
    <tr>
        <td colspan="3" style="background: #eff4f6;"><input type="checkbox" name="overrideLightingSchema" value="overRide">
            &nbsp;&nbsp;Override Current Lighting Pattern
        </td>
    </tr>
    <tr>
        <td colspan="3">
            <select name="lightingSchemas">
                <option value="feedingLightingPattern">Feeding Schema</option>    
                <option value="morningLightingPattern">Morning Schema</option>
                <option value="standardLightingPattern">Standard Schema</option>
                <option value="twilightLightingPattern">Dusk Schema</option>
                <option value="nightTimeLightingPattern">Night Schema</option>
                <option value="deepNightLightingPattern">Late Night Schema</option>
            </select>
        </td>
    </tr>
</table>

Sometimes this displays the values from the database other times it just says:

{{ ledValues }}

It may be an async issue of some sort but my angular, well JS for that matter, is weak. Any help would be great.

6
  • It's not clear how your controllers are structured. When you see {{ ledValues }}, it may be the case that your variable is not defined. You should have a base controller in which $scope. ledValues is defined and modify the value in the controllers nested in it. Commented Dec 6, 2015 at 23:47
  • This is the problem. I am such a novice with angular I am not certain how to achieve this. Can you link me to a working example? I will edit the top post and insert my entire controller file code. Commented Dec 6, 2015 at 23:50
  • "it's weak" make me smile, nothing is weak if you know how to use it well. Jokes apart, I'm not a fan of mixing jquery and angular so freely, I would stick with an ng-click directive inside the button to tell what function to call. Also AngulaJS has its own XHR request service $http, you should use that. Apart for the controller statement I don't see any angularjs in your code, sorry. Commented Dec 7, 2015 at 0:49
  • @Gianmarco - angular and js are not weak... I meant my knowledge of these subjects are weak. So I need my hand held to figure it out. :) Commented Dec 7, 2015 at 0:54
  • 1
    I think one problem can be those ajax calls, I don't know if its response is handled correctly in angular. You should try to use angular's $http service that has a well coded behavior for 'future' or 'promises'. Commented Dec 7, 2015 at 1:03

1 Answer 1

2

The main issue I can see here is that you are using $ajax to make requests to the server.

You use the response from the server to set your variable...

 reefController.config.data = data;

However since $ajax is not part of Angular this update occurs outside of the scope digest. Therefore Angular does not know to update the binding. You could try wrapping you assignment in $apply.

$scope.$apply(function(){
    reefController.config.data = data;
});

That said, I cannot see where reefController is defined. You probably want to be assigning it to the scope:

$scope.$apply(function(){
    $scope.MyData = data;
});

However, I would actually recommend you replace the $ajax calls with the Angular $http service.

//inject $http
.controller('dashboardController', ['$scope', '$http', function($scope, $http) {

//then use it later on
$http({
  method: 'GET',
  url: 'php/reef_controller_selectVals.php'
}).then(function successCallback(response) {
    // this callback will be called asynchronously
    // when the response is available
    $scope.MyData = data;

  }, function errorCallback(response) {
    // called asynchronously if an error occurs
    // or server returns response with an error status.
  });

Example

Below Is a very quick example of how to use $http to get the data from the server.

The full example, including a fake service (that does not require a server response) can be found here: http://codepen.io/anon/pen/GogWMV

'use strict';

angular.module('reefApp', [ 'uiSlider']); 

/* CONTROLLER */
angular.module('reefApp')
.controller('dashboardController', dashboardController);

  /* Define controller dependencies. 
  Note how we do not use $scope as we are using controller as vm syntax 
  And we assign our scope variables to 'ctrl' rather than scope directly.
  see john papa styleguide (https://github.com/johnpapa/angular-styleguide#style-y032)
  */
dashboardController.$inject = ['ledService'];

function dashboardController(ledService)
{
  var ctrl = this;

  //define an array to hold our led data
  ctrl.ledData = [];

  //call method to get the data
  getLedData();

  //this method uses our service to get data from the server
  //when the response is received it assigns it to our variable
  //this will in turn update the data on screen
  function getLedData()
  {
      ledService.getLedData()
      .then(function(response){
        ctrl.ledData = response.data;
      });
  }
}   

/* LED SERVICE */
/* the service is responsible for calling the server to get the data.
*/
angular.module('reefApp')
.factory('ledService', ledService);

ledService.$inject = ['$http'];

function ledService($http)
{
    var endpointUrl = "http://addressOfYourEndpoint";

    /* expose our API */
    var service =  {      
      getLedData: getLedData,      
    }
    return service;

   function getLedData()
   {
       /* this is how you would call the server to get your data using $http */     
       /* this will return a promise to the calling method (in the controller) 
          when the server returns data this will 'resolve' and you will have access to the data
          in the controller:
   Promises: http://andyshora.com/promises-angularjs-explained-as-cartoon.html*/
       return  $http.get(endpointUrl); 
   }
} 

Taking this further, best practice would be to hold a reference to the data returned from the server inside the service; one reason is the service is a singleton - so this data service and it's data can be shared across controllers.

function ledService($http)
{
    var endpointUrl = "http://addressOfYourEndpoint";

    /* expose our API */
    var service =  {      
      ledData: [],
      getLedData: getLedData,      
    }
    return service;

   function getLedData()
   {
       return  $http.get(endpointUrl)
        .then(function(response)
        {
           /* assign response to service variable, before promise is returned to caller */
           service.ledData = response.data;
         }); 
   }
} 

Then in our controller...

  function getLedData()
  {
      ledService.getLedData()
      .then(function(response){
        ctrl.ledData = ledService.ledData;
      });
  }

Update

To collect more data, you could add a service for each piece of data you want to collect - or add more methods to existing service. Let's assume you add a phService.

You then inject this into your controller. And add call a new method to use the service to the data and assign to the model. It can then be shown in the view.

dashboardController.$inject = ['ledService', 'phService'];

function dashboardController(ledService, phService)
{
  var ctrl = this;

  //our data will be stored here
  ctrl.ledData = [];
  ctrl.phData = [];

  //call methods to get the data
  getLedData();
  getPhData();

  function getLedData()
  {
      ledService.getLedData()
      .then(function(response){
        ctrl.ledData = response.data;
      });
  }

  function getPhData()
  {
      phService.getPhData()
      .then(function(response){
        ctrl.phData = response.data;
      });
  }
}  

Then in the view (HTML):

   <tr ng-repeat="ph in ctrl.phData">
        <td> PHValue</td>
        <td >
            {{ ph }}
        </td>
    </tr>
Sign up to request clarification or add additional context in comments.

5 Comments

So from what I am understanding, regardless of wanting to just add LED data to the screen I should change the entire controller file around to use the $http service rather than ajax? Once I do that I assume doing what I set out to do will be a lot easier to set up?
@VinnyGuitara In short, yes. Either update to use Angular for server side calls or remove Angular and use Jquery / javascript to handle the Ajax response and update the HTML. I have updated my answer with an example of how to use Angular's $http service to query the server for data and update the HTML.
So just to clarify something with you. Following your example I set my endpointUrl to the php service that returns the data correct? Also, how easy is it to add other controllers in this setup - let's say for gathering the pH? Would I just add these functions to gather the data from the server within the current controller like you have setup here? As currently setup this website is not really leveraging angular so I am going to go with your suggestions. I am just concerned with how to capture the pH/Temp data along with the led.
@VinnyGuitara I don't know PHP, but yes that should work. You can add as many controllers and services as you want. You could add all these methods to the existing controller (and service). Or you could have this controller consume multiple services, each associated with one piece of data - e.g. leds, ph, temp etc. See update to answer
If I wanted to try your code could I just use that link to codepen and copy all of the data down to my local sandbox?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.