如何更改模型(在指令中)以在视图中更新? $申请?

时间:2014-08-02 07:09:03

标签: angularjs angularjs-directive angularjs-scope

这里有很多关于stackoverflow和google关于这个主题的问题/答案($apply),我觉得我已经阅读过每一个并关注它们,但无济于事。我在谷歌上的所有搜索现在返回紫色链接。

这是我面临的问题(试图在没有过度杀伤的情况下具体化):

我有一个应用程序,它通过Web服务提取数据,将记录存储在一个数组中。我创建了一个拖放目标来上传excel文件,其中包含对数据的更改/添加。我已经为drop target创建了一个指令,它将事件侦听器绑定到该元素。我已经隔离了范围,使用&将函数从控制器绑定到指令中。控制器中的函数处理删除的文件并更新模型。这是设置:

HTML

<div ng-controller="myController as vm">
  <div class="row">
    <div class="col-lg-12">
      <div drop-target drop="vm.drop(files)">
        <p>Drag an XLSX file here to import.</p>
      </div>
    </div>
  </div>
</div>

之后我还有一张表用ng-repeat来显示记录。

控制器

app.controller('myController', ['dataService', '$scope', function (data, $scope) {
    var vm = this;
    vm.data = data;
    vm.drop = function (files) {
        var reader = new FileReader();
        reader.onload = function (e) {
            ... Read and parse excel sheet into array of objects ...

            importLines(wbJson);  //call function to update model with objects
        };
        reader.readAsBinaryString(files[0]);
    }

    function importLines(lines) {
        //do a bunch of validation and update model data
    }
}

指令

app.directive('dropTarget', function () {
    return {
        restrict: 'A',
        scope: {
            drop: '&'
        },
        link: function (scope, el, attrs, controller) {
            el.bind("dragover", function (e) {
                ...
            });

            el.bind("dragenter", function (e) {
                ...
            });

            el.bind("dragleave", function (e) {
                ...
            });

            el.bind("drop", function (e) {
                if (e.preventDefault) { e.preventDefault(); } // Necessary. Allows us to drop.
                if (e.stopPropogation) { e.stopPropogation(); } // Necessary. Allows us to drop.
                var files = e.originalEvent.dataTransfer.files;
                scope.$apply(function () {
                    scope.drop({ files: files });
                });
            });
        }
    }
});

所以我在线阅读的所有内容似乎表明我应该将调用包裹在$apply()中的控制器功能中,正如您所看到的那样。拖放和更新模型的所有功能都可以正常工作。但是,视图不会更新。模型已更新 - 我可以在控制台中看到它。当我触发任何其他Angular活动时(单击某个按钮,其中包含ng-click或选中具有ng-change的复选框等),整个UI更新,我可以看到模型的所有更新。

如果我使用$ apply()将控件包含在控制器中importLines,那么效果很好。但我的理解是应该在指令内完成$ apply()调用...尽量避免在控制器中使用它。

我不能为我的生活弄清楚为什么它不起作用。它似乎遵循我在读过的关于$ apply()的论坛和博客上的数十个和几十个例子。我仍然相当新的角度 - 我知道当他们开始讨论$ digest周期等时,我并不完全了解一些论坛/博客(但我知道比我几天前做的更多)。我知道模型的一些更改是在angular的上下文之外完成的,所以你必须调用$ apply()。一些答案说,指令的链接功能在角度的上下文中,其他的没有。我没有收到任何错误($ apply适用于$申请)。一切运行顺利......它只是不更新​​视图。

我错过了什么?任何帮助,将不胜感激。

<小时/>

更新

感谢Erti-Chris的回答。我最终在$q函数中放了一个vm.drop承诺,在importLines函数完成后解析它。在指令中,当我离开scope.$apply()时,我收到一个错误,即$ apply已经在进行中。但是我仍然必须在函数调用上有一个.then,即使它是空的。没有它,它将无法正常工作。

控制器

app.controller('myController', ['dataService', '$scope', function (data, $scope) {
    var vm = this;
    vm.data = data;
    vm.drop = function (files) {
        var deferred = $q.defer(); //Added
        var reader = new FileReader();
        reader.onload = function (e) {
            ... Read and parse excel sheet into array of objects ...

            importLines(wbJson);
            deferred.resolve(); //Added
        };
        reader.readAsBinaryString(files[0]);
        return deferred.promise; //Added
    }

    function importLines(lines) {
        //do a bunch of validation and update model data
    }
}

指令

scope.drop({ files: files }).then(function (r) {
    // Do nothing here, but doesn't work without .then
});

立即更新。谢谢你的帮助!

2 个答案:

答案 0 :(得分:0)

我希望FileReader类是异步的。在JS中阻止它是没有意义的。这就解释了为什么你的scope.apply不能在指令中工作。原因是:范围应用运行前importLines有机会运行(因为它是异步的)。

您可以查看$q(一个可以很好地解决它的promise对象),或者您可以创建一个回调函数,该函数将“通知”指令最终完成了importLines。

vm.drop = function (files, doneCallback) {
    var reader = new FileReader();
        reader.onload = function (e) {
          ... Read and parse excel sheet into array of objects ...

          importLines(wbJson);  //call function to update model with objects
          if(doneCallback) 
            doneCallback();
        };
        reader.readAsBinaryString(files[0]);
}

并在指令中:

  scope.drop({ files: files }, scope.$apply);

答案 1 :(得分:0)

指令可以具有独立的范围,也可以与父范围共享范围。但是,在您的情况下,您通过声明

来使用隔离范围
scope: {
  drop: '&'
},
在你的指令中

。 &安培;允许指令的隔离范围将值传递到父作用域中,以便在属性中定义的表达式中进行评估。相反,=在指令的隔离范围和父范围之间设置双向绑定表达式。如果你使用

scope: {
  drop: '='
}, 
在你的指令中

然后它将完全传递该范围对象并使用双向绑定。

您可以在指令中使用ngModel。

app.directive('dropTarget', function () {
  return {
    restrict: 'A',
    require:'^ngModel',
    link: function (scope, el, attrs, controller,ngModel) {
      el.bind("dragover", function (e) {
        ...
      });

      el.bind("dragenter", function (e) {
        ...
      });

      el.bind("dragleave", function (e) {
        ...
      });

      el.bind("drop", function (e) {
        if (e.preventDefault) { e.preventDefault(); } // Necessary. Allows us to drop.
        if (e.stopPropogation) { e.stopPropogation(); } // Necessary. Allows us to drop.
        var files = e.originalEvent.dataTransfer.files;
        scope.$apply(function () {
          ngModel.$setViewValue({ files: files });
        });
      });
    }
  }
});