AngularJS simple factory causes infinite cycle loop - why? (codepen)

Viewed 119

I'd like to check an image to see if the resource is available, before displaying it. I found a good way to do that in AngularJS here: Angular js - isImage( ) - check if it's image by url

But every time I try to implement it, an infinite loop is triggered, even if I reduce the function to its simplest form in a codepen : https://codepen.io/anon/pen/mBwgbE

test image function (js)

$scope.testImage = function(src) {
    console.log('function triggered');
                Utils.isImage(src).then(function(result) {
             return "result";
         });
    };

Usage (html)

<h3>Image link broken<h3>
<p>{{testImage('anylink')}}</p>

<h3>Image link OK<h3>
<p>{{testImage('http://lorempixel.com/400/200/sports/1/')}}</p>

Can anyone explain this behaviour to me, and help me fix it?

2 Answers

Angular runs the digest loop, and interprets your template. It sees {{testImage('anylink')}} and calls into it. This calls into Utils.isImage, which creates a promise. The promise is returned to testImage, but testImage itself doens't return anything, so the template shows nothing.

A little later, the promise resolves. Angular sees this, so it runs the digest loop and interprets your template. It sees {{testImage('anylink')}} and calls into it. This calls into Utils.isImages, which creates a prom... oh crap, we're in an loop. It's going to call isImage, which creates a promise, and then when that promise resolves, it interprets the template again and calls isImage, starting all over.

Instead, i would recommend that when your controller loads, you create the promises right then, and when they resolve, you stick whatever values you need from them onto the controller as concrete values. Something like this:

function myController($scope, Utils) {
    $scope.anyLink = null;
    $scope.sportsLink = null;
    Utils.isImage('anyLink')
        .then(function (result) { $scope.anyLink = result });
    Utils.isImage('http://lorempixel.com/400/200/sports/1/')
        .then(function (result) { $scope.sportsLink = result });

    $scope.heading = "My Controller";
}

And then on your template, interact with $scope.anyLink or $scope.sportsLink

$scope.testImage is automatically watched by angular to see change of testImage. As a result you can stop infinite loop by using $scope.cache variable.

  $scope.testImage = function(src) {
    console.log('function triggered');
    if($scope.cache[src] == "result")
      return "result";
    Utils.isImage(src).then(function(result) {
             $scope.cache[src] = "result";
             return "result";
         });
    }; 

It was tested on your codepen.

Related