I must connect a TypeScript class to an Angular component

Here is an example of element class

export class Element {
    fields = []
}

And here are two field classes

export class SmallText {
    value
    constructor(value) {
        this.value = value
    }
}

export class LargeText {
    value
    constructor(value) {
        this.value = value
    }
}

Imagine we have an instance of this element

export class Element {
    fields = []
    constructor(){
        this.fields.push(new SmallText("foo"))
        this.fields.push(new LargeText("bar"))
    }
}

Each field has a unique template, for example

<!-- the small text is displayed as an input -->
<input>
<!-- while the large text appears as a textarea -->
<textarea></textarea> 

Now, I want to iterate through the fields array and display the corresponding template for each field.

The current solution involves using ngFor directive like so:

<field *ngFor="let field of element.fields" [inputField]="field"></field>

Within the field component, there exists logic such as:

<input *ngIf="inputField.constructor.name == 'SmallText'">
<textarea *ngIf="inputField.constructor.name == 'LargeText'"></textarea> 

This approach becomes troublesome in production as class names get overwritten, necessitating the maintenance of a mapping in a service.

I propose having separate components for SmallText and LargeText so that when looping through the element.fields array, each component is instantiated with its respective template. This way, we avoid the need for additional logic to handle different field types.

Is there a more efficient solution to this issue?

Answer №1

Give this a shot: https://stackblitz.com/edit/angular-foreach-field-in-component?file=src%2Fapp%2Fapp.component.html

This example utilizes components for the same purpose.

You have the flexibility to select and pass the type to your component field.

// field.component.ts
import { Component, Input } from '@angular/core';

@Component({
  selector: 'field',
  template: `
    <input *ngIf="type == 'small-text'" name="{{name}}" [value]="value" />
    <textarea *ngIf="type == 'large-text'" name="{{name}}" [value]="value"></textarea>
  `
})
export class FieldComponent  {
  @Input() type;
  @Input() name;
  @Input() value;

}

To use it in your component template:

<!-- app.component.html -->
<div *ngFor="let field of fields">
  <label>{{field.name}}: </label>
  <field [type]="field.type" value="{{field.value}}"></field><br><br>
</div>

The array containing the components:

fields = [
{ type: 'small-text', name: 'foo', value: 'foo' },
{ type: 'large-text', name: 'bar', value: 'bar' },
{ type: 'small-text', name: 'moo', value: 'moo' },
];

Answer №2

Utilize the 'static' keyword to ensure consistency post-compilation

export class SmallText {
    static type = 'SmallText';
    value
    constructor(value) {
        this.value = value
    }
}

export class LargeText {
    static type = 'LargeText';
    value
    constructor(value) {
        this.value = value
    }
}

template:

<input *ngIf="inputField.type == 'SmallText'">
<textarea *ngIf="fieinputFieldd.type == 'LargeText'"></textarea> 

however, leveraging components may be a more effective approach

AppModule:

@NgModule({
  imports:      [ BrowserModule ],
  declarations: [ AppComponent, SmallTextComponent, LargeTextComponent ],
  bootstrap:    [ AppComponent ],
  entryComponents: [SmallTextComponent, LargeTextComponent]
})
export class AppModule { }

SmallTextComponent:

@Component({
  selector: 'app-small-text',
  template: '<input/>',
})
export class SmallTextComponent { }

LargeTextComponent:

@Component({
  selector: 'app-large-text',
  template: '<textarea></textarea>',
})
export class LargeTextComponent { }

AppComponent:

@Component({
  selector: 'my-app',
  template: '<ng-container #container></ng-container>'
})
export class AppComponent implements AfterViewInit {
  @ViewChild('container', { read: ViewContainerRef }) container: ViewContainerRef;
  private _components: Type<any>[];

  constructor(
    private _cfr: ComponentFactoryResolver,
    private _injector: Injector,
  ) {
    this._components = [LargeTextComponent, SmallTextComponent, LargeTextComponent];
  }

  ngAfterViewInit() {
    this._components.forEach(component => {
      const componentFactory = this._cfr.resolveComponentFactory(component);
      const componentRef = componentFactory.create(this._injector);
      this.container.insert(componentRef.hostView);
    });
  }
}

Answer №3

It appears that your "fields" are actual JavaScript classes initialized with something like new LargeText(...). In this scenario, the instanceof operator would be your best friend. It's important to note that this reference won't break after production build.

Here is an example of how you can achieve this:

Template

<input *ngIf="isSmallText(inputField)">
<textarea *ngIf="isLargeText(inputField)"></textarea> 

Component

public isSmallText(field: SmallText | LargeText): boolean {
  return field instanceof SmallText;
}

public isLargeText(field: SmallText | LargeText): boolean {
  return field instanceof LargeText;
}

@chris mentioned that using method calls in Angular templates may lead to unexpected updates due to change detection triggers. The following code provides a more production-ready solution while still utilizing instanceof:

Template

<input *ngIf="isSmallText$ | async">
<textarea *ngIf="isLargeText$ | async"></textarea> 

Component

// Your field observable monitored when input changes
public inputField$ = ...;

public isSmallText$ = this.inputField$.map(field => field instanceof SmallText);

public isLargeText$ = this.inputField$.map(field => field instanceof LargeText);

As your business logic becomes more intricate (e.g., multiple types of fields), further refactoring to handle type checking in a unified way will be necessary. However, this goes beyond addressing the original issue at hand.

In my humble opinion, the performance trade-off is worth it especially when starting to learn Angular. This will naturally improve as you delve deeper into reactive programming concepts.

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

Is it possible to utilize Angular's structural directives within the innerHtml?

