Components in Angular that are conditionally rendered from a shared source

As someone who primarily specializes in backend development rather than Angular, I find myself facing a challenge and seeking guidance. Despite my lack of expertise with Angular, I am attempting to work out a concept that may or may not be feasible. My struggle is compounded by the difficulty of articulating my question effectively through online searches. I hope to describe my predicament clearly, share my objectives, explain how I envision the solution working, and receive advice on whether this approach aligns with Angular conventions, its feasibility, and possibly an example implementation.

Our Single Page Application (SPA) utilizes a .NET Core Web API backend which includes an API endpoint called /api/feedItems. This endpoint returns a collection of "feed items" akin to event logs.

The structure of a feed item can be summarized as follows:

export enum FeedItemType {
    undefined = "undefined",
    foo = "foo",
    bar = "bar"
}

export class FeedItemModel {
    id: number;
    createdUtcTimestamp: Date;
    feedType: FeedItemType;
    feedData: {} // object? any?
}

In essence, each feed item comes with standard metadata such as an identifier, timestamp, and payload. The content of the payload varies depending on the FeedItemType.

A sample response from the API call presenting a list of feed items could resemble the following:

[  
    {  
    "id":12345,
    "createdUtcTimestamp":"2018-12-05T13:30:00Z",
    "feedType":"foo",
    "feedData":{  
        "name":"this is a foo type feed",
        "description":"This foo feed is describing an event of type 'foo'"
    }
    },
    {  
    "id":67890,
    "createdUtcTimestamp":"2018-12-06T15:45:00Z",
    "feedType":"bar",
    "feedData":{  
        "value1":11111,
        "value2":22222,
        "value3":33333
    }
    }
]

We have numerous feed item types, each necessitating a tailor-made visualization for optimal data presentation.

I aim to create an Angular component responsible for fetching the feed item list from the API, iterating over the items using an *ngFor directive, displaying common properties for each item, and dynamically rendering a different "sub" component based on the feedType.

In our scenario, the container component structure would look like this:

<div class="feed-item-container" *ngFor="let feedItem of feedItemList; let i = index;">
    <div class="feed-item">
        <div class="timestamp-display">{{feedItem.createdUtcTimestamp}}</div>

        <!-- conditional render based on "feedItem.feedType == 'foo'", 
        potentially using ngIf or ngSwitch directives -->
        <feed-item-foo (method of passing feedData here needs clarification) />

        <!-- conditional render based on "feedItem.feedType == 'bar'" -->
        <feed-item-bar (uncertainty surrounds the contents of this section) />

        <!-- fallback render if no matching feedType found -->
        <feed-item-generic (placeholder required) />
    </div>
</div>

The objective is to allow external creation of components as needed without extensive modifications (ideally none) to the container template. It would be beneficial if I could programmatically identify the feedType, locate a registered component selector corresponding to it, and automatically pass the feedData to it. However, manual referencing of components in the container is also acceptable. I seek to avoid consolidating all rendering logic within the container to prevent complexity and maintainability issues.

The individual feed item components should feature distinct formatting tailored to their respective feed types, including unique styles and functionalities. I wish to circumvent the creation of a monolithic feedItem container component.

Hence, my query centers around the possibility and methodology involved in achieving this goal.

Answer №1

If I grasp it correctly, the following concept might assist you... I have taken your example and made slight modifications.

<div class="feed-item-container" *ngFor="let feedItem of feedItemList; let i = index;">
<div class="feed-item">
    <div class="timestamp-display">{{feedItem.createdUtcTimestamp}}</div>

    <!-- equivalent of "if feedItem.feedType == 'foo'", 
    possibly ngIf or ngSwitch, but I'd prefer to avoid 
    explicitly checking for each type -->

    <!-- I am not aware of any alternative, but for better readability
    you can utilize <ng-container>

    <ng-container *ngIf="feedItem.feedType === 'foo'">
        <!-- you can create your own component and add an @Input() data, so your component will understand what it is dealing with (see example below)
        <feed-item-foo [data]="feedItem"/>
    </ng-container>

    <!-- equivalent of "if feedItem.feedType == 'bar'" -->
    <ng-container *ngIf="feedItem.feedType === 'bar'">
        <!-- you can create your custom component and include an @Input() data, so 
        your component knows how to handle it (see example below)
        <feed-item-bar [data]="feedItem"/>
    </ng-container>
    <!-- equivalent of "if unable to match on feedType" -->
    <!-- that can be a bit challenging... --> 
   <ng-container *ngIf='!foo && !bar && !xyz'>
    <feed-item-generic (something) />
   </ng-container>

</div>

All your components should ideally include something like a 'base' class

export class FeedItemBase implements OnInit {
  @Input() data = <YourType>null; // a little trick to ensure correct typing 
  // (there was some bug in an angular 4 version if I recall correctly)
  constructor() {
  }
  ngOnInit() {
    // add implementation as needed, otherwise feel free to delete :)
  }
}

Subsequently, all other classes can extend this base class (since the data will remain consistent...

export class FeedItemFoo extends FeedItemBase implements OnInit {
  constructor() {
  }
  ngOnInit() {
    // access data here 
  }
}

If you only have two types (which is doubtful), you could consider something along these lines:

<div class="feed-item-container" *ngFor="let feedItem of feedItemList; let i = index;">
<div class="feed-item">
    <div class="timestamp-display">{{feedItem.createdUtcTimestamp}}</div>

    <!-- equivalent of "if feedItem.feedType == 'foo'", 
    possibly ngIf or ngSwitch, but I'd prefer to avoid 
    explicitly checking for each type -->

    <!-- I am not aware of any alternative, but for better readability
    you can use <ng-template>
    <ng-container *ngIf="feedItem.feedType === 'foo'; then fooId;else barId"> 
    </ng-container>

   <ng-template #fooId>
        <!-- you can create your custom component and include an @Input() data, so 
        your component understands what it is handling (see example below)
        <feed-item-bar [data]="feedItem"/>
    </ng-template>

    <!-- equivalent of "if feedItem.feedType == 'bar'" -->
    <ng-template #barId>
        <!-- you can create your custom component and include an @Input() data, so 
        your component knows what it is dealing with (see example below)
        <feed-item-foo [data]="feedItem"/>
    </ng-container>
    <!-- equivalent of "if unable to match on feedType" -->
    <!-- that can be a bit challenging... --> 
   <ng-container *ngIf='!foo && !bar && !xyz'>
    <feed-item-generic (something) />
   </ng-container>

</div>

Hopefully, this explanation proves to be helpful :)

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

Alter the value by clicking a button within the DynamicRadioGroupModel in ng Dynamic Forms

I am working with ng-dynamic-form (version 6.0.4) and NG Bootstrap in Angular 6. I have a simple question. When a button click event is triggered, I want to change the value in DynamicRadioGroupModel by using the "setValue()" method. However, I am facing ...

Issue with Dialogflow Webhook functionality not operating properly when initiated from an external Angular 6 chat interface

I recently developed a Dialogflow chatbot and successfully integrated it with Firebase using a webhook. The chatbot functions perfectly when users interact with it in the internal dialogflow chat box. However, I decided to create a custom chat window usin ...

Transitioning from AngularJS to Angular 2: Exploring Alternatives to $rootScope.$on

Our journey with the AngularJS project has begun on the path towards the modern Angular. The ngMigration utility advised me to eliminate all dependencies on $rootScope since Angular does not have a concept similar to $rootScope. While this is straightforw ...

"Encountering a 400 bad request error when making a Graphql POST

Seeking assistance with my graphql code. I have included the service and component files below. I am currently new to graphql and not utilizing the apollo client; instead, I am attaching a query on top of the HTTP POST call to send requests to the graphql ...

The function argument does not have the property 'id'

