angular component one-time binding from $http shows undefined

614 views Asked by At

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?

3

There are 3 answers

0
georgeawg On BEST ANSWER

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.

0
davidxxx On

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);
0
Aaron Pool On

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.