Template for typed variable - `ng-template`

How can the parent component correctly identify the type of let-content that is coming from ngTemplateOutletContext? The current usage of {{content.type}} works as expected, but my IDE is showing:

unresolved variable type

Is there a way to specify the type as Video?

parent.component.ts:

export interface Video {
  id: number;
  duration: number;
  type: string;
}

public videos: Video = [{id: 1, duration: 30, type: 'documentary'}];

parent.component.html:

<ul>
  <li *ngFor="let video of videos">
    <tile [bodyTemplate]="tileTemplate" [content]="video"></app-card>
  </li>
</ul>

<ng-template #tileTemplate let-content>
  <h5 class="tile__type">{{content.type}}</h5>
</ng-template>

tile.component.ts:

@Component({
  selector: 'tile',
  templateUrl: './tile.component.html',
  styleUrls: ['./tile.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CardComponent {
  @Input() tileTemplate: TemplateRef<any>;
  @Input() content: Video;
}

tile.component.html:

<div
...
  <ng-container
    [ngTemplateOutlet]="tileTemplate"
    [ngTemplateOutletContext]="{ $implicit: content }">
  </ng-container>
...
</div>

Answer №1

I developed a custom directive to address this issue.

import { Directive, Input, TemplateRef } from '@angular/core';

@Directive({selector: 'ng-template[typedTemplate]'})
export class TypedTemplateDirective<TypeToken> {

  // specifying the type for the directive
  @Input('typedTemplate')
  typeToken: TypeToken;

  // accessing the template from Angular
  constructor(private contentTemplate: TemplateRef<TypeToken>) {
  }

  // defining the context type for the directive and template
  static ngTemplateContextGuard<TypeToken>(dir: TypedTemplateDirective<TypeToken>, ctx: unknown): ctx is TypeToken{ return true; }
}

Usage example:

<!-- typedTemplate is the directive, typeToken is an object on our component -->
<ng-template #someTemplate [typedTemplate]="typeToken" let-param="param">
  {{param}}
</ng-template>

In the component:

// creating typeToken to define parameter types
typeToken: { param: string };

Answer №2

When it comes to let-* variables, type inference is not available. The Angular micro syntax parser incorporates the let- context, making it challenging for IDEs to deduce the type due to the lack of a clear origin.

To address this issue, you can attempt to suppress the IDE warning by utilizing $any()

For more information, check out https://angular.io/guide/template-syntax#the-any-type-cast-function

<ng-template #tileTemplate let-content>
  <h5 class="tile__type">{{$any(content).type}}</h5>
</ng-template>

If you wish to enforce type inference, you can achieve this by implementing a function:

<ng-template #tileTemplate let-content>
  <h5 class="tile__type">{{toVideo(content).type}}</h5>
</ng-template>

public toVideo(value: any): Video { return value as Video; }

Answer №3

To ensure your IDE cooperates and offers code completion, utilize a custom type guard:

Include a method in your class that accepts a variable as a parameter and returns the same variable:

export class PlayerComponent {
  ...
  public player = (selected: Player) => selected;
}

Then, simply apply the function to the variable in your template:

<p class="selected__player">{{player(selection).name}}</p>

Answer №4

An alternative approach to creating a new directive, this method is similar to another workaround mentioned by @Reacangular.


To solve this issue, you can wrap your variable inside an additional ng-template. I prefer this solution over others as it only requires adding 2 more lines of code in the HTML. Of course, if you are using your variable only once or twice, the approach from @Reactangular's answer might be better. Here's my solution:

Instead of using:

<ng-template *ngTemplateOutlet="foo; context: {$implicit: {fooProp: 'Hello!'}}"></ng-template>
<ng-template #foo let-args>
    This is untyped: {{ args.fooProp }}<br>
</ng-template>

Try this instead:

<ng-template *ngTemplateOutlet="foo; context: {$implicit: {fooProp: 'Hello!'}}"></ng-template>
<ng-template #foo let-untypedArgs>
    <ng-container *ngIf="identity(untypedArgs) as args">
        This is typed: {{ args.fooProp }}<br>
    </ng-container>
</ng-template>
identity(foo: Foo): Foo {
    return foo;
}

The type assertion is recognized by the IDE when using *ngFor or *ngIf. One drawback of this solution is that the inner <ng-container> is rendered later due to the *ngIf statement.

With this change, now if you add an invalid property to your context, you will receive a compilation error, which is beneficial. Check out the demo on StackBlitz:

Property 'newFooProp' does not exist on type 'Foo'.


As noted in the comments, similar to the accepted answer, this solution has the downside of calling ngZone every lifecycle. It may be advisable to use this in conjunction with ChangeDetectionStrategy.OnPush.

Answer №5

By utilizing an *ngIf directive along with a function, you can type any template variable

<ng-container *ngIf="asMyType(anyType) as myType">
  <!-- Type definition of myType goes here -->
</ng-container>
const asMyType = (something: unknown) => something as myType;

This strategy can also be implemented within an ng-template to assign a type to a particular variable

<ng-template let-my-type="my-type">
  <ng-container *ngIf="asMyType(my-type) as myType">
    <!-- Typed content for myType is defined here -->
  </ng-container>
</ng-template>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Having trouble assigning a value of `undefined` to a TextField state in React hook

I am in need of setting the initial state for a TextField date to be undefined until the user makes a selection, and then allowing the user an easy way to reset the date back to undefined. In the code snippet below, the Reset button effectively resets par ...

The functionality of verifying the type of an item in a list using Typescript is not functioning

In my TypeScript code, I am working with a type called NameValue and another one called MixedStuff. type NameValue = { name: string; value: string }; type MixedStuff = NameValue | string; function stripTwoChars(stuffs: MixedStuff[]): string { let st ...

The error message "The type 'MouseEvent' is non-generic in TypeScript" popped up on the screen

Having created a custom button component, I encountered an issue when trying to handle the onClick event from outside the component. I specified the parameter type for the onClickCallback as MouseEvent<HTMLButtonElement, MouseEvent>, which is typical ...

Changing md-sidenav mode in Angular Material 2

Looking to modify the behavior of the md-sidenav in Angular Material 2, switching from side on desktops to over on mobile devices. Is there a method to achieve this programmatically? Appreciate any guidance! ...

Move the page to the beginning of the vertical stepper upon clicking the "next" button

I am currently working on optimizing a lengthy form to enhance user experience. To illustrate my point, I have come across a simplified example of the code I am dealing with which can be found here. My primary goal is to ensure that when a user proceeds t ...

Unlocking the value of the "input" field within an EventListener function in Angular directly from the DOM

In my "characters" module, there is a form with a text field and a button. When the button is clicked, it triggers a function but I am struggling to retrieve the current input text and pass it to the async function. HTML: TS: Unfortunately, simply getti ...

Angular is facing problems with routing and caching, as the route path includes an empty string

Currently, I am tackling routing issues in Angular that are causing some roadblocks for me. Here are a couple of challenges I am facing: 1) Within my app.component.ts, which is the initial component file that gets called, I have the following function: c ...

Using a child element in a component to access a directive method: What steps should you follow?

I'm looking to develop a directive that can display or hide a component based on a condition validated in a service. Additionally, I want some child elements of the component to be able to execute a method of the directive. I would like to use somethi ...

The FireBase getToken function with the forceRefresh set to true has failed to perform as expected

I encountered a problem with this code snippet from Firebase when trying to run it in Angular 2 CLI. It gives an error of 'unreachable code'. How can I fix this issue and get it to work properly? firebase.auth().currentUser.getToken(/forceRefres ...

A guide on transferring received data from dataService within the parent component to the child component in Angular2

Within the context of my application, there exists a parent component named app-parent and a child component called app-child. In app-parent, I retrieve data from a DataService. @Component({ selector: 'app-parent', providers: [DataService] ...

JavaScript - Employing the .every function with an array containing objects

Is it possible to use the array.every method on multidimensional arrays? The structure of my array is as follows: tabs=[ {label: string, icon: icon, routerLink: link}, {label: string, icon: icon, routerLink: link}, {label: string, icon: icon, routerLink: ...

Angular 2 error: "HttpService Provider Not Found"

In my Angular 2 / Ionic 2 project, I have the following "constellation" of components and services: Component1 -> Service1 -> Service3 Component2 -> Service2 -> Service3 (where -> denotes a relationship like "using" or "calling") Whenever ...

Tips for integrating the react-financial-charts library into your React and JavaScript project

While exploring the react-financial-charts library, I discovered that it is written in TypeScript (TS). Despite my lack of expertise in TypeScript, I am interested in using this library in my React+JS project due to its active contributions. However, I hav ...

Navigating nested data structures in reactive forms

When performing a POST request, we often create something similar to: const userData = this.userForm.value; Imagine you have the following template: <input type="text" id="userName" formControlName="userName"> <input type="email" id="userEmail" ...

Issues encountered while establishing a connection to an API in React Native

When attempting to log in a user by connecting to my API, I encountered some issues. It seems that every time my laptop has a different IP address, I need to make changes in the file where the fetch or XMLHttpRequest is located in order for the login proce ...

Creating and accessing files within the `dist` folder in Angular 5: A comprehensive guide

After deploying my Angular 5 app to Cloud Foundry, a file named app-number-version is automatically generated in the dist folder with just the version number (e.g., "1.0.0"). My goal is to show this version number in the navigation bar of our app's H ...

"Angular SwtAlert2: The Ultimate Multi-Selection Tool

I am trying to integrate the ng-select directive into SweetAlert2, but the code snippet below is not working as expected: const { value: formValues } = await swal.fire({ title: 'Multiple inputs', html: '<ng-select id=" ...

Flag is activated to retrieve the data from the @Input source

@Input() config= []; flag = false; I need to change the flag to true only when I receive data in the config from the @input. Where should I do this? The data in the config is delayed and I am unable to access it in ngOnInit but can get it in ngOnChanges. ...

The module named "mongoose" does not have any member called 'PaginateResult' exported

I'm facing an issue while trying to add the necessary types for "mongoose-paginate" in my Angular 4 project setup with "angular-cli". The problem arises when Webpack throws an error. import {PaginateResult} from "mongoose"; ... getAll(page: number) ...

Center the mat-icon within a label

I'm seeking assistance with aligning a mat-icon beside text within a label Here is the HTML code snippet: <label> <mat-icon>warning</mat-icon> Warning </label> And here is the corresponding CSS code: label { font- ...