使用 SVG 模板注入动态组件

问题描述 投票:0回答:4
angular svg angular5
4个回答
1
投票

不确定模板,但可以在一些解决方法的帮助下通过内容投影动态注入 svg-s。

主要的解决方法是找到一种方法来删除组件的宿主元素,这可以通过创建如下指令来处理,该指令将处理某些元素的“展开”。如下所示,该指令执行类似于 js 中的展开运算符的操作,它获取其所附加的元素的所有子元素并将它们展开到父元素的级别

@Directive({
  selector: '[appRemoveHost]',
  standalone: true,
})
export class RemoveHostDirective {
  constructor(private el: ElementRef) {}

  //wait for the component to render completely
  ngOnInit() {
    var nativeElement: HTMLElement = this.el.nativeElement,
      parentElement: HTMLElement = nativeElement.parentElement;
    // move all children out of the element
    while (nativeElement.firstChild) {
      parentElement.insertBefore(nativeElement.firstChild, nativeElement);
    }
    // remove the empty element(the host)
    parentElement.removeChild(nativeElement);
  }
}

感谢这个问题和答案删除由角度组件创建的主机 HTML 元素选择器

之后,您只需为要使用的 SVG 元素创建包装器组件即可。

示例画布组件看起来像这样

@Component({
  selector: 'app-canvas',
  template: `<svg width="400" height="400">
                <ng-content></ng-content>
              </svg>`,
  standalone: true,
  imports: [CommonModule],
})
export class CanvasComponent {}

自定义 SVG 元素可以如下所示

@Component({
  selector: 'app-custom-rect,[app-custom-rect]',
  template: `<svg appRemoveHost>
              <rect  [attr.x]="x" [attr.y]="y" [attr.width]="width" [attr.height]="height" style="{{ style }}" />
             </svg>`,
  standalone: true,
  imports: [CommonModule, RemoveHostDirective],
})
export class CustomRectComponent {
  @Input() style = '';
  @Input() x = '';
  @Input() y = '';
  @Input() width = '';
  @Input() height = '';
}

这里值得一提的是,否则我们需要创建

svg
包装元素
Only void and foreign elements can be self-closed "rect"
作为 rect 元素只能存在于 svg-s 中。 正如此处所示,我们正在使用 appRemoveHost,这要归功于当我们的组件被渲染时, 包装器将被删除。

最后,在我们想要使用画布的地方,我们应该执行以下操作

<app-canvas>
  <app-custom-rect
    appRemoveHost
    [y]="'50'"
    [x]="'0'"
    [width]="'100'"
    [height]="'100'"
    [style]="'fill:red'"
  ></app-custom-rect>
  <app-canvas/>

这里我们再次需要使用 appRemoveHost 指令来删除 rect 组件的剩余包装。

在这里您可以找到工作 Stackblitz


1
投票

是否可以注入作为子 svg 元素的动态组件?

是的,当然。 Angular 已经支持这一点好几年了,但我不确定我是否已经看到了这一点的详细记录。

当你创建 Angular 组件时,你的选择器可以是很多东西。它不一定是一个标签。我们将利用这一点来使用属性。

首先,让我们构建托管 SVG 的主要组件:

import { Component } from '@angular/core';

@Component({
  selector: 'app-main',
  templateUrl: './main.component.html',
  styleUrls: ['./main.component.css'],
})
export class MainComponent {}

和 HTML:

<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
  <svg:g house></svg:g>
</svg>

正如您可能已经猜到的,诀窍就在这里:

<svg:g house></svg:g>
。我们创建一个组,它不会渲染任何内容。但我们给它添加了一个属性
house

然后要在那里“注入”组件,我们可以让组件选择器以带有组的 svg 标签和

house
属性为目标:

import { Component } from '@angular/core';

@Component({
  selector: 'svg:g[house]',
  templateUrl: './house.component.html',
  styleUrls: ['./house.component.css'],
})
export class HouseComponent {}

最后一块拼图几乎就是 SVG:要注入的部分

<svg:polygon points="25,10 40,30 10,30" style="fill:rgb(0,0,255);" />

<svg:rect width="30" height="30" x="10" y="30" style="fill:rgb(0,0,255);" />