Can I insert HTML code with *ngIf and *ngFor directives using the innerHtml property of a DOM element? I'm confused about how Angular handles rendering, so I'm not sure how to accomplish this. I attempted to use Renderer2, but it didn't wor ...

Specify the object key type when using a `for-in` loop

My current situation involves an object type: interface ShortUrlParam { openid: string; avatar: string; nickname: string; } const param: ShortUrlParam = { openid: 'abc123', avatar: '', nickname: 'wenzi&apo ...

TypeScript Type Mapping for HTML Element Tags

I am currently working on a TypeScript + React project and facing an issue with the polymorphic as prop in one of my components. Specifically, I want to restrict this prop to only accept HTML tags, excluding ReactNodes or JSX Elements. Unfortunately, I hav ...

I am currently experiencing a problem with deleting documents in Firebase Firestore using React, Typescript, and Redux. Despite the code running, the document continues to exist in

I seem to be encountering an issue that I can't quite figure out. Despite my code running smoothly, I am unable to delete a document from Firestore. The document ID definitely exists within the database, and there are no subcollections attached to it ...

Struggling with transitioning from TypeScript to React when implementing react-data-grid 7.0.0

I'm trying to add drag and drop functionality to my React project using react-data-grid, but I keep encountering a "TypeError: Object(...) is not a function" error. I have a TypeScript version of the file in the sandbox as a reference, but when I try ...

What is the proper method for updating _variables in the latest version of Bootstrap, 4.3.1?

I am currently utilizing Bootstrap 4.3.1 within my Angular 8 project and I aim to customize some of the default variables in Bootstrap, such as $font-size-base. Despite attempting the following code, it does not seem to be effective. Can someone provide g ...

What is the best way to incorporate and utilize the JavaScript MediaWiki API in my projects?

I find it frustrating that the documentation for this library is lacking, making it difficult to figure out how to import it into my Typescript/React project. I am attempting to utilize the MediaWiki officially supported library but the examples provided ...

Exploring the functionality of Angular Material Paginator in conjunction with Observables, esch

(Issue still unresolved) I am currently following guidance from both this stackoverflow post(using mat-card) and this one(using observable) to address my problem. My goal is to utilize *ngFor with mat-card to display an array of objects coming from an obs ...

Issue with Angular 8+: *ngFor loop not rendering data on the HTML page despite successful data retrieval in console.log()

I am attempting to showcase a list of tuitions in a table with a separate component for each result in the list using *ngFor. However, the HTML is not rendering it properly. console.log() accurately prints the data (list of tuitions) : View console log da ...

How to implement a reusable module with distinct routes in Angular

In my current angular project, we have various menus labeled A, B, C, D, and E that all utilize the same module. Specifically, menus A, C, and E use the same component/module. My goal is to ensure that when I am on menu A and then click on menu C, the sa ...

Is it possible to transfer parameters from one page to another page using the pop method in Ionic 2?

Is it possible to pass parameters from one page to another during a push operation, but how can this be done during a pop operation? showfilter(){ this.navCtrl.push(FilterPage,{ fulldetail : this.selectedarea }); } Can you explain how ...

Encountering a Typescript issue while trying to access two distinct values dynamically from within a single object

Currently, I'm developing a component library and facing an issue with a TypeScript error: An Element implicitly has an 'any' type due to the expression of type 'levelTypes | semanticTypes' being unable to index type '{ level1 ...

Tips for utilizing [ngClass] with various class situations in Angular4

My current div structure is as follows: <div class="class-a" [ngClass]="{'class-b': !person.something}"> However, I now need to add an additional condition... I want the div to have class-a if one condition is met, class-b if another con ...

Body element in Angular component seems to be mysteriously added at the bottom

<pI'm experiencing something unusual. Every time I render my custom element, the content of the body (innerHTML) of the element is getting appended at the end.</p> <pHere's an example on my homepage - notice how 'childbody' ...

How do I resolve the issue of 'filter' not being recognized on type 'Observable<Event>'?

Hey there, everyone! I'm facing an issue with integrating a template into my Angular project. It seems that the filter function is no longer available in the Observable library (Update from 10 to 11?). I attempted to use pipe as a solution, but as a ...

How can I make TypeScript mimic the ability of JavaScript object wrappers to determine whether a primitive value has a particular "property"?

When using XMLValidator, the return value of .validate function can be either true or ValidationError, but this may not be entirely accurate (please refer to my update). The ValidationError object includes an err property. validate( xmlData: string, opti ...

Challenges with deploying Angular applications and dealing with undefined properties within Angular code

Currently, I have successfully added new products to the database with all the desired properties. However, I am facing errors that are preventing me from deploying the application for production. Fixing these errors causes further issues where I cannot ad ...

How to Properly Retrieve an Array of JSON Objects Using Ionic 3 Storage

Storing and retrieving an array of JSON objects locally with Ionic storage: Country[5] 0: {record_id: "1", local_TimeStamp: "16:00:00", country: "USA"} 1: {record_id: "2", local_TimeStamp: "17:00:00", country: "Japan"} 2: {record_id: "3", local_TimeStamp: ...

ErrorHookWebpack: When executing npm build in the node container, the service is detected as inactive

Encountering an issue when executing npm run build within a container on Kubernetes during a gitlab-ci job. It's worth noting that npm install, npm run lint, and npm run test all completed without any problems. Error: node@runner-7gbsh-sz-project-966 ...

Title positioned between two buttons in the header

I am trying to add two menu buttons in my header, but the Hamburger menu button is not aligning to the left as expected. Currently, it looks like this: https://i.stack.imgur.com/s5ptT.png Below is the code snippet I am using: <ion-header> <io ...