简体   繁体   中英

Manipulating unrendered DOM elements for export in Angular

I created an Angular service to export an SVGElement for the user. To put it simply, it builds an SVG and appends symbols to the <defs> . The service then returns this SVG to the component using a Promise, and the component copies it to the clipboard or exports it as a file.

My problem is that the exported SVG element is empty at the time the component tries to export it. If I insert a setTimeout() around the resolve(svgElement) in the service, it works.

How can I manipulate these dynamically generated DOM elements in a more synchronous manner? The SVG is never going to be rendered for the user.

Here is some simplified code to try and illustrate the functionality.

\\ service.ts
public exportToSVG(ids: string[]): Promise<SVGElement> {
   return new Promise((resolve, reject) => {
      const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
      const defs = document.createElement('defs');

      ids.forEach(async id => {
         // use another method to get the symbol associated with the id
         const symbol = await this._getSymbolByString(id);
         defs.appendChild(symbol);
      });

      svg.appendChild(defs);
      resolve(svg);
   });
}

\\ component.ts
public copyToClipboard(ids: string[]) {
   this.myService.exportToSVG(ids).then(svg => {
      // this only copies `<svg><defs></defs></svg>`, unless a setTimeout is used
      this.clipboardService.copyFromContent(svg.outerHTML);
   });
}

Try to use innerHTML instead:

\\ component.ts
public copyToClipboard(ids: string[]) {
   this.myService.exportToSVG(ids).then(svg => {
      this.clipboardService.copyFromContent(svg.innerHTML);
   });
}

Angular has the asynchronous nature. When you apply the Foreach loop, angular do not wait for the response and execute the next operation, that's why you could not get the defs variable. You should put this code into the condition which is fired after foreach loop is completed.

 \\ service.ts public exportToSVG(ids: string[]): Promise<SVGElement> { return new Promise((resolve, reject) => { const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); const defs = document.createElement('defs'); let lengthIds = is.length; let count = 0; ids.forEach(async id => { // use another method to get the symbol associated with the id const symbol = await this._getSymbolByString(id); defs.appendChild(symbol); count++; }); // check length of total number of elements in array and its count if(count === lengthIds){ svg.appendChild(defs); resolve(svg); } }); } \\ component.ts public copyToClipboard(ids: string[]) { this.myService.exportToSVG(ids).then(svg => { // this only copies `<svg><defs></defs></svg>`, unless a setTimeout is used this.clipboardService.copyFromContent(svg.outerHTML); }); }

So i think it would work for you.

I resolved this by chaining Promises, not using async... await.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM