Approach to streamlining and making services more flexible

Currently, I am immersed in a complex Angular 2 endeavor that requires fetching various types of objects from a noSQL database. These objects are represented using straightforward model classes, and I have set up services to retrieve the data from the DB and create instances of these objects. This process is similar to the methodology demonstrated in the Heroes tutorial.

However, I have encountered an issue where each type of object necessitates its own service with slight variations. The primary discrepancy lies in the fact that I need the service to instantiate and return different object types. To streamline these services, I attempted to pass the object type from the component to the service, allowing the service to make the appropriate DB call and construct the specified object type.

This setup appears as follows:

Within the component:

import { Market }   from './market'; // Market represents a basic model class

/* additional code */

ngOnInit(){
    this.route.params.forEach(params => {
        this.nodeID = params['id'];
        this.nodeService.getNodeOfType(Market, this.nodeID)
        .subscribe(market => this.market = market)
    });
    }

Service:

export class NodeService {
    getNodeOfType(type, nodeID: number) {
        return this.neo4j.readProperties(nodeID)
        .map(properties => new type(nodeID, properties));
    }
}

I have two inquiries:

  1. How should I specify the class type when passing it as a parameter in the function?

  2. Given my limited experience with TypeScript, and the absence of similar examples in the documentation, I suspect this approach might be considered an anti-pattern. Could this design practice potentially lead to complications down the road?

Answer №1

One major issue that stands out is the lack of type safety in your code due to not specifying a return type for your getNodeOfType() method. When you need a method to return different types based on input parameters, using a generic method is the recommended approach - and fortunately, Typescript supports generics.

Here is one way to implement it in your scenario:

getNodeOfType<T>(factory: (input:any, nodeId:number)=>T , nodeID: number): T {
  return this.neo4j.readProperties(nodeID)
                   .map(properties => factory(properties, nodeId));
}

Instead of passing a type directly, you are passing a function that returns an object of the desired type (T) based on some input. If there is a common base type returned by this.neo4j.readProperties(), it's advisable to use that instead of any.

By returning type T dynamically based on the factory function, you maintain type safety without losing it.

In response to your query about creating multiple factory functions, consider using simple anonymous functions as shown in this example:

class Foo{ 
  constructor(input: any, id:number) {/*constructor stuff*/ }
} 

//usage
foo: Foo = myService.getNodeOfType((props, id)=>new Foo(props, id), 1234);

The advantage of using a factory function over a specific type is the flexibility it offers in mapping data to the desired result type or performing validation before instantiation.

If you do not require such flexibility, you can simplify the implementation as follows:

getNodeOfType<T>(Type: new(input:any, id:number)=>T , nodeID: number): T {
  return this.neo4j.readProperties(nodeID)
   .map(properties => new Type(properties, id));
}

//usage (using Foo class as above)
foo: Foo = myService.getNodeOfType(Foo, 1234);

Note the type structure of the first parameter, which indicates a constructor accepting an any and a number to return type T.

Answer №2

Modified (Unable to Add Comments):

In my opinion, utilizing generics for a factory may not be the most optimal solution. Abstract classes and interfaces are specifically designed for this purpose. Take a look at this example: https://gist.github.com/jordic/589ac6dbc2e0badb5ce29e3e112b47b5

Initial Version:

Instead of using generics, consider creating an abstract class and specifying the return type of the function as this abstract Class (similarly for interface):

export abstract class Modeled {}
getNodeOfType(type:ValidModeled, nodeID: number):Modeled

You can also employ a union type for the parameters:

https://www.typescriptlang.org/docs/handbook/advanced-types.html

type ValidModeled =  "Class1" | "Class2" | "Class3"

To address your second inquiry: it's worth noting that employing getNodeOfType as a factory function is not considered bad practice.

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

The 'locale' parameter is inherently assigned the type of 'any' in this context

I have been using i18n to translate a Vue3 project with TypeScript, and I am stuck on getting the change locale button to work. Whenever I try, it returns an error regarding the question title. Does anyone have any insights on how to resolve this issue? ...

Ways to encourage children to adopt a specific trait

Let's discuss a scenario where I have a React functional component like this: const Test = (props: { children: React.ReactElement<{ slot: "content" }> }) => { return <></> } When a child is passed without a sl ...

How can I adjust the appearance of an HTML tag within an Angular component?

