Here is the revised text: "What is the best way to send a variable to a component and retrieve it within the ng-template using

I've developed a component named "foo" that accepts a data object and displays it within an ng-template as its own variable.

The issue arises when the variable inside the ng-template is of type any, lacking proper typing for checking and autocomplete features.

My goal is to pass any type of variable - be it an observable, string, number, object, or any other data type - to the foo component and access the same variable with appropriate typing within the ng-template.

Is it possible to achieve this in Angular?

import { CommonModule } from '@angular/common';
import { Component, ContentChild, Input, TemplateRef } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { of } from 'rxjs';
import 'zone.js';

@Component({
  selector: 'foo',
  standalone: true,
  imports: [CommonModule],
  template: `
    <div>
      <ng-container *ngTemplateOutlet="fooTemplate; context: { data }" />
    </div>
  `,
})
export class FooComponent {
  @ContentChild('foo') fooTemplate!: TemplateRef<unknown>;
  @Input() data: any; // <--- type?
}

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, FooComponent],
  template: `
    <h1>Hello from {{ name }}!</h1>
    <a target="_blank" href="https://angular.dev/overview">
      Learn more about Angular
    </a>

    <foo [data]="person$ | async">
      <ng-template #foo let-person="data">
        {{ person | json}}
      </ng-template>
    </foo>
   
    <foo [data]="[1,2,3,4]">
      <ng-template #foo let-numbers="data">
        {{ numbers | json}}
      </ng-template>
    </foo>
  `,
})
export class App {
  name = 'Angular';

  person$ = of({
    name: 'toto',
    age: 3,
  });
}

bootstrapApplication(App);

You can find the code here

Answer №1

If you want to enable type checking within ng-template contexts, you can create a custom directive with a special static property called ngTemplateContextGuard

interface TypeChecker<TItem extends any> {
  $implicit: TItem;
}

@Directive({
  selector:'ng-template[typeChecker]', 
  standalone:true 
})
export class TemplateTypeChecker<T>{
  @Input('typeChecker') data!:T; // Define an input property named 'typeChecker' which will receive data of type T

  static ngTemplateContextGuard<TContextItem extends any>(
    _: TemplateTypeChecker<TContextItem>, 
    ctx: unknown 
  ): ctx is TypeChecker<TContextItem> { 
    return true; 
  }
}

You can then use the typeChecker directive in templates and provide data so that it can infer automatically.

  <foo [data]="person$ | async">
      <ng-template let-person [typeChecker]="person$ | async"">
        {{ person | json}}
      </ng-template>
   </foo>

  <foo [data]="[1,2,3,4]">
      <ng-template [typeChecker]="[1,2,3,4]" let-numbers>
        {{ numbers | json}}
      </ng-template>
  </foo>

This answer was inspired by a Twitter thread

Example

Answer №2

One technique we can utilize to implement the desired typing is through the use of @if and alias syntax. An example demonstrating this concept in action is provided below using Stackblitz!

import { CommonModule } from '@angular/common';
import { Component, ContentChild, Input, TemplateRef } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { of } from 'rxjs';
import 'zone.js';

@Component({
  selector: 'foo',
  standalone: true,
  imports: [CommonModule],
  template: `
    <div>
      <ng-container *ngTemplateOutlet="fooTemplate; context: { data }" />
    </div>
  `,
})
export class FooComponent {
  @ContentChild('foo') fooTemplate!: TemplateRef<unknown>;
  @Input() data: any;
}

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, FooComponent],
  template: `
    <h1>Hello from {{ name }}!</h1>
    <a target="_blank" href="https://angular.dev/overview">
      Learn more about Angular
    </a>

    <foo [data]="person$ | async">
      <ng-template #foo let-person="data">
        @if(setTypeForPerson(person); as personTyped) {
          {{ personTyped | json}}
          {{numbersTyped.age1}}
        }
      </ng-template>
    </foo>
   
    <foo [data]="[1,2,3,4]">
      <ng-template #foo let-numbers="data">
        @if(setTypeForNumbers(numbers); as numbersTyped) {
          {{ numbersTyped | json}}
          {{numbersTyped.age}}
        }
      </ng-template>
    </foo>
  `,
})
export class App {
  name = 'Angular';

  person$ = of({
    name: 'toto',
    age: 3,
  });

  setTypeForPerson(item: any): Person {
    return item as Person;
  }

  setTypeForNumbers(item: any): Numbers {
    return item as Numbers;
  }
}

export interface Person {
  name: string;
  age: number;
}

export interface Numbers {
  name: string;
  age1: number;
}

bootstrapApplication(App);

Explore the Stackblitz Demo

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

Issue with Syncfusion Grid: Unable to Display Column Data as Hyperlinks

I'm currently working on a Webapp that utilizes the Syncfusion grid to display tabular data. One of my requirements is to showcase a column's data as a clickable link and trigger a function upon user interaction. After consulting the tutorial av ...

My reselect function seems to be malfunctioning - I'm not receiving any output. Can anyone help me

