AngularJS setting model value from directive and calling a parent scope function holds on to the previous value inside that function

亡梦爱人 提交于 2019-12-13 06:27:13

问题


js fiddle http://jsfiddle.net/suras/JzaV9/4/

This is my directive

'use strict';

barterApp.directive('autosuggest', function($timeout, $http) {
  return {
    restrict: "E",
    scope: {
      modelupdate:"=",
      suggestions:"=",
      urlsend:"@"
    },
    template: '<ul><li ng-repeat="suggest in suggestions" ng-click="updateModel(suggest)">{{suggest}}</li></ul>',
    link: function (scope, element) {
      scope.$watch('modelupdate', function() {
        $timeout(function(){
          $http.post(scope.urlsend, {q:scope.modelupdate}).then(function(data){
            scope.suggestions = data.data;
            console.log(data.data);
          });
        }, 3000);
      });
      scope.updateModel = function(value){
        scope.modelupdate = value;
        scope.$parent.getBookInfo();
      }
    }
  };

});

controller is

barterApp.controller('openLibraryCtrl', ['$scope','$http',function ($scope,$http) {
    $scope.title = "";
    $scope.getBookInfo = function(value){
      if($scope.title == "" || $scope.title == " ") //here title is 'r'(previous value)
      {
        return;
      }
      $http.get('/book_info.json?q='+$scope.title).then(function(res){
        if(Object.keys(res).length !== 0)
        {
           data = res.data
           console.log(data);

        }
      });
    }
    //here title is 'rails' (updated value from directive).
     //( used a watch function here  on model update 
    // and checked it but inside getBookInfo it is still 'r' )   

}]);

in the update model function i set the model value and call the getBookInfo function on parent scope. but the thing here is when (this is a autocomplete) i enter the value in a input field that contains ng-model say for example 'r' then triggers the watch and i get suggestions from a post url (lets say "rails", "rock") and show it through the template as in the directive. when i click one of the suggestions (say 'rails') it triggers the updatemodel function in directive and sets the model value. its fine upto this but when i call the getBookInfo function in parent scope then $scope.title is 'r' inside the function (i checked with console log outside the function the model value was updated correctly as 'rails' ). again when i click 'rock' the model value inside getBookInfo is 'rails'. i have no clue whats going on. (i also tested with watch function in controller the model gets updated correctly but the function call to getBookInfo holds back to the previous value)

view

 <form ng-controller="openLibraryController">
 <input type="text" ng-model="title" id="title" name="book[title]"  />
   <autosuggest modelupdate = "title" suggestions = "book_suggestions" urlsend="/book_suggestions.json"> </autosuggest>
</form>

回答1:


I didn't look deep into it, but I suspect (with a high degree of confidence) that the parent scope has not been updated at the time of calling getBookInfo() (since we are still in the middle of a $digest cycle).

Not-so-good Solution 1:
You could immediately update the parent scope as well (e.g. scope.$parent.title = ...), but this is clearly a bad idea (for the same reasons as nr 2, but even more so).

Not-so-good Solution 2:
You could pass the new title as a parameter to getBookInfo().

Both solutions result in mixing controller code with directive code and creating a tight coupling between your components, which as a result become less reusable and less testable.

Not-so-bad Solution:
You could watch over the title and call getBookInfo() whenever it changes:

$scope.$watch('title', function (newValue, oldValue) {
    getBookInfo();
}); 

This would be fine, except for the fact that it is totally unnecessary.


Better Solution:
Angular is supposed to take care of all that keep-in-sync stuff for us and it actually does. You don't have given much context on what is the purpose of calling getBookInfo(), but I am guessing you intend to update the view with some info on the selected book.
In that case you could just bind it to an element (using ng-bind) and Angular will make sure it is executed properly and timely.
E.g.:

<div>Book info: <span ng-bind="getBookInfo()"></span></div>

Further more, the autosuggest directive doesn't have to know anything about it. It should only care about displaying suggestions, manipulating the DOM (if necessary) and updating the specified model property (e.g. title) whenever a suggestion is clicked. What you do with the updated value should be none of its business.

(BTW, ideally the suggestions should be provided by a service.)


Below is a modified example (based on your code) that solves the problem. As stated above there are several methods of solving the problem, I just feel this one tobe cleaner and more aligned to the "Angular way":

Book title: <input type="text" ng-model="book.title" />
<autosuggest modelupdate="book.title"
             suggestions="book.suggest()"></autosuggest>
Book info: <span ng-bind="book.getInfo()"></span>

Just by looking at the HTML (without knowing what is in JS), one can easily tell what is going on:

  1. There is a text-field bound to book.title.
  2. There is a custom autosuggest thingy that offers suggestions provided by book.suggest() and updates book.title.
  3. There is a span that displays info about the book.

The corresponding directive looks like this:

app.directive('autosuggest', function() {
    return {
        restrict: 'E',
        scope: {
            modelupdate: '=',
            suggestions: '&'
        },
         template:
          '<ul><li ng-repeat="suggest in suggestions()" ' +
                  'ng-click="modelupdated = suggest">' +
               '{{suggest}}</li></ul>'
    };
});

As you can see, all the directive knows about is how to retrieve suggestions and what to update.

Note that the same directive can be used with any type of "suggestables" (even ones that don't have getBookInfo()); just pass in the right attributes (modelupdated, suggestions). Note also, that we could remove the autosuggest element and the app would continue to work as expected (no suggestions of cource) without any further modification in HTML or JS (while in your version the book info would have stopped updating).


You can find the full version of this short demo here.



来源:https://stackoverflow.com/questions/21145191/angularjs-setting-model-value-from-directive-and-calling-a-parent-scope-function

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!