I have created an Angular component with the following definition: import { Component, Input, Output, EventEmitter } from '@angular/core'; @Component({ selector: 'rdc-dynamic-icon-button', templateUrl: './dynamic-icon-button. ...

Avoiding repeated observable executions

My current task involves implementing a screen that displays constantly changing data from an API. To achieve this, I have utilized the repeatWhen() method on the observable generated by the API call for polling purposes. Additionally, I need to make an ex ...

Troubleshooting: Angular Application Fails to Launch Following Git Repository Cloning

After successfully completing the Angular Tour of Heroes tutorial with some tweaks, I attempted to push it to a GIT repo in VSTS and clone it onto another machine. However, upon cloning it to the new machine, I am encountering issues trying to get it up an ...

How to set a default option in a dropdown menu using Angular 4

Many questions have been raised about this particular issue, with varying answers that do not fully address the question at hand. So here we go again: In my case, setting the default value of a dropdown select by its value is not working. Why is that so? ...

Is it possible for an app's feature module to access routing configurations from another lazily loaded module in Angular routing?

The functionality of our App is divided into multiple feature modules that are lazily loaded. Each module is loaded under different path matches, and some modules import a shared module containing reusable components. Everything seems to be working well so ...

Highchart in ionic 2 not displaying

I inserted code for a highchart on my webpage, but it's not appearing I followed instructions from this video tutorial https://www.youtube.com/watch?v=FSg8n5_uaWs Can anyone help me troubleshoot this issue? This is the TypeScript code I used: ts; ...

Error message in Angular 8: Cannot be assigned to AsyncValidatorFn

I am currently working on implementing a custom validator function in a reactive form. Here is the code snippet: form.component.ts ... form = new FormGroup({ username: new FormControl('', [ Validators.required, ...

What is the syntax for assigning a public variable within a TypeScript function?

I am finding it challenging to assign a value to a public variable within another function. I seem to be stuck. export class MyClass{ myVar = false; myFunction(){ Module.anotherFunction(){ this.myVar = true; } } } ...

Dynamic autocomplete in Oclif utilizing an HTTP request

Is it feasible for Oclif to support the functionality of making API calls to retrieve values for autocomplete? Consider this scenario: A database stores multiple users information Upon typing show users <Tab> <Tab>, the CLI triggers an API ca ...

The functionality of Angular 9 Service Worker appears to be inhibited when hosting the site from a S3 Static Website

I created a Progressive Web Application (PWA) following these steps: Started a new Angular app using CLI 9.1.8; Added PWA support by referring to the documentation at https://angular.io/guide/service-worker-getting-started; Built it with ng build --prod; ...

The issue arises when attempting to apply CSS styles to an existing component or body tag,

I am currently working on an Angular 7 project where I have a requirement to dynamically load a component using routes. However, when I try to add styles for the body tag and existing component tags in the dynamically loaded component style-sheet, they do ...

Encountering errors after updating to Angular Material version 6.4.7

I recently updated my Angular/Material to version 6.4.7 in my Angular2 project, but now I am experiencing a number of errors. I suspect that this may be due to the fact that my Angular/CLI version is at 1.7.4. Is there a way for me to successfully integra ...

Is Babel necessary for enabling JavaScript compatibility in my TypeScript React project, excluding Create React App?

This is the webpack configuration for my React project built in TypeScript, module.exports = { mode: 'development', entry: ['./src/main.tsx'], module: { rules: [ { // Rule for ts/tsx files only, no rule for js/js ...

Can you explain the meaning of `((prevState: null) => null) | null`?

Upon encountering this code snippet: import { useState } from "preact/hooks"; export default function Test() { const [state, setState] = useState(null); setState('string'); } An error is thrown: Argument of type 'string' ...

Angular tests are not reflecting HTML changes when there is a modification in the injected service

I'm currently testing a component that dynamically displays a button based on the user's login status. The user details are obtained from a service method, and the component uses a getter to access this information. Inside the component: get me ...

In Vue 3, the v-model feature is utilized as parameter passing instead of using :prop and @emit

I've been trying to implement two-way binding using v-model in Vue.js based on this article. The idea is to pass values from a parent component to a child component with automatic event emission when the value changes in the child component. However, ...

Issue: Query is not re-executing after navigatingDescription: The query is

On my screen, I have implemented a query as follows: export const AllFriends: React.FunctionComponent = () => { const navigation = useNavigation(); const { data, error } = useGetMyProfileQuery({ onCompleted: () => { console.log('h ...

How can a custom event bus from a separate account be incorporated into an event rule located in a different account within the CDK framework?

In account A, I have set up an event rule. In account B, I have a custom event bus that needs to act as the target for the event rule in account A. I found a helpful guide on Stack Overflow, but it was specific to CloudFormation. I am providing another a ...