Example of my async Angular code (async chain with Fetch site icon plus async save to storage) with ActivityIndicator on frontend
This is very basic demonstration how to combine Promise service one to another and how to show Activity indicator at the same time on Frontend. First service is simple JS Fetch API to receive Blob from Internet. >
1: import { Injectable } from "@angular/core";
2:
3: @Injectable({
4: providedIn: "root",
5: })
6: export class IconService {
7: /**
8: * @template {string} url - 'https://www.vb-net.com/'
9: */
10: async getIconBlobFromUrl(url: string): Promise<Blob> {
11: return await fetch(
12: "https://s2.googleusercontent.com/s2/favicons?domain_url=" + url
13: )
14: .then((response) => {
15: let Blob = response.Blob();
16: console.log(
17: `Read Icon from ${url}, Response.Status=${response.status}`
18: );
19: return Blob;
20: })
21: .then((Blob) => {
22: console.log(`Icon.size=${Blob.size}`);
23: return Blob;
24: })
25: .catch((err) => {
26: console.log(`Fetch error: ${err}`);
27: return new Blob();
28: });
29: }
Main point of difficulties for me was to understand that Promise of Blob can return only second then in line 21-24, not first then! Because first than return only Promise of Response. Next key point of this puzzle is extract Image from Blob. Need to understand what is Blob.
Next service was a writing to Android phone FileSystem, my first attempt was wrong! But this is, of course, joke and baby issue.
More serious challenge was a build correct Promise chain and understand what we can write only binary array, therefore we need firstly extract binary array from Blob. As you can see on my code, I have created test Blob firstly in order to isolate one service from another and simulate Promise of Blob.
1: import { Injectable } from "@angular/core";
2: import { knownFolders } from "@nativescript/core";
3: import {
4: Dialogs,
5: EventData,
6: File,
7: Folder,
8: TextField,
9: path,
10: } from "@nativescript/core";
11:
12: @Injectable({
13: providedIn: "root",
14: })
15: export class FileService {
16: /**
17: * @template {string} fileName - 'vb-net.ico'
18: * @template {Promise<Blob>} - this.http.get(url, { responseType: 'Blob' }).subscribe(data => { ... })
19: */
20: async writeIcon(
21: fileName: string,
22: binarySource: Promise<Blob>
23: ): Promise<void> {
24: const appRootFolder = knownFolders.currentApp();
25: const iconPath = path.join(appRootFolder.path, "icons");
26: const iconFolder: Folder = Folder.fromPath(iconPath);
27: const IconFolderExists: boolean = Folder.exists(iconPath);
28: const icoFilePath = path.join(iconFolder.path, fileName);
29: const icoFileExists = File.exists(icoFilePath);
30:
31: let testBinarySource = this.getRandomBinaryData(50);
32: //console.log(testBinarySource.text());
33: let testBinarySourcePromise = new Promise<Blob>((resolve, reject) => {
34: resolve(testBinarySource);
35: });
36:
37: console.log(
38: `iconPath=${iconPath}, IconFolderExists=${IconFolderExists}, icoFilePath=${icoFilePath}, icoFileExists=${icoFileExists} `
39: );
40: const icoFile = iconFolder.getFile(fileName);
41: if (icoFileExists) {
42: icoFile
43: .remove()
44: .then((res: boolean) => {
45: console.log(`Replaced ${res}`);
46: return this.writeBinary(icoFile, binarySource);
47: //return this.writeBinary(icoFile, testBinarySourcePromise);
48: })
49: .catch((err) => {
50: console.log(`Delete error ${err}`);
51: throw `Delete file ${icoFilePath} error: ${err}`;
52: });
53: } else {
54: return this.writeBinary(icoFile, binarySource);
55: //return this.writeBinary(icoFile, testBinarySourcePromise);
56: }
57: }
58:
59: async writeBinary(icoFile: File, binarySource: Promise<Blob>): Promise<void> {
60: Promise.resolve(binarySource).then((data) =>
61: //NS write require convert the Blob object into Byte array.
62: icoFile.write(this.convertBlobToByteArray(data)).catch((e) => {
63: throw `Write file ${icoFile.path} error: ${e}`;
64: })
65: );
66: }
67:
68: convertBlobToByteArray(Blob: Blob): Array<any> {
69: const ret = (Array as any).create("byte", Blob.size);
70: for (let i = 0; i < ret.length; i++) {
71: ret[i] = (Blob as any)._buffer[i];
72: }
73: return ret;
74: }
75:
76: getRandomBinaryData(len: number): Blob {
77: const array = new Uint8Array(len).map((v, i) => this.getRandomInt(1, 255));
78: return new Blob([array], { type: "application/octet-stream" });
79: }
And last point of this puzzle is Page code, firstly we need to define variable what mark that asynchronous code is executing (IsFeatching) and inject services.
And than create this code
421: readIcon() {
422: let icon = new IconService();
423: this.isFetching = true;
424: const finished = new Promise<void>((resolve, reject) => {
425: resolve(
426: this.fileService.writeIcon(
427: "tst.ico",
428: icon.getIconBlobFromUrl(this.urlElementRef.nativeElement.text)
429: )
430: );
431: })
432: .then(() => {
433: this.isFetching = false;
434: })
435: .catch(() => {
436: this.isFetching = false;
437: });
And finally we need to place Android Activity Indicator on the center of screen.
That's it! Happy programming!
Update. In final version I change processing Blob to just Http.getImage. Pay attention how I pass reference to nativeElement_src_ref to avoid Closure problem.
1: import { Injectable } from "@angular/core";
2: import { path, Folder, knownFolders, Http } from "@nativescript/core";
3:
4: interface successCallback {
5: (isFetching: boolean, iconPath: string, nativeElement_src_ref: any, This:any): void;
6: }
7:
8: interface failCallback {
9: (isFetching: boolean, color: string, randomColor: string): void;
10: }
11:
12: @Injectable({
13: providedIn: "root",
14: })
15: export class SaveIcon {
16:
17: saveIcon(
18: url: string,
19: name: string,
20: successCallback: successCallback,
21: failCallback: failCallback,
22: isFetching: boolean,
23: nativeElement_src_ref: any,
24: randomColor: string,
25: This:any
26: ): void {
27: const iconPath = this.getfullIconPath(`${name}.png`);
28: try {
29: Http.getImage(
30: "https://s2.googleusercontent.com/s2/favicons?domain_url=" + url
31: )
32: .then((image) => {
33: if (image) {
34: return image.saveToFileAsync(iconPath, "png").then((success) => {
35: return success;
36: });
37: } else {
38: return false;
39: }
40: })
41: .catch(() => {
42: return failCallback(isFetching, this.getRandomColor(), randomColor);
43: })
44: .then((success) => {
45: if (success) {
46: successCallback(isFetching, iconPath, nativeElement_src_ref, This);
47: } else {
48: failCallback(isFetching, this.getRandomColor(), randomColor);
49: }
50: })
51: .catch(() =>
52: failCallback(isFetching, this.getRandomColor(), randomColor)
53: );
54: } catch (error) {
55: failCallback(isFetching, this.getRandomColor(), randomColor);
56: }
57: }
58:
59: getfullIconPath(iconName: string) {
60: const appRootFolder = knownFolders.currentApp();
61: const iconPath = path.join(appRootFolder.path, "icons");
62: const iconFolder: Folder = Folder.fromPath(iconPath);
63: return path.join(iconFolder.path, iconName);
64: }
65:
66: getRandomColor(): string {
67: var letters = "0123456789ABCDEF";
68: var color = "#";
69: for (var i = 0; i < 6; i++) {
70: color += letters[Math.floor(Math.random() * 16)];
71: }
72: return color;
73: }
74: }
|