Home > Back-end >  Type 'string[]' is not assignable to type 'listData[]'
Type 'string[]' is not assignable to type 'listData[]'

Time:09-22

I am working on a generic component where the list view can be utilized by other components. But the issue is data format is different for each component. In my project, I can't use type any[] which will cause linting issues that I can't skip also.

list-view.component.html(shared component)

          <div *ngFor="let list of lists">
              <ng-template #defaultTemplate>
                  <p> {{list}}</p>
             </ng-template>
             <ng-container
              [ngTemplateOutlet]="optionTemplate || defaultTemplate"
              [ngTemplateOutletContext]="{ $implicit: list}"
             >
            </ng-container>
          </div>

list-view.component.ts

          import {Component,ContentChild,EventEmitter,Input,Output,TemplateRef} from '@angular/core';

          export interface listData{
             id: number;
             brand: string;
             model: string;
             url: string;
          }

          @Component({
             selector: 'app-my-selector',
             templateUrl: './my-selector.component.html',
          })
           export class MySelectorComponent {
               @Input() lists: listData;  **// can't use any[], because of linting issue.**
               @ContentChild('optionTemplate', { static: false }) optionTemplate: TemplateRef;
               constructor() {}
           }

test1.component.html

           <div >
              <app-my-selector [lists]="list">
                  <ng-template #optionTemplate let-list>
                       <img src="{{list.url}}" alt="{{list.model}}">
                       <p>{{list.brand}}: {{list.model}}</p>
                  </ng-template>
              </app-my-selector>
            </div>

test1.component.ts

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

             export interface listData{
               id: number;
               brand: string;
               model: string;
               url: string;
             }
             @Component({
                  selector: 'app-test1',
                  templateUrl: './test1.component.html',
             })
             export class Test1Component {
                 list:listData[];
                 constructor() {}
                 ngOnInit() {
                     this.list = [
                      {
                          id: 1,
                          brand: 'TATA',
                          model: 'Indica - 2008',
                          url: '/indica-img.jpg'
                      },
                      {
                          id: 2,
                          brand: 'Suzuki',
                          model: 'Swift - 2011',
                          url: '/swift-img.jpg'
                      }
                   ];
                 }
                }

test2.component.html

                   <div >
                      <app-my-selector [lists]="list"></app-my-selector>
                   </div>

test2.component.ts

                   import { Component, OnInit } from "@angular/core";
                   @Component({
                        selector: "app-test2",
                        templateUrl: "./test2.component.html",
                   })
                   export class Test2Component {
                      list: string[];  **// this is where causing the issue.**
                      constructor() {}
                      ngOnInit() {
                         this.list = ['Harley Davidson','Bajaj','Triumph'];
                      }
                    }

If I run the above code I am getting Type 'string[]' is not assignable to type 'listData[]' in test2.component.html. Because the test1 component is an array of object data & the test2 component is an array of data. So without using any[] how can I achieve this?

CodePudding user response:

As suggested in one of the comments, try using a generic type.

Try something like this:

@Component({
   selector: 'app-my-selector',
   templateUrl: './my-selector.component.html',
})
export class MySelectorComponent<T> {
   @Input() lists: T[];  // can't use any[], because of linting issue.
   @ContentChild('optionTemplate', { static: false }) 
   optionTemplate: TemplateRef<T>;
   constructor() {}
}

PS: You may find this article helpful for future reference on creating reusable components.

CodePudding user response:

I was not able to reproduce this issue but I have identified that there were many improvements I could suggest...

import { CommonModule } from '@angular/common';
import { Component, ContentChild, Input, NgModule, TemplateRef } from '@angular/core';

// capitalize ListData instead of listData
export interface ListData {
  id: number;
  brand: string;
  model: string;
  url: string;
}

@Component({
  selector: 'test-parent',
  // put the container class directly to the host component (give it display: block)
  host: { class: 'container' },
  template: `
    <!-- remove unnecessary div node to add a class -->
    <test-child [lists]="list">
      <ng-template #optionTemplate let-list>
        <!-- no need to use interpolation {{ }} to pass attributes -->
        <img [src]="list.url" [alt]="list.model" />
        <p>{{ list.brand }}: {{ list.model }}</p>
      </ng-template>
    </test-child>
  `,
})
export class TestComponentParent {
  list: ListData[] = [
    {
      id: 1,
      brand: 'TATA',
      model: 'Indica - 2008',
      url: '/indica-img.jpg',
    },
    {
      id: 2,
      brand: 'Suzuki',
      model: 'Swift - 2011',
      url: '/swift-img.jpg',
    },
  ];

  // ngOnInit() {}; no need to wait fot this lyfecycle to initialize a component property
}

@Component({
  selector: 'test-child',
  template: `
    <!-- you can use structural directive and attribute directive in the same container, no need to create a div node -->
    <ng-container
      *ngFor="let list of lists"
      [ngTemplateOutlet]="optionTemplate || defaultTemplate"
      [ngTemplateOutletContext]="{ $implicit: list }"
    >
    </ng-container>

    <!-- remove this template from the loop no need to redefine the template for each iteration -->
    <ng-template #defaultTemplate let-list>
      <p>{{ list }}</p>
    </ng-template>
  `,
})
export class TestComponentChild {
  // lists should be an array: ListData[], not an instance of ListData
  @Input() lists: ListData[];

  // most angular versions have the static property as false by default, consider removing if useless
  @ContentChild('optionTemplate', { static: false }) optionTemplate: TemplateRef<any>;
}

@NgModule({
  declarations: [TestComponentParent, TestComponentChild],
  imports: [CommonModule],
})
export class FeatureModule {}
  • Related