Best Angular practice for rendering canvas only after images are loaded

Discussão em 'Angular' iniciado por Gene, Outubro 22, 2024.

  1. Gene

    Gene Guest

    I know Typescript/Javascript pretty well, but am working on my first Angular app. It has a big component (a CAD drafting board) that includes a few canvases. Images are loaded programmatically.

    What's a clean, scalable way to ensure images are loaded before ever trying to render with ctx.drawImage()? Seems like this ought to be a solved problem, but I'm finding only fragments that look like kludges.

    Some use Promises, but NgAfterViewInit() is not async. I've read you can just declare it so anyway and call await on a Promise that's doing the loading, but this seems ugly and error prone.

    Maybe something old school like this?

    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"));



    I just don't want to pick a bad pattern that will need to be replaced later when there's much more code.

    Continue reading...

Compartilhe esta Página