How to assign the correct type to an item interface that imports a fontawesome icon

4.6k Views Asked by At

I have an angular 11 project in development that uses angular-fontawesome and angular material icon. It contains a 'problem' model with an optional 'icon' property: string;'. The data constant "problem " contains an arrangement of several "problem " in whose property "icon": is a string, for example "donate", that refers to the "faDonate" icon imported from the fontawesome library.

My code:

comun.module.ts: common module that imports angular material icon and fontawesome

import { MatIconModule } from '@angular/material/icon';
...
// FontAwesome
import {
  FontAwesomeModule,
  FaIconLibrary,
} from '@fortawesome/angular-fontawesome';
import {
  faBars,
  faDonate,
  …
} from '@fortawesome/free-solid-svg-icons';
…
export class ComunModule {
  constructor(library: FaIconLibrary) {
    library.addIcons(
      faBars,
      faDonate,
...

problemas.model.ts

export interface Problema {
  id: number;
  grupo: string;
  idgrupo: number;
  subgrupo?: string;
  titulo: string;
  encabezado?: string;
  frase: string;
  icon?: string;
  ley: string;
  parrafounotitulo?: string;
  parrafounoparrafo?: string;
  parrafounoimg?: string;
  parrafodostitulo?: string;
  parrafodosparrafo?: string;
  parrafodosimg?: string;
  parrafotrestitulo?: string;
  parrafotresparrafo?: string;
  parrafotresimg?: string;
  parrafotablaa1?: string;
  parrafotablaa2?: string;
  parrafotablab1?: string;
  parrafotablab2?: string;
  parrafotablac1?: string;
  parrafotablac2?: string;
  parrafotablad1?: string;
  parrafotablad2?: string;
}

problema.component.html

<div class="mostrando">
      <mat-chip-list aria-label="Icono">
        <mat-chip>
          <h4>{{ "problemas.problemas.icon" | transloco }}:</h4>
          <mat-icon><fa-icon icon="{{ problema.icon }}"></fa-icon></mat-icon>
        </mat-chip>
      </mat-chip-list>
    </div>
  </div>
...

problema.component.ts

import {
  Component,
  OnInit,
  ChangeDetectionStrategy,
  OnDestroy,
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
// import { untilDestroyed } from 'ngx-take-until-destroy';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { filter, map, switchMap } from 'rxjs/operators';

import { ANIMACIONES_RUTA_ELEMENTOS } from 'src/app/nucleo/nucleo.module';

import { ProblemasQuery } from 'src/app/pages/problemas/state/problemas.query';
import { ProblemasService } from 'src/app/pages/problemas/state/problemas.service';

@UntilDestroy()
@Component({
  selector: 'bab-problema',
  templateUrl: './problema.component.html',
  styleUrls: ['./problema.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProblemaComponent implements OnInit, OnDestroy {
  rutaAnimacionesElementos = ANIMACIONES_RUTA_ELEMENTOS;
  problema$ = this.problemasQuery.selectEntity(this.problemaId);

  constructor(
    private activatedRoute: ActivatedRoute,
    private problemasQuery: ProblemasQuery,
    private problemasService: ProblemasService
  ) {}

  ngOnInit(): void {
    this.activatedRoute.paramMap.pipe(
      map((params) => params.get('id')),
      filter((id) => !this.problemasQuery.hasEntity(id)),
      untilDestroyed(this),
      switchMap((id) => this.problemasService.getProblemaPorId(id))
    );
  }

  get problemaId(): any {
    return this.activatedRoute.snapshot.params.id;
  }

  ngOnDestroy(): void {}
}

problemas.service.ts

@Injectable({ providedIn: 'root' })
export class ProblemasService {
  filtrosProblemas: AkitaFiltersPlugin<ProblemasState>;

  constructor(
    private problemasStore: ProblemasStore,
    private problemasQuery: ProblemasQuery
  ) {
    this.filtrosProblemas = new AkitaFiltersPlugin<ProblemasState>(
      this.problemasQuery
    );
  }

  get(): Observable<Problema[]> {
    return timer(500).pipe(mapTo(problemas));
  }

  getProblemas(): Observable<Problema[]> {
    const request$ = this.get().pipe(
      tap((resp) => {
        this.problemasStore.set(resp);
      })
    );

    const requestUpdate$ = this.get().pipe(
      tap((resp) => {
        this.problemasStore.remove();
        this.problemasStore.set(resp);
      })
    );

    return this.problemasQuery.getHasCache() === false
      ? request$
      : requestUpdate$;

    // return request$;
  }

  getProblemaPorId(id: any): Observable<any> {
    const problema = problemas.find((current) => current.id === id);

    return timer(500).pipe(
      mapTo(problema),
      map(() => this.problemasStore.add(problema))
    );
  }

package.json

 "dependencies": {
    "@agm/core": "^3.0.0-beta.0",
    "@angular/animations": "~11.0.6",
    "@angular/cdk": "^11.0.3",
    "@angular/common": "~11.0.6",
    "@angular/compiler": "~11.0.6",
    "@angular/core": "~11.0.6",
    "@angular/forms": "~11.0.6",
    "@angular/material": "^11.0.3",
    "@angular/platform-browser": "~11.0.6",
    "@angular/platform-browser-dynamic": "~11.0.6",
    "@angular/router": "~11.0.6",
    "@datorama/akita": "^4.22.0",
    "@fortawesome/angular-fontawesome": "^0.8.1",
    "@fortawesome/fontawesome-common-types": "^0.2.34",
    "@fortawesome/fontawesome-free": "^5.15.1",
    "@fortawesome/fontawesome-svg-core": "^1.2.34",
    "@fortawesome/free-brands-svg-icons": "^5.15.1",
    "@fortawesome/free-solid-svg-icons": "^5.15.1",
    "@ngneat/transloco": "^2.20.0",
    "@ngneat/transloco-locale": "^1.4.0",
    "@ngneat/until-destroy": "^8.0.3",
    "akita-filters-plugin": "^4.0.0",
    "bootstrap": "^4.5.3",
    "browser-detect": "^0.2.28",
    "moment": "^2.29.1",
    "ngx-take-until-destroy": "^5.4.0",
    "rxjs": "~6.6.0",
    "tslib": "^2.0.0",
    "zone.js": "~0.10.2"
  },
...

package-lock.json

"@fortawesome/angular-fontawesome": {
      "version": "0.8.1",
      "resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.8.1.tgz",
      "integrity": "sha512-dNmtFb/LTYWLNRfkKgCFwxgtQslNZLwUC+u7lkVAcIcjirIG6J9Ff0evl+9zR4DXFAkP0PN4RKe14NVDP3rUWA==",
      "requires": {
        "tslib": "^2.0.3"
      }
    },
    "@fortawesome/fontawesome-common-types": {
      "version": "0.2.34",
      "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.34.tgz",
      "integrity": "sha512-XcIn3iYbTEzGIxD0/dY5+4f019jIcEIWBiHc3KrmK/ROahwxmZ/s+tdj97p/5K0klz4zZUiMfUlYP0ajhSJjmA=="
    },
    "@fortawesome/fontawesome-free": {
      "version": "5.15.2",
      "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.2.tgz",
      "integrity": "sha512-7l/AX41m609L/EXI9EKH3Vs3v0iA8tKlIOGtw+kgcoanI7p+e4I4GYLqW3UXWiTnjSFymKSmTTPKYrivzbxxqA=="
    },
    "@fortawesome/fontawesome-svg-core": {
      "version": "1.2.34",
      "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.34.tgz",
      "integrity": "sha512-0KNN0nc5eIzaJxlv43QcDmTkDY1CqeN6J7OCGSs+fwGPdtv0yOQqRjieopBCmw+yd7uD3N2HeNL3Zm5isDleLg==",
      "requires": {
        "@fortawesome/fontawesome-common-types": "^0.2.34"
      },
      "dependencies": {
        "@fortawesome/fontawesome-common-types": {
          "version": "0.2.34",
          "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.34.tgz",
          "integrity": "sha512-XcIn3iYbTEzGIxD0/dY5+4f019jIcEIWBiHc3KrmK/ROahwxmZ/s+tdj97p/5K0klz4zZUiMfUlYP0ajhSJjmA=="
        }
      }
    },
    "@fortawesome/free-brands-svg-icons": {
      "version": "5.15.2",
      "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.15.2.tgz",
      "integrity": "sha512-YPlVjE1cEO+OJ9I9ay3TQ3I88+XkxMTYwnnddqAboxLhPNGncsHV0DjWOVLCyuAY66yPfyndWwVn4v7vuqsO1g==",
      "requires": {
        "@fortawesome/fontawesome-common-types": "^0.2.34"
      }
    },
    "@fortawesome/free-solid-svg-icons": {
      "version": "5.15.2",
      "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.2.tgz",
      "integrity": "sha512-ZfCU+QjaFsdNZmOGmfqEWhzI3JOe37x5dF4kz9GeXvKn/sTxhqMtZ7mh3lBf76SvcYY5/GKFuyG7p1r4iWMQqw==",
      "requires": {
        "@fortawesome/fontawesome-common-types": "^0.2.34"
      }
    },
...

When compiling the app the icon looks and works, but it throws the error TS2322 Type ‘string’ is not assignable to Type ‘IconProp’: error TS2322

However, the icon is perfectly visible when the app manages to launch with "ng serve": the icon is displayed

What I have tried without it working:

  1. Switch to "strict" = false; in compilerOptions in tsconfig.json.
  2. Read the instructions for angular-fontawesome in its official documentation.
  3. Import * IconProp *, and others like * IconName *, from @fortawesome / fontawesome-svg-core and assign it to the optional property "icon ?: string;":
    • “icon?: IconProp;”
    • “icon?: IconProp | string;”
    • “icon?: IconProp | IconName | string;”
  4. Install ”@ fortawesome / fontawesome-common-types":"^ 0.2.34" and set all libraries @fortawesome/* with the same version of fontawesome-common-types, as advised by this issue
  5. Delete node_modules and package-lock.json, then run npm i @ fortawesome / fontawesome-svg-core --save and npm install following this issue

I thank you in advance for all the invaluable help that you can give me. Thank you.

UPDATE: the solution to the problem.

I found the problem in setting angularCompilerOptions in tsconfig.json with the option of "strictTemplates ": true which enables strict mode.

According to the official angular documentation when strictTemplates is true what we do is enable the strict checking of the template type, which is only available with Ivy since angular version 9 and later.

The strict mode already failed me when I used the agm library for the web app maps, so I have disabled strictTemplates. So I disabled strict templates and the compile error no longer appears. This seems to be the only solution, since I have no alternative to the parameter "icon?: string;", which is of type IconProp, and at the same time combining it with the angular interpolation in the html template.

3

There are 3 best solutions below

0
On

the best way to bind string as font awesome icon

1- you must add this package @fortawesome/fontawesome-common-types

2- in your .ts file add this

import {IconName} from `@fortawesome/fontawesome-svg-core`

const myIcon = 'user' as IconName 

3- in your HTML file add this

<fa-icon [icon]="['far', myIcon]" ></fa-icon>

notice: use font awesome for angular from here

1
On

Try to use it in the way font awesome suggest, with tag:

 <span >
            <i [ngClass]="['fas', 'fa-' + data.icon]"></i>

          </span>
4
On

haven't used these icons before but have you tried referencing it in the HTML without the {{ }} ?