Область AngularJS не обновляется после асинхронного вызова

У меня возникли проблемы с обновлением моей области видимости в интерфейсе, когда вы обращаетесь к API. На бэкэнде я вижу, что значение переменной my $ scope меняется, но это не отражается в представлениях.

Вот мой контроллер.

Controllers.controller('searchCtrl', 
 function($scope, $http, $timeout) {
   $scope.$watch('search', function() {
      fetch();
   });

 $scope.search = "Sherlock Holmes";

 function fetch(){
   var query = "http://api.com/v2/search?q=" + $scope.search + "&key=[API KEY]&format=json";
    $timeout(function(){
      $http.get(query)
      .then(function(response){ 
        $scope.beers = response.data; 
        console.log($scope.beers);
      });
    });  
 }
});

Вот фрагмент моего html

<div ng-if="!beers">
  Loading results...
</div>
<p>Beers: {{beers}}</p>
<div ng-if="beers.status==='success'">

  <div class='row'>
    <div class='col-xs-8 .col-lg-8' ng-repeat="beer in beers.data track by $index" ng-if="beer.style">
    <h2>{{beer.name}}</h2>          
    <p>{{beer.style.description}}</p>
    <hr>
    </div>
  </div>
</div>

<div ng-if="beers.status==='failure'">
  <p>No results found.</p>
</div>

Я попробовал несколько решений, включая использование $ scope. $ Apply(); но это просто создает общую ошибку

Ошибка: $ digest уже выполняется

Следующая публикация предложила использовать $ timeout или $ asyncDefault AngularJS: Предотвратить ошибку $ digest, которая уже выполняется при вызове $ scope. $ Apply()

Код, который у меня выше, использует $ timeout, и у меня нет ошибок, но все же представление не обновляется.

Помогите оценить

Ответ 1

Я использую AngularJS 1. 3+, вы можете попробовать $scope.$applyAsync() сразу после $scope.beers = response.data; выражение.

Это то, что Angular documentation говорит о $applyAsync()

Запланируйте вызов $ apply, чтобы произойти в более позднее время. Фактическая разница во времени варьируется в разных браузерах, но обычно около ~ 10 миллисекунд. Источник

Обновить

Как указывали другие, вы не должны (обычно) запускать цикл дайджеста вручную. В большинстве случаев это просто указывает на плохой дизайн (или, по крайней мере, не дизайн, ориентированный на AngularJS) вашего приложения.

В настоящее время в OP метод fetch запускается на $ watch. Если вместо этого метод должен был быть вызван ngChange, цикл дайджеста должен запускаться автоматически.

Вот пример того, как выглядит такой код:

HTML

// please note the "controller as" syntax would be preferred, but that is out of the scope of this question/answer
<input ng-model="search" ng-change="fetchBeers()">

JavaScript

function SearchController($scope, $http) {

    $scope.search = "Sherlock Holmes";

    $scope.fetchBeers = function () {
        const query = 'http://api.com/v2/search?q=${$scope.search}&key=[API KEY]&format=json';
        $http.get(query).then(response => $scope.beers = response.data);
    };

}

Ответ 2

Как следует из комментариев, вам не нужно использовать $timeout для запуска цикла дайджеста. Пока UX, который вызывает изменение, находится в пределах угловой конструкции (например, функции контроллера, службы и т.д.), Тогда он должен проявляться в цикле дайджеста.

Основываясь на том, что я могу сделать из вашего сообщения, вы, вероятно, используете вход для поиска, чтобы попасть в API с результатами. Я бы рекомендовал изменить логику таким образом, чтобы вы запускали поиск по явному событию, а не к $watch er.

<input ng-model="search" ng-change="fetch()">

Удалите логику $watch и $timeout.

function fetch(){
    var query = "http://api.com/v2/search?q=" + $scope.search + "&key=[API KEY]&format=json";
$http.get(query)
.then(function(response){ 
    $scope.beers = response.data; 
    console.log($scope.beers);

    //it a good habit to return your data in the promise APIs
    return $scope.beers;  
});
}

Причины, по которым я рекомендую эту рекомендацию:

  • У вас есть более тонкий контроль за тем, как вызван обратный вызов ng-change с помощью ng-model-options. Это означает, что вы можете наложить на него задержку, вы можете запускать различные события UX и т.д.
  • Вы сохранили более четкую последовательность того, как вызывается fetch.
  • Возможно, вы избежали проблем с производительностью и $ digest.

Ответ 3

Эй, ребята, я решил проблему, но я точно не знаю, почему это ничего не изменило. Переупорядочивая мой код на JS Fiddle, я просто поместил все мои частичные файлы в файл index.html, как и переменные запроса и области видимости, которые плавно обновляются. Может быть, конфликт контроллера с моим html выше?

<body ng-app="beerify" ng-controller='searchCtrl'>

<nav class="navbar navbar-inverse navbar-fixed-top">
  <div class="container"><!-- nav bar code -->
  </div>
</nav>

<!-- Main jumbotron for a primary marketing message or call to action -->
<div class="jumbotron">
  <div class="container">
    <h1>Title</h1>

    <form ng-submit="fetch()">
      <div class="input-group">
          <input type="text" ng-model="search" 
                 class="form-control" placeholder="Search the name of a beer" name="srch-term" id="srch-term">
          <div class="input-group-btn">
              <button class="btn btn-default" type="submit"><i class="glyphicon glyphicon-search"></i></button>
          </div>
      </div>
    </form>

  </div>
</div>

<div class="container">

  <div ng-if="!beers">
    Loading results...
  </div>
  <div ng-if="beers.status==='success'">
   <div class='row'>
      <div class='col-xs-8 .col-lg-8' ng-repeat="beer in beers.data track by $index" ng-if="beer.style">
        <!-- ng-if will make sure there is some information being displayed
           for each beer -->
        <h2>{{beer.name}}</h2>
        <h3>{{beer.style.name}}</h3>
        <p>AbvMin: {{beer.abv}}</p>
        <p>AbvMax: {{beer.ibu}}</p>      
        <p>{{beer.style.description}}</p>
        <hr>
      </div>
    </div>
  </div>

  <div ng-if="beers.status==='failure'">
    <p>No results found.</p>
  </div>
</body>