Phil Barresi
Phil Barresi

Reputation: 1355

How can I pass data from the directive of a parent element to child elements?

I am attempting to write a directive to resolve promises and add data to the scope so that I can lazily load data into child elements from an API.

Through console.logging I am certain that I am resolving the promise and getting my expected data, but nothing updates on the page. I am assuming that dynamically adding to the scope from inside a directive is causing my problem.

The directive seems fairly trivial and I know that the promise is getting resolved:

app.directive('resolver', () => {
  return {
        restrict: "AEC",
        scope: {
            outName: "@",
            promise: "&",
            defaultVal: "="
        },
        link: function ($scope, element, attrs) {
            var promise = $scope.promise,
                outName = $scope.outName || 'myPromise',
                defaultVal = $scope.defaultVal || {};

            $scope[outName] = defaultVal;

            promise().then(data => {
                $scope[outName] = data;
            });
        }
    }
});

I was hoping that this would let me do something like the following:

<li class="company-entry" ng-repeat="company in currentFeed.companies" resolver promise="getPreview(company.companyId)" out-name="profile" default-val="{name:'Loading...',description:'',images:[],sources:[]}">
    ...
    <a ui-sref="company.feed({ id: company.companyId })" class="company-name">{{ profile.name || 'foo' }}</a>
    </li>

Firstly, I have profile.name || 'foo' in order to test if the default-val part of my code would work; since it stays at foo, not even the default value gets placed into profile.

Secondly, after the promise is resolved, I should be setting profile to the data, but the page does not updating.

Adding a $scope.$apply() causes me to get rootscope update already in progress errors.

Is there any way to accomplish what I am trying to do?

Upvotes: 4

Views: 634

Answers (1)

Benjamin Gruenbaum
Benjamin Gruenbaum

Reputation: 276306

I would solve this differently. Instead of building this into a directive you can easily produce a thenable that automatically unwraps just like ngResource does.

Let's consider an example: You have an array you need to fetch online with a getArray() function which returns a promise and makes an http request.

Normally you do:

getArray().then(function(result){
    $scope.data = result;
});

Which I assume you're trying to avoid because it's a lot to write if everything in your scope is like this. The trick promises used to use and ngResource still uses in Angular is the following:

  • Return an empty result, which is also a promise
  • Whenever that promise resolves, update the result on the screen.

For example, our getArray() could look something like:

function getArray(){
    var request = $http(...); // actual API request
    var result = []
    result.then = request.then; // make the result a promise too
    result.then(function(resp){ 
        // update _the same_ object, since objects are passed by reference
        // this will also update every place we've send the object to
        resp.data.forEach(function(el){ result.push(el); });
    });
    return result;
}

Now if you call getArray() and assign the result to a scope variable, whenever the promise resolves it will update the value.

Upvotes: 3

Related Questions