我非常了解 Typescript/Javascript,但正在开发我的第一个 Angular 应用程序。它有一个大组件(CAD 绘图板),其中包括一些画布。图像以编程方式加载。
有什么干净、可扩展的方法来确保在尝试使用
ctx.drawImage()
渲染之前加载图像?看起来这应该是一个已解决的问题,但我只找到看起来像拼凑的片段。
有些使用 Promises,但
NgAfterViewInit()
不是异步的。我读过你可以这样声明它,然后在正在加载的 Promise 上调用 await
,但这看起来很难看并且容易出错。
也许像这样的老派东西?
class ImagesLoader {
private readonly images: Map<string, HTMLImageElement>;
private readonly pendingActions: ((images: Map<string, HTMLImageElement>) => void)[] = [];
public readonly errors = new Set<string>();
private remainingCount: number;
constructor(urls: string[]) {
this.remainingCount = urls.length;
this.images = new Map<string, HTMLImageElement>(
((loader) => {
return urls.map(url => {
const image = new Image();
image.onload = () => {
// On last load, execute pending actions.
if (--loader.remainingCount == 0) {
loader.pendingActions.forEach(action => action(loader.images));
loader.pendingActions.length = 0; // Not necessary, but nice.
}
}
image.onerror = () => loader.errors.add(url);
image.src = url;
return [url, image];
})
})(this));
}
public invokeAfterLoaded(action: (images: Map<string, HTMLImageElement>) => void): void {
if (this.remainingCount == 0) {
action(this.images);
} else {
this.pendingActions.push(action);
}
}
}
// Start loading the images. Put this in the component constructor.
this.loader = new ImagesLoader([
'https://www.jqwidgets.com/wp-content/design/i/logo-jqwidgets.svg',
'https://www.greencastonline.com/assets/img/logo.png']);
// Then we can invoke rendering in NgAfterViewInit, possibly delayed until loaded,
// and re-render as needed with the same loader.
this.loader.invokeAfterLoaded((images) => console.log("first"));
this.loader.invokeAfterLoaded((images) => console.log("second"));
我只是不想选择一个不好的模式,当有更多代码时需要替换它。
也许使用信号和可观察量?
@Component({
/* ... */
})
export class MyComponent {
$urls = input.required<string[]>();
private images$ = toObservable(this.$urls).pipe(
map((urls) =>
urls.map(
(url) =>
new Promise((res, rej) => {
const image = new Image();
img.onload = () => {
// Do your thing then
res(image);
};
image.onerror = rej;
image.src = url;
}),
),
),
map((promises) => Promise.allSettled(promises)),
switchMap((images) => from(images)),
);
$images = toSignal(this.images$, { initialValue: [] });
constructor() {
effect(() => {
const images = this.$images();
if (!images.length) return;
console.log('Images loaded', images);
});
}
}