我有一个Angular 4.3 + Cordova应用程序,以前工作得非常好。但是现在,我在app启动时出现了一个空白屏幕,而且没有任何事情发生了。
经过一段时间的挖掘,我意识到它的来源:
我的主页受CanActivate
警卫保护,该警卫将检查一些文件系统持久化的偏好设置,并将用户重定向到另一个页面,如果这是第一次运行或缺少必要的偏好,则填写 - 在所需的属性。
因此,该应用的推出取决于我的CanActivate
后卫取决于PreferenceService
本身取决于我自己实施的FileSystemService
。问题是,当我尝试读取存储用户首选项的文件时,没有触发单个回调,没有任何反应,甚至不是错误。
这是FileSystemService
失败而没有任何错误的部分:
read(file: FileEntry, mode: "text" | "arrayBuffer" | "binaryString" | "dataURL" = "text"): Observable<ProgressEvent> {
return this.cdv.ready.flatMap(() => {
return Observable.create(observer => {
file.file(file => {
let reader = new FileReader();
reader.onerror = (evt: ErrorEvent) => {
this.zone.run(() => observer.error(evt)); //never triggered
};
reader.onload = (evt: ProgressEvent) => {
this.zone.run(() => observer.next(evt)); //never trigerred
};
switch (mode) {
case "text":
reader.readAsText(file);
break;
case "arrayBuffer":
reader.readAsArrayBuffer(file);
break;
case "binaryString":
reader.readAsBinaryString(file);
break;
case "dataURL":
reader.readAsDataURL(file);
break;
}
});
});
});
}
为什么会发生这种情况,如何解决这个问题,以便我的回调被触发?
答案 0 :(得分:5)
在调试此代码时,我意识到FileReader
构造函数已被cordova和zone.js修补。根据我对zone.js修补的理解,它将每个“onProperty”(onload
,onloadend
,onerror
)更改为其addEventListener(...)
counterPart。
模块名称:
on_property
使用zone.js的行为:
target.onProp
将成为区域感知target.addEventListener(prop)
但是cordova没有使用dispatchEvent(...)
API来通知侦听器操作已经结束。
一种解决方案可能是从zone.js停用onProperty
模块,但可能会破坏角度行为。
所以这就是我应对这种情况的方法:
read(file: FileEntry, mode: "text" | "arrayBuffer" | "binaryString" | "dataURL" = "text"): Observable<ProgressEvent> {
return this.cdv.ready.flatMap(() => {
return Observable.create(observer => {
file.file(file => {
let FileReader: new() => FileReader = ((window as any).FileReader as any).__zone_symbol__OriginalDelegate
let reader = new FileReader();
reader.onerror = (evt: ErrorEvent) => {
this.zone.run(() => observer.error(evt)); //never triggered
};
reader.onload = (evt: ProgressEvent) => {
this.zone.run(() => observer.next(evt)); //never trigerred
};
switch (mode) {
case "text":
reader.readAsText(file);
break;
case "arrayBuffer":
reader.readAsArrayBuffer(file);
break;
case "binaryString":
reader.readAsBinaryString(file);
break;
case "dataURL":
reader.readAsDataURL(file);
break;
}
});
});
});
}
这里的秘密是zone.js将原始构造函数保留在__zone_symbol__OriginalDelegate
属性中,因此调用它实际上会直接调用Cordova的FileReader
而不使用zone.js补丁。
这个解决方案很脏,我有openned an issue on zone's repository
与FileWriter
有同样的问题(它在内部调用FileReader
)所以我写了这个小垫片:
function noZonePatch(cb: () => void) {
const orig = FileReader;
const unpatched = ((window as any).FileReader as any).__zone_symbol__OriginalDelegate;
(window as any).FileReader = unpatched;
cb();
(window as any).FileReader = orig;
}
然后将我的调用包装成读/写操作:
write(file: FileEntry, content: Blob) {
return this.cdv.ready.flatMap(() => {
return Observable.create((out: Observer<ProgressEvent>) => {
file.createWriter((writer) => {
noZonePatch(() => {
writer.onwrite = (evt: ProgressEvent) => {
this.zone.run(() => {
out.next(evt);
out.complete();
});
};
writer.onerror = (evt) => {
this.zone.run(() => out.error(evt));
};
writer.write(content); // this is where FileReader is called internally
})
}, err => out.error(err));
});
});
}
read(file: FileEntry, mode: ReadMode = "text"): Observable<ProgressEvent> {
return this.cdv.ready.switchMap(() => Observable.create((observer: Observer<ProgressEvent>) => {
file.file(file => {
noZonePatch(() => {
let reader = new FileReader();
reader.onerror = (evt: ErrorEvent) => {
this.zone.run(() => observer.error(evt));
};
reader.onload = (evt: ProgressEvent) => {
this.zone.run(() => observer.next(evt));
};
switch (mode) {
case "text":
reader.readAsText(file);
break;
case "arrayBuffer":
reader.readAsArrayBuffer(file);
break;
case "binaryString":
reader.readAsBinaryString(file);
break;
case "dataURL":
reader.readAsDataURL(file);
break;
}
});
});
}));
}
答案 1 :(得分:1)
如果您使用的是ionic / cordova,则不需要@disante的HackFileReader解决方案(我实际使用过)
您需要做的两件事,首先是确保您具有最新的zone.js。
npm install --save zone.js@latest
第二,您需要确保index.html
添加了cordova.js
之后 build/polyfills.js
答案 2 :(得分:0)
聚会晚了一些,但是我不得不解决一个Ionic3 / Angular4项目,但确实遇到了这个问题,我发现the answer from @n00dl3在点上,但是在全局服务中创建了一个modelState.Remove("Password")
实例。因为有时区域尚未修补FileReader
FileReader
对象,所以找不到window
。
所以我总是要获得正确的类,是通过一个小的工厂函数返回一个__zone_symbol__OriginalDelegate
实例:
FileReader
并使用它就可以了:
function HackFileReader(): FileReader {
const preZoneFileReader = ((window as any).FileReader as any).__zone_symbol__OriginalDelegate;
if (preZoneFileReader) {
console.log('%cHackFileReader: preZoneFileReader found creating new instance', 'font-size:3em; color: red');
return new preZoneFileReader();
} else {
console.log('%cHackFileReader: NO preZoneFileReader was found, returning regular File Reader', 'font-size:3em; color: red');
return new FileReader();
}
}
我希望它能对某人有所帮助