I created a function that authorizes a user, which can return either a User object or a ResponseError Here is my function: async loginUser ({ commit }, data) { try { const user = await loginUser(data) commit('setUser', user) ...

Using template strings in one-way binding: a beginner's guide

Are you familiar with combining template strings with interpolation? This particular line is not functioning correctly. <a [href]='`#/emailing/scenario/${marketing._id}`'>{{marketing.name}}</a> Thank you! PS: I understand that the a ...

Error encountered in typescript when trying to implement the Material UI theme.palette.type

Just starting out with Material UI and TypeScript, any tips for a newcomer like me? P.S. I'm sorry if my question formatting isn't quite right, this is my first time on Stack Overflow. https://i.stack.imgur.com/CIOEl.jpg https://i.stack.imgur.co ...

Why is this statement useful? _ equals _;

While studying an Angular 7 class, I stumbled upon the following code that left me a bit confused. It's not exactly a search engine-friendly statement, so my apologies for that :) @Component({ selector: 'app-some', templateUrl: './ ...

Navigating through a dictionary in React TypescriptWould you like to learn

Currently, I am delving into the world of React and TypeScript. Within my journey, I have stumbled upon a dictionary representing various departments, with employee data stored in arrays. type Department = { Emp_Id: number, Name: string, Age: n ...

What could be causing the malfunction of my Nextjs Route Interception Modal?

I'm currently exploring a different approach to integrating route interception into my Nextjs test application, loosely following this tutorial. Utilizing the Nextjs app router, I have successfully set up parallel routing and now aiming to incorporate ...

How can I properly configure the 'main' file in angular-cli.json to utilize Express?

While attempting to set up an express server using Angular 2, I noticed in various configuration examples that the angular-cli.json file was being modified to point to a server.js file as the main configuration. However, after making this change, the appl ...

Tips for extracting individual fields from a payload in Angular 8

How do I extract individual fields from the payload and bind them with HTML on a page? I am trying to retrieve the alphacode and countryname in Angular 8. This is my Component: this.table$ = this.productService.get(id) .map(actions => { c ...

TypeScript allows for the declaration of a function that includes a mandatory property within the function signature

If I desire a tagged function with an interface such as: interface TaggedFun { (args): void; tag: boolean; } It appears that declaring a function to match this signature is not possible (since any function literal will lack the mandatory tag prop ...

Is it possible to obtain the URL (including parameters) of the first navigation before proceeding with the navigation process?

My goal is to automatically navigate to a specific website when a certain condition for the URL is met. Consider the following scenario in the ngOnInit() method of app.component.ts: if (urlMatchesCondition()) { await this.router.navigateByUrl('sp ...

Is There a Comparable Feature to *ngIf in DevExtreme?

Currently, I am diving into the world of webapp development using DevExtreme. As a novice in coding, this is my first time exploring the functionalities of DevExtreme. Essentially, I am seeking guidance on how to display certain elements based on specific ...

Boolean value 'isEdit' in Angular not being updated within the subscribe callback

Having a problem updating the isEdit component property within the event emitter's subscribe callback in my Angular application. Situation: In my CreateComponent, I handle adding or editing mobiles. The isEdit property (boolean) determines whether t ...

After compilation, any variables declared within a module remain undefined

I have declared the following files app.types.ts /// <reference path="../../typings/tsd.d.ts"/> module App{ export var Module = "website"; //---------------Controller Base Types--------------- export interface IScope extends ng.ISco ...

Using 'interface' declarations from TypeScript is unsupported in JS for React Native testing purposes

I have a ReactNative app and I'm attempting to create a test using Jest. The test requires classes from a native component (react-native-nfc-manager), and one of the needed classes is defined as follows export interface TagEvent { ndefMessage: N ...

Angular 2+ encountering an internal server error (500) while executing an http.post request

Here is my service function: public postDetails(Details): Observable<any> { let cpHeaders = new Headers({ 'Content-Type': 'application/json' }); let options = new RequestOptions({ headers: cpHeaders }); return this.htt ...

Guide to building a nested dropdown navigation in Angular 12 with the power of Bootstrap 5

I am looking to implement a multilevel dropdown feature in my Angular 12 project with Bootstrap 5. Can someone please guide me on how to achieve this? For reference, you can view an example here. Thank you in advance! ...