I'm looking to implement reselect in my code to retrieve the shopping cart based on product ids. Here's my reselect.ts file: import { createSelector } from "reselect"; import { RootState } from "../store"; export const shopp ...

Issue with Angular 2 directive update detection; unresolved directive not refreshing

I have created an Angular 2 application that displays a number which can be either negative or positive. In order to change the font color to red when the value is negative, I've implemented a directive. This number gets updated constantly through an ...

Incorporate a stylish gradient background into react-chartjs-2

I am currently working on adding a gradient background with some transparency to a radar graph within a react component. So far, I have only found solutions that involve referencing a chartjs canvas in an html file, but none for implementing it directly in ...

Change a nested for-loop into an Observable that has been transformed using RxJS

Currently, the following function is operational, but I consider it a temporary solution as I'm extracting .value from a BehaviorSubject instead of maintaining it as an observable. Existing Code Snippet get ActiveBikeFilters(): any { const upd ...

Detecting when users stop scrolling in Angular 5

Is there a way to toggle visibility of a div based on user scrolling behavior? I'd like the div to hide when the user scrolls and reappear once they stop. I've attempted to achieve this effect using @HostListener, but it only seems to trigger wh ...

Can you provide guidance on effectively utilizing a Pinia store with Vue3, Pinia, and Typescript?

I'm currently facing challenges while using the Pinia store with TypeScript and implementing the store within a basic app.vue Vuejs3 option api. Here is my app.js file: import {createApp} from 'vue' import {createPinia} from "pinia&quo ...

What is the best approach to validating GraphQL query variables while utilizing Mock Service Worker?

When simulating a graphql query with a mock service worker (MSW), we need to verify that the variables passed to the query contain specific values. This involves more than just type validation using typescript typings. In our setup, we utilize jest along ...

Difficulty in activating or deactivating form controls in Angular 2 upon receiving an HTTP response

When using formcontrol in Angular, I encountered an issue where I tried to disable the form control based on a variable assigned from an HTTP response. Angular2 gave me a warning message. The warning mentioned that it's not recommended to use the dis ...

Store the video file transmitted through a multipart form in Serverless Offline mode

I currently have a website built with Angular4 featuring a basic form for uploading data using ng2-file-upload. The files are sent to a Node.js-based serverless offline server where my goal is to simply save those files received from the form onto disk. A ...

Using AKS Kubernetes to access a Spring Boot application from an Angular frontend using the service name

I have developed two frontend applications using Angular and a backend application using Spring Boot. Both apps are running in the same namespace. I have already set up two services of type Loadbalancer: The frontend service is named frontend-app-lb (exp ...

Creating click event handler functions using TypeScript

I encountered an issue when trying to set up an event listener for clicks. The error message I received was that classList does not exist on type EventTarget. class UIModal extends React.Component<Props> { handleClick = (e: Event) => { ...

How can one access a dynamically generated element in Angular without using querySelector?

Currently in the process of developing my custom toastr service, as shown in the GIF below My Objective: https://stackblitz.com/edit/angular-ivy-tgm4st?file=src/app/app.component.ts But without using queryselector. It's recommended to avoid querysele ...

How can one avoid the use of an opening curly bracket in Angular?

Is there a way to avoid the first opening curly brace shown below? <div> { <ng-container *ngFor="let x of [1,2,3,4]; let last=last"> {{x}} <ng-container *ngIf="!last">,</ng-conta ...

Exploring TypeScript: Determining the data type of an object key within the object

Apologies for the vague title, I'm struggling to articulate my problem which is probably why I can't find a solution! Let me illustrate my issue with a snippet of code: type Type<T> = { key: keyof T, doStuff: (value: T[typeof key]) =& ...

Angular 2: The linting error shows up as "Anticipated operands need to be of the same type or any"

So, I have this shared service file where a variable is defined like so: export class SharedService { activeModal: String; } Then, in my component file, I import the service and define it as follows: constructor(public sharedService: SharedService) ...

Font Awesome functions properly on one Angular component but not on the second one

I've encountered a strange issue while working on my Angular project. About a month ago, I added a component with a form that included some inputs along with Font Awesome icons. The code snippet is provided below. <div class="field"> ...

Reveal the class to the global scope in TypeScript

ClassExample.ts: export class ClassExample{ constructor(){} } index.html: <script src="ClassExample.js"></<script> // compiled to es5 <script> var classExample = new ClassExample(); //ClassExample is not defined < ...

After the initial test is executed, Jasmine's spy-on function proceeds to call the actual function

The issue arises when the second test fails due to an error message stating that "Cannot read property 'get' of undefined". This error occurs because the second test references the real service, which contains a private property called "http" tha ...

The appearance of the Angular material component is altered once integrated into a new Angular application compared to its presentation in the provided example

After adding the Angular Material component "Toolbar" to my project, I noticed that it does not appear the same as it does in the example provided. Despite using the same styles, the outcome is different. Here is what the example looks like on the project ...