Angular 4.x + Cordova: FileReader не работает тихо (белый экран смерти)

У меня есть приложение Angular 4.3 + Cordova, которое раньше работало очень хорошо. Но теперь при запуске приложения появляется пустой экран, и больше ничего не происходит.

Пройдя некоторое время, я понял, откуда это происходит:

моя домашняя страница защищена 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;
                }
            });
        });
    });
}

Почему это происходит, и как я могу справиться с этим, чтобы мои обратные вызовы срабатывали?

1 ответ

Решение

При отладке этого кода я понял, что FileReader конструктор был исправлен как кордовой, так и zone.js. Из того, что я понял в отношении исправления zone.js, это то, что оно меняет каждое "onProperty" (onload,onloadend,onerror) к своему addEventListener(...) собрат.

Название модуля:

on_property

Поведение с zone.js:

target.onProp станет зоной осведомленности target.addEventListener(prop)

источник

Но Кордова не использует dispatchEvent(...) API для оповещения слушателей закончился.

Одним из решений может быть отключение onProperty модуль из zone.js, но он может нарушить поведение angular.

Вот как я справился с ситуацией:

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 свойство, так что называя это на самом деле вызовет Кордовы FileReader напрямую без патча zone.js.

Это грязное решение, я открыл проблему в репозитории зоны

Редактировать:

Была такая же проблема с 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;
                }
            });
        });
    }));
}

Что-то поздно для вечеринки, но мне пришлось исправить проект Ionic3/Angular4 с этой точной проблемой, и я обнаружил, что ответ от @n00dl3 был подходящим,но есть что-то вроде состояния гонки, когда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();
  }
}

и для его использования просто выполните:

const reader = HackFileReader();

Надеюсь, это кому-то поможет

Если вы используете ionic/cordova, нет необходимости в решении HackFileReader от @disante (которое я фактически использовал)

Вам нужно сделать две вещи. Во-первых, убедитесь, что у вас самая последняя версия файла zone.js.

npm install --save zone.js@latest

Во-вторых, вам нужно убедиться, что ваш index.html добавляет cordova.js ПОСЛЕ build/polyfills.js

Другие вопросы по тегам