4

I'm having a hard time figuring out a good way to wait component bindings to stabilize in the parent scope before doing initialization in the child components that depend on the binding values to complete its initialization.

Here's my fiddle: https://jsfiddle.net/nLpd9bsq/2/

Possible solutions:

1. Use $onChanges() to wait all bindings stabilize
In this example we only have 1 binding, but imagine we have a component with 3 bindings and each value is loaded asynchronously in the parent scope. It would be a mess trying to sync the initialization on the child scope without using something like a promise.

2. Moving the "Async value" initialization to a Service
Moving the initialization to a Service and using a promise to resolve it and then inject this service into both uppercase and lowercase components. By doing this, besides the fact that we would be giving up on the component binding mechanism, we would also be losing control over the initialization in the appController, and would need extra code into the Service to prevent both components from making the same Async request during its own initialization cycle and ending up with duplicate async requests.

What are your thoughts? Which solution would you use? Thanks!

app.js

angular.module('app', [ ]);
angular
    .module('app')
  .controller('appController',function($scope, $timeout){
    var $ctrl = this;
    $timeout(function(){
      $ctrl.value = 'Async value'
    }, 1000);        
  })
  .component('uppercase', {
    bindings : {
        stringVal : '<'
    },
    controller: function($scope){

        this.$onChanges = function(obj){

        /* Needed to wait initialization on the parent */
        if(obj.stringVal.currentValue){
            this.upperVal = this.stringVal.toUpperCase();    
         }
      };
    },
    template : '<div>Uppercase value: {{$ctrl.upperVal}}</div>'
  })
  .component('lowercase', {
    bindings : {
        stringVal : '<'
    },
    controller: function($scope){

        this.$onChanges = function(obj){
          /* Needed to wait initialization on the parent */
          if(obj.stringVal.currentValue){
            this.upperVal = this.stringVal.toLowerCase();    
          }
      };
    },
    template :'<div>Lowercase value: {{$ctrl.upperVal}}</div>'
  });

app.html

<div ng-app="app" ng-controller="appController as $ctrl">
  <uppercase data-string-val="$ctrl.value"></uppercase>
  <lowercase data-string-val="$ctrl.value"></lowercase>
</div>
Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
Juliano
  • 443
  • 1
  • 5
  • 16

1 Answers1

2

I would move the initialization of the data to a service and emit an event when data is available or updated.

You can use https://stackoverflow.com/a/36291681/217408 to prevent duplicate requests.

Community
  • 1
  • 1
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • I think I agree with you, but I'm still not clear about when it's better to have a service injected rather than an input binding when using components. From a testing perspective, injecting services into multiple components crates an overhead, since we'd need to mock the same service calls multiple times. Sometimes these service calls were introduced just because we needed to solve sync issues like this, not because the component actually depends on it. I think this is symptom of a highly coupled application... But anyways, I don't think there's a magic solution here... Thanks for you reply! – Juliano May 10 '16 at 07:15
  • The components should be simple and business logic should be in services. Using DI makes it easy to mock dependencies and also easy to test components without the business logic. Inputs are convenient to pass simple status valued from parent to child. – Günter Zöchbauer May 10 '16 at 07:19