这里有很多关于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
});
立即更新。谢谢你的帮助!
答案 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 });
});
});
}
}
});