3

I'm new to Angular. I'm trying to use components (1.6). In the parent, I have an $http.get that gets data from a service and then assigns the response to a $scope variable. That scope variable is passed to a child component using one-way binding <. In the JavaScript, if I alert the variable passed in, I get "undefined", however, the html template in the child does show the variable. It's like there is a race condition happening and I don't know how to tell it to wait until the data from the service is loaded.

In my parent.js:

(function (angular) {
    'use strict';
    $http.get("http://localhost:52422/api/PayOffYourCc")
    .then(function mySucces(response) {
        $scope.baseline = response.data;

    }
    ,
    function myError(respone) {
        $scope.baseline = response.statusText;

    }
    );
})(window.angular);

In my parent HTML template:

<thermometer baseline="baseline"></thermometer>

In my child component:

(function (angular) {
    'use strict';

    function drawChart(baselineVal) {
        alert(baselineVal);
    }

    function ThermometerController($scope) {
        var ctrl = this;
        ctrl.$onInit = function () {
            drawChart(ctrl.baseline);
        };


    }

    angular.module('payOffYourCcApp').component('thermometer', {
        templateUrl: '../PayOffYourCC/partials/thermometer.html',
        transclude: true,
        controller: ThermometerController,
        bindings: {
            baseline: '<'
        }
    });
})(window.angular);

In my child html template:

<div>
    baseline:{{$ctrl.baseline}}
</div>

In the html, {{$ctrl.baseline}} is displayed fine, but when I alert it in the .js, it's undefined. Why is that? How can I make sure the {{$ctrl.baseline}} is in scope before the javascript loads?

georgeawg
  • 48,608
  • 13
  • 72
  • 95
Leon Gentle
  • 45
  • 1
  • 1
  • 5
  • Once the http callback function completes and a value has been assigned to baseline, it triggers a digest cycle which updates your view accordingly. The alert operation happens synchronically though - before the http operation even has a chance to complete – IAmDranged Dec 19 '16 at 18:44
  • Thank you very much for all the solutions. I understand it much better now, however, I've decided to give react.js a go. I'm still having issues with http call backs and race conditions, but I'll let you know how I get on. – Leon Gentle Dec 23 '16 at 11:54
  • Actually, had other similar issues with React. It's all to do with component life cycle. I've gone back to using Angular - thanks to the answer given below. – Leon Gentle Jan 12 '17 at 12:39

3 Answers3

1

Use the $onChanges life-cycle hook:

function ThermometerController($scope) {
    var ctrl = this;
    /* REPLACE THIS
    ctrl.$onInit = function () {
        drawChart(ctrl.baseline);
    }; */
    // WITH THIS
    ctrl.$onChanges = function (changesObj) {
        if (changesObj.baseline && changesObj.baseline.currentValue) {
            drawChart(changesObj.baseline.currentValue);
        };
    };
}

The controller needs to wait for the data to come from the server. By using the $onChanges life-cycle hook, the drawChart function will be called when the data becomes available and will be called on subsequent updates.

For more information, see AngularJS Comprehensive Directive API Reference - Life-Cycle Hooks.

georgeawg
  • 48,608
  • 13
  • 72
  • 95
0

With Angular component, you should privilege communication from the child back to the parent since it allows a very cheap binding (&)

You can communicate from the parent to the child but it is more expensive (=).
I will give you an example on how to do it.
It is a not tested solution but you should have the idea.

You should change your parent to have a child api to transmit the data :

JS Parent :

(function (angular) {
    'use strict';
    $http.get("http://localhost:52422/api/PayOffYourCc")
    .then(function mySucces(response) {
        $scope.baseline = response.data;
        $ctrl.apiChild.transmit(response.data);
    }
    ,
    function myError(respone) {
        $scope.baseline = response.statusText;
    }
    );
})(window.angular);

HTML Parent :

<thermometer api="$ctrl.apiChild"></thermometer>

Change your child to have a function to receive the data from the parent and also change the binding to "=" :

JS Child :

(function (angular) {

'use strict';

function drawChart(baselineVal) {
    alert(baselineVal);
}

function ThermometerController($scope) {
    var ctrl = this;
    ctrl.$onInit = function () {
        drawChart(ctrl.baseline);
        ctrl.api = {};
        ctrl.api.transmit = ctrl.transmitData;
    };

   this.transmitData = function transmitData(data){
        // here you get the data from the parent to the child
   }

}

angular.module('payOffYourCcApp').component('thermometer', {
    templateUrl: '../PayOffYourCC/partials/thermometer.html',
    transclude: true,
    controller: ThermometerController,
    bindings: {
        api : '='
    }
});
})(window.angular);
davidxxx
  • 125,838
  • 23
  • 214
  • 215
0

This is the result of the fact that $http requests are asynchronous. The alert that happens when the child component initializes prints undefined, because at that instance the $http request that is retrieving the data has not returned yet. Since you're using > binding however, the template in your child component will update with the correct value as soon as the request resolves (which is pretty darn fast), so you don't ever see undefined actually printed in the template. In fact, I don't think angular will print undefined, I think it's just blank. So to your eye, it looks like it has the right value right away, when, in reality, it was momentarily undefined while the $http request was resolving.

Aaron Pool
  • 479
  • 2
  • 6