我正在node-webkit中编写一个Angular应用程序,以便我可以访问文件系统和其他API,如桌面通知等等。
我有一个Angular服务(让我们称之为FileService
)使用fs.readdir()
来读取文件目录并将每个文件的名称作为对象推送到名为allFiles
的数组上。因此,当此方法完成其操作时,它将返回具有此结构的对象的数组allFiles
:{name: "some_file_name.txt"}
通过回调。
以下是FileService
代码的相关位的显示方式:
// ...
methods.getFileList = function (cb) {
var allFiles = [];
fs.readdir(directory, readFiles);
// @todo: this is ugly as hell. Refactor later.
function readFiles(err, files) {
if (err) throw err;
files.forEach(function (filename) {
if (!isSupportedFileFormat(filename)) return;
fs.open(directory + filename, 'r', function (err, fileDescriptor) {
var buffer = new Buffer(85);
fs.read(fileDescriptor, buffer, 0, buffer.length, null, function (err, bytesRead, buffer) {
allFiles.push({
name: filename,
contents: buffer.toString('utf8', 0, bytesRead)
});
fs.close(fileDescriptor);
});
});
});
cb(allFiles);
}
};
// ...
在我的主控制器中,我们称之为MyCtrl
,我有一些类似的代码:
// FileService is the Angular factory I wrote that uses Node's fs to get files
FileService.getFileList(function (list) {
$scope.listOfFiles = list;
console.log($scope.listOfFiles);
});
在我看来(让我们说它在index.html
中),我有一些像这样的代码:
<ul class="file-list">
<li ng-repeat="file in listOfFiles">{{ file.name }}</li>
</ul>
现在,我已经设置了所有内容,以便在应用程序加载时弹出调试控制台,以便我可以看到正在发生的事情。当我构建并运行应用程序时,我可以在控制台中看到所有文件的列表,这些文件在范围变量listOfFiles
中很好地显示。但是,在我的index.html
视图中,我只是在屏幕上显示一个空白区域,列表应该是。 但是,一旦我移动应用程序窗口或点击textarea
甚至是列表所在的空白区域,文件列表就会像我期望的那样神奇地弹出。
我开始抓住稻草,并认为我可能需要触发重绘以显示我的文件列表。所以我尝试了this site上的建议以及Paul Irish的一些建议,但这些解决方案都没有解决问题。
更新:我想要注意我尝试了
$scope.$apply(function() {
$scope.listOfFiles = list;
}
但遗憾的是,这也没有解决问题。
答案 0 :(得分:2)
就像对注释的扩展一样,你在那里进行了太多的非角度异步活动,并且你正在使用数组引用调用回调(此时将为空),但数组中没有项目。这就是你不能立即看到数据的原因。还要避免在角度服务中进行回调,而是使用angular $q
并返回一个promise。在非角度活动的情况下,您通常可以使用延迟对象($q.defer
)返回自定义承诺。创建$ q promise并将其链接起来将确保在执行promise链回调后自动调用摘要周期。这样就无需执行scope.$apply
。在您的情况下,更新后没有发生摘要周期,因此即使您的模型更新,相应的DOM绑定(在视图中)也不会更新(这是摘要周期的一部分)。当你点击其他一些可能有自己的ng-click
或者某个消化周期的控件时,你会突然看到数据。
在你的服务中注入$q
并执行类似的操作(需要进行大量的重新分解,这是一个未经测试的快速和脏代码):
methods.getFileList = function () {
var defer = $q.defer(); //Create a deferred object
var allFiles = [];
fs.readdir(directory, readFile);
// @todo: this is ugly as hell. Refactor later.
function readFile(err, files) {
if (err) throw err;
var cnt = 0; //Set up a counter
//Resolve/reject promise here too if there is no file list
files.forEach(function (filename) {
if (!isSupportedFileFormat(filename)) {
//Resolve promise here too after the check
return ++cnt;
}
fs.open(directory + filename, 'r', function (err, fileDescriptor) {
var buffer = new Buffer(85);
fs.read(fileDescriptor, buffer, 0, buffer.length, null, function (err, bytesRead, buffer) {
allFiles.push({
name: filename,
contents: buffer.toString('utf8', 0, bytesRead)
});
fs.close(fileDescriptor);
//Check for the counter (Reverify this logic)
if(++cntr === files.length){
defer.resolve(allFiles);
}
});
});
});
}
//Return a promise
return defer.promise;
};
在您的控制器链中,通过服务方法返回的承诺: -
FileService.getFileList().then(function (list) {
$scope.listOfFiles = list;
console.log($scope.listOfFiles);
});
答案 1 :(得分:1)
作为@ PSL答案的后续内容,我重构了我的代码以避免回调地狱,并将其作为一个机会来了解有关$q
的更多信息。我仍然有我的保留意见,但我想我会把它发布在这里,以防它对任何人有帮助。这就是我想出的:
methods.getNotes = function () {
var allFiles = [];
return getFiles(directory).then(processFiles);
function getFiles(directory) {
return $q(function (resolve, reject) {
fs.readdir(directory, function (err, files) {
if (err) {
resolve(err);
} else {
resolve(files);
}
});
});
}
function processFiles(files) {
return $q(function (resolve, reject) {
var promises = [];
files.forEach(function (filename) {
promises.push(processFile(filename));
});
$q.all(promises).then(function () {
resolve(allFiles);
});
});
}
function processFile(filename) {
return $q(function (resolve, reject) {
if (!isSupportedFileFormat(filename)) {
resolve();
return 'Error processing ' + filename;
}
openFile(directory, filename, 'r').then(function () {
resolve();
});
});
}
function openFile(directory, filename, flags) {
return $q(function (resolve, reject) {
fs.open(directory + filename, flags, function (err, fileDescriptor) {
if (err) {
resolve(err);
// implement later
} else {
readFile(fileDescriptor, filename).then(function (err, bytesRead, buffer) {
if (err) return err;
resolve(bytesRead, buffer);
});
}
});
});
}
function readFile(fileDescriptor, filename) {
return $q(function (resolve, reject) {
var buffer = new Buffer(85);
fs.read(fileDescriptor, buffer, 0, buffer.length, null, function (err, bytesRead, buffer) {
allFiles.push({
filename: filename,
title: stripFileExtension(filename),
contents: buffer.toString('utf8', 0, bytesRead)
});
fs.close(fileDescriptor);
resolve(err, bytesRead, buffer);
});
});
}
};
我认为这有点冗长,但它更平坦,避免使用文件的计数器,并且更容易阅读和理解......至少对我来说,无论如何。您现在已经注意到我已经跳过了实现错误处理的问题,因为我发现如果阵列中的任何承诺被拒绝,Angular的$q.all()
将会保释。显然原始的Q
实现确实有allSettled
方法来处理这类事情,但不幸的是,它看起来并不像Angular那样移植过(参见this question)。
欢迎任何建议和意见:)