htmlcssangulartypescriptangular-signals

Input based signal check gives not a function error


I have an input signal which I take and set as attribute but in browser I see an error

this.width is not a function

export class SkeletonComponent implements OnInit {
  readonly width = input<string>('');

  ngOnInit(): void {
    if (isPlatformServer(this.platformId)) {
      return;
    }

    const host = this.host.nativeElement;
    host.style.setProperty('--app-skeleton-width', this.width() || '100%');

Solution

  • The reason you might be getting this error, is because the ngOnInit is execute in a context (scope) somewhere other than your component scope, The code shared does not seem to have any issues, but you can achieve this feature using lesser code using @HostBinding or host demonstrated below.


    Define the width CSS variable using host property of @Component params:

    @Component({
      ...
      host: {
        'style.--app-skeleton-width': 'width()',
      },
    })
    

    Here we can define the CSS property using 'style.--app-skeleton-width' and execute the signal inside a string.

    We can then use the transform property to set a default value.

    readonly width = input<string, string>('', {
      transform: (value: string) => value || '100%',
    });
    

    Or we can simply define the width as the default value.

    readonly width = input<string>('100%');
    

    Define the @HostBinding decorator and execute the signal inside:

    We can define the @Hostbinding and return the signal value.

    @HostBinding('style.--app-skeleton-width')
    get skeletonWidth() {
      // if (isPlatformServer(this.platformId)) {
      //   return;
      // }
      return this.width() || '100%';
    }
    

    We can achieve this using the two methods, below is a working example for your reference:


    Full Code:

    import { Component, HostBinding, input } from '@angular/core';
    import { bootstrapApplication } from '@angular/platform-browser';
    
    @Component({
      selector: 'app-child2',
      template: `
        this is the child
        <div style="width: var(--app-skeleton-width)">
          I got my width from --app-skeleton-width
        </div>
      `,
      styles: [
        `div {
          background-color: lightblue;
        }`,
      ],
      host: {
        'style.--app-skeleton-width': 'width()',
      },
    })
    export class Child2 {
        readonly width = input<string, string>('', {
          transform: (value: string) => value || '100%',
        });
    }
    
    @Component({
      selector: 'app-child',
      template: `
        this is the child
        <div style="width: var(--app-skeleton-width)">
          I got my width from --app-skeleton-width
        </div>
      `,
      styles: [
        `div {
          background-color: lightblue;
        }`,
      ],
    })
    export class Child {
      readonly width = input<string>('');
    
      @HostBinding('style.--app-skeleton-width')
      get skeletonWidth() {
        // if (isPlatformServer(this.platformId)) {
        //   return;
        // }
        return this.width() || '100%';
      }
    }
    @Component({
      selector: 'app-root',
      imports: [Child, Child2],
      template: `
        <app-child [width]="'500px'"/>
        <br/>
        <br/>
        <app-child2 [width]="'300px'"/>
      `,
    })
    export class App {
      name = 'Angular';
    }
    
    bootstrapApplication(App);
    

    Stackblitz Demo