[FIXED] A way to cast variables in template to multiple types

Issue

I have an app in angular where I set

  "angularCompilerOptions": {
    "strictInjectionParameters": true,
    "fullTemplateTypeCheck": true,
    "strictTemplates": true
  }

So every input / output is not type checked.

It is good for most of the app, but I have some app input like (here I show select, but I also have a simple app-input that works the same) :

.html

      <app-select
        [useSearch]="true"
        [formControlName]="'country'"
        (valueChange)="setSelectedCountry($event)" <=== $event is of type unknown
      >
        <app-option
          *ngFor="let country of locations$ | async"
          [name]="'COUNTRIES.' + country.code | translate"
          [value]="country.code" <=== this is of type Country
        ></app-option>
      </app-select>

.ts

  setSelectedCountry(code: Country) {
    this.store.dispatch(loadLocationRequest({ payload: { code } }));
    this.selectedLocation$ = this.store.pipe(select(getLocationByCode(), { code }));
  }

in the above, since I use my app-select for multiple selector with various value, it is type :

  @Input()
  get value(): unknown | unknown[] {
    return this.pValue;
  }
  set value(newValue: unknown | unknown[]) {
    if (newValue !== this.pValue) {
      this.pValue = newValue;
      this.writeValue(newValue);
    }
  }

Now, There is 2 solutions I see,

  1. I do not use the ngModel like this [(value)]="country" and I make a method that typecheck in all my components that use a select:
  2. I create a type for every type of value my select use and cast to it.

But I would like to have something easier for those case only.

Is it possible to pass a Generic type to a component via input or something, so that it return a type of the Generic I passed ?
like (ex : <app-select<string>>)

Is it possible to make a pipe that cast a to a generic value ? without having to make a pipe for each type ? string number etc… ?

Is it possible to ignore certain checks ?

Solution

So, I tried multiple thing, and I came with a hacky way, I do not really recommend, unless in some special case (a bit like the $any()).

The correct way would be :

a Pipe for each unknown type, so that inside the type you can do some types check for ex

import { Pipe, PipeTransform } from '@angular/core'
import { Gender } from '@app/__generated__/globalTypes'

@Pipe({
  name: 'toAnimal',
  pure: true,
})
export class ToAnimalPipe implements PipeTransform {
  transform(value: unknown): Animal {
    return typeof value === "string" && Object.keys(Animal).includes(value) ? 
              value as Animal : 
              Animal.DOG // Default value just use anything you want.
  }
}

The hacky way :

I made the following simple pipe :

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'toType',
  pure: true,
})
export class ToTypePipe implements PipeTransform {
  transform<T>(value: unknown, _: T): T {
    return value as T;
  }
}

You can call it like

 value | toType: Animal.DOG

value can be anything of Animal but we just cast is as one of the value, so the compiler consider it ok to use it and do not complain.

IT IS NOT SAFE AND KIND OF REMOVE THE PURPOSE OF STRICT MODE.

But, it allow to do some workaround and make some very simple cast easy. like in this case :

<table [dataSource]="animals"> <=== Array<Animal>
 <ng-container [cdkColumnDef]="columns.name.def">
  <td class="align-items-center p-4 w-80" cdk-cell *cdkCellDef="let element"> 

*cdkCellDef="let element" this IS an Animal but angular template do not allow us to have the correct typing, so in this case let element | typeOf: Animal.DOG should be completely safe

Answered By – Crocsx

Answer Checked By – Katrina (Easybugfix Volunteer)

Leave a Reply

(*) Required, Your email will not be published