如果我们查看渲染的 HTML:

<app-main _ngcontent-rcx-c227="" _nghost-rcx-c226=""
  ><svg
    _ngcontent-rcx-c226=""
    viewBox="0 0 100 100"
    xmlns="http://www.w3.org/2000/svg"
  >
    <g _ngcontent-rcx-c226="" house="" _nghost-rcx-c217="">
      <polygon
        _ngcontent-rcx-c217=""
        points="25,10 40,30 10,30"
        style="fill: rgb(0,0,255);"
      ></polygon>
      <rect
        _ngcontent-rcx-c217=""
        width="30"
        height="30"
        x="10"
        y="30"
        style="fill: rgb(0,0,255);"
      ></rect>
    </g></svg
></app-main>

没有我们内部组件的任何痕迹。当然,您可以根据需要拥有任意数量和嵌套的数量。您还可以通过将数据作为输入传递来参数化它们,就像使用任何其他组件或注入服务一样。

这是 Stackblitz 上的 现场演示


1
投票

jscw 使用本机 JavaScript Web 组件可能会更容易

  • <svg-floorplan>
    处理其innerHTML
  • 并在shadowDOM中生成
    <SVG>
  • 基于 HTMLUnknownElements
    <room>
    (打造出色的语义 HTML
    它声明了正确的 SVG 的
    x,y,width,height,fill
    属性
    <rect>
  • 基于
    <rect>
    标签可以定位在中间
  • innerHTML 中任何剩余的 SVG 元素都被移动到新的
    <SVG>
  • 您的 JavaScript 技能可以增加更多...
    <svg-floorplan viewBox="0 0 300 100">
      <style>text{fill:gold}</style>

      <room rect="0 0 200 50">living</room>
      <room rect="200 0 100 100 blue">kitchen</room>
      <room rect="0 50 100 50">bedroom</room>
      <room rect="100 50 100 50 green">bathroom</room>

      <circle fill="red" cx="25" cy="25" r="10"></circle>
    </svg-floorplan>

创造:

<svg-floorplan viewBox="0 0 300 100">
  <style>text{fill:gold}</style>
  <room rect="0 0 200 50">living</room>
  <room rect="200 0 100 100 blue">kitchen</room>
  <room rect="0 50 100 50">bedroom</room>
  <room rect="100 50 100 50 green">bathroom</room>
  <circle fill="red" cx="25" cy="25" r="10"></circle>
</svg-floorplan>

<script>
  customElements.define("svg-floorplan", class extends HTMLElement {
    connectedCallback() {
      setTimeout(() => { // make sure innerHTML is parsed
        let svg = `<svg viewBox="${this.getAttribute("viewBox")}">`;
        svg += `<rect x="0" y="0" width="100%" height="100%" fill="grey"/>`;
        this.querySelectorAll('room[rect]').forEach(rm => {
          let [x, y, width, height, fill="grey"] = rm.getAttribute("rect").split(" ");
          svg += `<rect x="${x}" y="${y}" width="${width}" height="${height}" stroke="black" fill="${fill}"/>`;
          let label = rm.innerHTML;
          svg += `<path id="${label}" pathLength="100" d="M${x} ${~~y+height/2}h${width}" stroke="none"></path>
           <text><textPath href="#${label}" startoffset="50" 
                           dominant-baseline="middle" text-anchor="middle">${label}</textPath></text>`;
          rm.remove();
        });
        svg += `</svg>`;
        this.attachShadow({mode:"open"}).innerHTML = svg;
        this.shadowRoot.querySelector("svg")
                       .insertAdjacentHTML("beforeend",this.innerHTML)
      })
    }
  })
</script>


-1
投票

文档可能会帮助您:

您可以在 Angular 应用程序中使用 SVG 文件作为模板。当您使用 SVG 作为模板时,您可以像使用 HTML 模板一样使用指令和绑定。使用这些功能动态生成交互式图形。

我建议你创建一个可以以 Angular 方式控制的 SVG 组件,对于动态模板显示,你可以做的是:

<div class="container" *appCanvasAnchor>
</div>
.container {
  width: 400px;
  height: 400px;
  svg {
    width: 100%;
    height: 100%;
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.