skip to Main Content

I want to nest an Angular component inside an SVG container. However, the CSS selector tag <app-square> turns red and won’t allow it. It works fine if I change the <svg> tags to a <div> or another container, but I think that I need an SVG. Any tips for getting around this?

Here is my code:

<svg *ngFor="let row of boardArray">
    <app-square *ngFor="let sqare of row" [reciever] = "sqare"></app-square>
</svg>

I’m also passing data to the child objects, but I think that’s unrelated. The CSS selector tags still show up red even with just:

<svg>
    <app-square ></app-square>
</svg>

I’m extremely new to HTML, CSS, and Angular. Any help is appreciated. Thanks.

Edit:

The reason that I think I need an SVG container is this. The HTML view template for the SquareComponent contains only an SVG <rect> element. It looks like this:

<svg>
    <rect [attr.x] = "reciever.x" [attr.y] = "reciever.y" width="100" height="100" fill = "brown"></rect>
</svg>

I want the parent component to display the squares in an 8×8 grid, with each square adjacent to the next (I’m trying to make a chess board). The Square Component’s properties reciever.x and reciever.y hold the x and y coordinates of each square, but I’m under the impression that all the Square Components (displayed as svg <rect>s) need to be in a singular SVG container in order to position them exactly adjacent to one another.

4

Answers


  1. UPDATE

    Since we need to use components for a child of svg I am using the component with the attribute selector [rectangle] then using the @HostBinding to set the individual properties of the squares this will solve your issue!

    The remaining of the workings is explained in the code below!

    We need to use @for (same as *ngFor) to loop through identical length of rows and columns which I generate using new Array(this.chessBoardSize).fill(null), then we can run two @for for the rows and columns and generate the rectangles by multiplying the unit with of the rectange by the index, to create the chessboard.

    For the Colors we use the logic value % 2 === 0 to identify if the row index is odd or even and then we use the same logic to determine if the column index is odd or even to finally generate the chessboard.

    main.ts

    import { CommonModule } from '@angular/common';
    import { Component, HostBinding, Input } from '@angular/core';
    import { bootstrapApplication } from '@angular/platform-browser';
    import 'zone.js';
    import { ChildComponent } from './child/child.component';
    
    @Component({
      selector: '[rectangle]',
      standalone: true,
      imports: [CommonModule],
      template: ``,
    })
    export class Rectangle {
      @Input() rowIndex = 0;
      @Input() columnIndex = 0;
      @Input() chessSquareSize = 0;
      @HostBinding('attr.x') get x() {
        return this.rowIndex * this.chessSquareSize;
      }
      @HostBinding('attr.y') get y() {
        return this.columnIndex * this.chessSquareSize;
      }
      @HostBinding('style.fill') get fill() {
        return this.getColorType(this.rowIndex, this.columnIndex) ? 'red' : 'black';
      }
    
      getColorType(rowIndex: number, columnIndex: number) {
        return rowIndex % 2 === 0 ? columnIndex % 2 === 0 : columnIndex % 2 !== 0;
      }
    }
    
    @Component({
      selector: 'app-root',
      standalone: true,
      imports: [ChildComponent, CommonModule, Rectangle],
      template: `
        <svg>
          @for(squareRow of rows; let rowIndex = $index;track rowIndex) {
            @for(square of columns; let columnIndex = $index;track columnIndex) {
              <rect [attr.x] = "rowIndex * chessSquareSize" [attr.y] = "columnIndex * chessSquareSize" width="10" height="10" [ngStyle]="{ fill : getColorType(rowIndex, columnIndex) ? 'red' : 'black'}"></rect>
            }
          }
        </svg>
        <!-- using child component -->
        <hr/>
        <svg>
          @for(squareRow of rows; let rowIndex = $index;track rowIndex) {
            @for(square of columns; let columnIndex = $index;track columnIndex) {
              <rect rectangle width="10" height="10" [rowIndex]="rowIndex" [columnIndex]="columnIndex" [chessSquareSize]="chessSquareSize"></rect>
            }
          }
        </svg>
      `,
    })
    export class App {
      chessBoardSize = 10;
      chessSquareSize = 10;
    
      get rows() {
        return new Array(this.chessBoardSize).fill(null);
      }
    
      get columns() {
        return new Array(this.chessBoardSize).fill(null);
      }
    
      getColorType(rowIndex: number, columnIndex: number) {
        return rowIndex % 2 === 0 ? columnIndex % 2 === 0 : columnIndex % 2 !== 0;
      }
    }
    
    bootstrapApplication(App);
    

    Stackblitz Demo

    Login or Signup to reply.
  2. You will have a lot of work to do when doing drap-drop to move pieces around within one SVG

    Making the chessboard with 64 DIVs in a CSS Grid and the individual pieces with SVG will be easier HTML and you can use the native Drap-Drop API

    Or use my existing Web Component:

     <script src="https://chessmeister.github.io/elements.chessmeister.js"></script>
    
     <chessmeister-board 
           fen="rnbqkbnr/
                pppppppp/
                8/
                8/
                8/
                8/
                PPPPPPPP/
                RNBQKBNR" >
        </chessmeister-board>

    Source: https://chessmeister.github.io/

    Login or Signup to reply.
  3. Using a directive (Stolen the idea of Naren Murali)

    @Directive({
      selector: '[square]',
      standalone:true,
    })
    export class SquareDirective implements AfterViewInit{
      @Input() rowIndex=0;
      @Input() columnIndex=0;
      @Input() chessSquareSize=10;
      constructor(private el:ElementRef){}
      ngAfterViewInit()
      {
        this.el.nativeElement.setAttribute('x',this.columnIndex*this.chessSquareSize)
        this.el.nativeElement.setAttribute('y',this.rowIndex*this.chessSquareSize)
        this.el.nativeElement.setAttribute('width',this.chessSquareSize)
        this.el.nativeElement.setAttribute('height',this.chessSquareSize)
        this.el.nativeElement.style.fill=this.rowIndex % 2 === 0 ? 
                                         this.columnIndex % 2 === 0 ? 'red':'black':
                                         this.columnIndex % 2 === 0 ? 'black':'red'
      }
    }
    
    <svg>
      @for(squareRow of rows; let rowIndex = $index;track rowIndex) {
        @for(square of columns; let columnIndex = $index;track columnIndex) {
          <rect square [rowIndex]="rowIndex" 
                       [columnIndex]="columnIndex" 
                       [chessSquareSize]="chessSquareSize" >
          </rect>
        }
      }
    </svg>
    
    Login or Signup to reply.
  4. In the old days you could use <svg:svg></svg:svg> to let the compiler know it is indeed an SVG element. Try using that, see if it works ?

    Otherwise, you can create SVG templates for your components instead of HTML ones. I think this would be the closest you can get to what you want to achieve.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search