Declare a component with generic type

48,542

Solution 1

You can also access the Type parameter through the ViewChild like this:

export class Bazz {
  name: string;

  constructor(name: string) {
    this.name = name;   
  }
}

@Component({
  selector: 'app-foo',
  template: `<div>{{bazz?.name}}</div>`,
  exportAs: 'appFoo'
})
export class FooComponent<T> {
  constructor() {}
  private _bazz: T;

  set bazz(b: T) {
    this._bazz = b;
  }

  get bazz(): T {
   return this._bazz;
  }
}

@Component({
  selector: 'app-bar',
  template: `<app-foo #appFoo></app-foo>`,
  styleUrls: ['./foo.component.scss'],
})
export class BarComponent<T> implements OnInit {
  @ViewChild('appFoo') appFoo: FooComponent<Bazz>;

  constructor() {}

  ngOnInit() {
    this.appFoo.bazz = new Bazz('bizzle');
    console.log(this.appFoo.bazz);
  }
}

Solution 2

You can declare it, but cannot use it directly. You can do something like this:

export abstract class Form<T> implements OnInit, OnChanges {
  someMethod() { throw 'Dont use directly' }
  otherMethod() { return 'Works!'; }
  // Note that below will cause compilation error
  //   TypeError: Object prototype may only be an Object or null: undefined
  // You cannot use protected in this usecase
  protected anotherMethod() { }
}

@Component({})
export class ModelOneForm extends Form<ModelOne> {
  someMethod() { return this.otherMethod(); }
}

Solution 3

You can consider this way. Create an interface for the data such as following:

interface ListItem {
  info: string;
  ...
}

Transform the data that you want to list to comply with the interface and thus can be interpreted by the ListDataComponent. Your ListDataComponent can then list the data according to the properties in the interface.

import { Component, OnInit, Input } from '@angular/core';

@Component({
  selector: 'data-list',
  templateUrl: './data-list.component.html',
  styleUrls: ['./data-list.component.scss']
})
export class DataListComponent implements OnInit {
    @Input() public items: ListItem[];

    constructor() {
    }

    ngOnInit() {
    }
}
Share:
48,542
Strider
Author by

Strider

Updated on May 06, 2020

Comments

  • Strider
    Strider about 4 years

    Is it possible to declare a component with a generic type in Angular 4?

    The following code causes build errors:

    export class MyGenericComponent<T> implements OnInit {
        @Input()  data: BehaviorSubject<T[]>;
    
        //...
    }
    

    The error when executing ng serve is:

    ERROR in C:/.../my-generic.module.ts (5,10): Module '"C:/.../my-generic.component"' has no exported member 'MyGenericComponent'.
    

    Example:

    The following example is an attempt to implement a generic data table where @Input() data changes from one component 'calling this component' to another. The question is could BehaviorSubject<any[]> be changed to BehaviorSubject<T[]> where T would be the generic type passed to the component?

    @Component({
      selector: 'my-data-list',
      templateUrl: './data-list.component.html',
      styleUrls: ['./data-list.component.css']
    })
    export class DataListComponent implements OnInit {
      @Input()  data: BehaviorSubject<any[]>;
      @Output() onLoaded = new EventEmitter<boolean>();
    
      private tableDataBase : TableDataBase = new TableDataBase();
      private dataSource : TableDataSource | null;
    
      constructor() { }
    
      ngOnInit() {
        this.tableDataBase.dataChange = this.data;
        this.dataSource = new TableDataSource(this.tableDataBase);
        this.onLoaded.emit(true);
      }
    }
    
    class TableDataBase {
      dataChange: BehaviorSubject<any[]> = new BehaviorSubject<any[]>([]);
    
      get data(): any[] {
        return this.dataChange.value;
      }
    }
    
    class TableDataSource extends DataSource<any> {
    
      constructor(private tableDataBase: TableDataBase) {
        super();
      }
    
      connect(): Observable<any[]> {
        return Observable.of(this.tableDataBase.data);
      }
    
      disconnect() {}
    }
    
  • Strider
    Strider over 6 years
    This method works, but I am still looking for more generic form of a component: Instead of using ...extends Form<ModelOne>, I am looking for something like export class ModelOneForm extends Form<T>, and this code throws a compilation error with the message Cannot find name 'T'
  • KarolDepka
    KarolDepka over 6 years
    Hmm @Componet? ?
  • biolauri
    biolauri over 5 years
    @KarolDepka, should be a type; i fixed it. :)
  • Denis Itskovich
    Denis Itskovich almost 5 years
    So how to you use T type argument of the BarComponent? It's still unclear what do you expect to receive as an argument, when BarComponent is instantiated? It's make sense to use generic concrete class when you are controlling its instantiation (so you can control what argument to pass). But in case of component - it's the angular framework itself which instantiates it - and the framework is not aware of your parameter and will not be able to pass something making sense
  • ntziolis
    ntziolis about 4 years
    Actually angular is smart enough to figure out the T from inputs as well. Lets say you have Input() data: T in your generic component. When you use that component and assign [data]="someTypedDataVariable" the angular compiler will use the type of someTypedDataVariable to instanciate the generic component. This is how angular material table (based on cdk table) works. This paired with the angular language service will ALSO provide you with type safety + typeahead iniside mat-table column definitions. For reference: github.com/angular/components/blob/master/src/cdk/table/…