Angular: defining a new class instance and implementing various services

Creating a class called User raises the concern of having access to services within it, especially when using the constructor() for dependency injection.

Below is an implementation of the User class:

import { ElementRef, Injectable } from '@angular/core';
import { DbService } from '../service/db.service';
import { ImageService } from '../service/skin.service';

export class User {
    name: string;
    img: ElementRef<HTMLCanvasElement>;

    constructor(
        name: string,
        img?: ElementRef<HTMLCanvasElement> | string[]) {

        this.name = name;

        if (img) {
            if (img instanceof ElementRef) {
                this.img = img;
            } else {
                this.img.nativeElement = this.ImageService.render(img);
            }
        } else {
            DbService.getSkin(name).subscribe(res => this.img.nativeElement.this.service.render(res));
        }
    }
}

Rather than making required functions public static, what is the best way to incorporate DbService and ImageService into the User class?

Answer №1

One way to improve the code is by following the principles of SOLID and dividing responsibilities effectively.

An entity called User has properties like name and img. It's recommended that the User entity does not have knowledge about the DBService.

To manage this, you can implement a factory that creates instances of User, where the factory itself is aware of the DBService. The factory could operate in two ways:

  • Create and provide a synchronous instance of User, then asynchronously fill the img property if needed by calling DbService.getSkin
  • Always create and return an asynchronous instance of User through an observable

The second option is preferred due to its more predictable behavior and avoids unexpected updates to the img property.

The revised code would look like this:

Case 1 - Return a User instance

interface User {
  name: string;
  img: ElementRef<HTMLCanvasElement> | string[];
}

@Injectable({providedIn: 'root')
export class UserFactory {
  constructor(private dbService: DBService) {}

  create(
    name: string,
    img?: ElementRef<HTMLCanvasElement> | string[],
  ): User {
    const user: User = {
      name,
      img,
    }

    if (!img) {
      this.dbService.getSkin(name).pipe(
        tap(someResponse => {
          /* 
            SET IMG HERE
            user.img = someResponse
          */
        }),
      ).subscribe();
    }

    return user;
  }
}

Case 2 - Return observable

interface User {
  name: string;
  img: ElementRef<HTMLCanvasElement> | string[];
}

@Injectable({providedIn: 'root')
export class UserFactory {
  constructor(private dbService: DBService) {}

  create(
    name: string,
    img?: ElementRef<HTMLCanvasElement> | string[],
  ): Observable<User> {
    const user: User = {
      name,
      img,
    }

    let user$: Observable<User> = of(user);

    if (!img) {
      user$ = this.dbService.getSkin(name).pipe(
        map(someResponse => {
          /* 
            SET IMG HERE
            user.img = someResponse
          */
          return user;
        }),
      )
    }

    return user$;
  }
}

In both scenarios, the factory handles API interactions. This approach leads to a clearer structure and enhances testability.

Answer №2

When setting up the constructor, make sure to initialize the service by passing it as an argument:

constructor(name: string,
        img?: ElementRef<HTMLCanvasElement> | string[], private imageService: ImageService) {
        
}

Afterwards, you can easily access your service by using:

this.imageService.getMethod()

Answer №3

To simplify your code structure, consider creating a factory service that allows you to inject necessary services. This factory service can include a method called build, which takes the required parameters for creating a User object along with any additional services:

@Injectable({providedIn: 'root')
export class UserFactory {
  constructor(private _yourService: YourService) {}

  build(name: string, img?: ElementRef<HTMLCanvasElement> | string[]) {
    return new User(name, img, this._yourService);
  }
}

You can then utilize this factory service in the following manner:

@Component({...})
export class YourComponent {
  ...

  constructor(private _uFactory: UserFactory) {}

  ...

    const user = this._uFactory.buid(name, img);

  ...
}

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

Isolated PWA disrupts login functionality

We currently have a Progressive Web App built with Angular that integrates AzureAD for login authentication using ng-adal. Upon logging in, a series of redirects occur until we are redirected back to our app with the authentication ticket. The issue arise ...

What is the best way to determine the property type dynamically in TypeScript based on the value of another property?

I have been working with Polymorphic relationships and currently have the following TypeScript interface defined: interface SubjectA {} interface SubjectB {} interface SubjectC {} enum SubjectType { SubjectA = 'Subject A', SubjectB = 'S ...

What is the method to deactivate a submit button when there have been no alterations in an input field?

I am facing an issue with multiple input fields containing user data from the database that can be edited. Even if no changes are made, the submit button remains enabled. How do I disable it? Here is the relevant function snippet from my code: const handle ...

The functionality of ngClass fails to toggle the value appropriately when utilizing an object within a component

When implementing the code below to toggle the class, everything runs smoothly <p [ngClass]="idPresent ? 'alert alert-success' : 'alert alert-danger'"> Working </p> After changing the value of IdPresent in the c ...

Tips for elegantly merging two Observables within an RXJS pipeline

I am working on developing a log viewer using Angular. Upon user entry, I aim to load historical logs and also begin monitoring for new logs. Users have the ability to filter logs using a simple form that emits a query object. Each time the query changes, ...

Angular TextInput Components don't seem to function properly when dealing with arrays

I am trying to create a collection of text input components with values stored in an array. However, when using the following code, the values seem to be placed incorrectly in the array and I cannot identify the bug. <table> <tr *ngFor="let opt ...

What is the rationale behind declaring variable System as any?

The repository for Angular material can be found on GitHub at https://github.com/angular/material.angular.io. Within the source code, there is a typings.d.ts file that contains a type definition: declare var System: any; I am curious about the purpose of ...

The Angular HttpClient Service will exclusively provide responses that have a status code of 200

I'm facing an issue with my login component where it calls an http client service to send a request to the server for logging in. Everything works smoothly when I enter valid credentials, but if I input wrong credentials, the service doesn't seem ...

The functionality of the KendoReact Grid for filtering and sorting is not functioning correctly when data is grouped

Once I group the data, the filter and sort functions in the KendoReact Grid stop working. Interestingly, if I bypass the grouping step and show the data without grouping, the filter and sort functions function perfectly fine. My main objective is to figu ...

Strategies for implementing recursive component calling while maintaining nested level monitoring in React Native

Within my application, I have two key components: CommentList and Comment. The CommentList component is responsible for rendering comments by calling the Comment component through a map function, which loops ten times. A unique feature of my app is that e ...

Which to Choose: Classes or Variables?

When retrieving information from a database, such as items from an invoice, should I simply extract the data into a data table and store it in variables, or should I create a class? Which option is preferable? MyClass.ItemNumber; MyClass.Price; MyClass.I ...

Concerns regarding Zod Schema Enhancements and Conditional Logic in TypeScript

I'm currently developing a robust Zod validation schema for an input field that handles product names. This schema needs to be able to adapt to various validation requirements, such as minimum/maximum length and the inclusion of special characters. B ...

Troubleshooting: Why is the Array in Object not populated with values when passed during Angular App instantiation?

While working on my Angular application, I encountered an issue with deserializing data from an Observable into a custom object array. Despite successfully mapping most fields, one particular field named "listOffices" always appears as an empty array ([]). ...

Issue: An object with keys {} is not suitable as a React child, causing an error

I am new to TypeScript and seeking help from the community. Currently, I am working on a to-do list project where I am using React and TypeScript together for the first time. However, I encountered an error that I cannot decipher. Any assistance would be g ...

What could be the reason behind the React Router Link component refusing to accept children in scenarios where it is being utilized as a variable

When using Link, I have encountered no issues. It has the ability to accept children. import { Link } from 'react-router-dom'; <Link to=''> {content} </Link> Similarly, <button> can also be used in a similar mann ...

Tips for configuring Nodejs v19+ to prioritize TLS 1.2 and prevent the occurrence of EPROTO SSL routines error messages such as ssl3_read_bytes:sslv3 alert handshake failure and SSL alert number 40

I've been attempting to use NodeJs to send GET requests to a website that has disabled TLS 1.3. Unfortunately, every request I make results in a handshake exception similar to this: Error: write EPROTO 00683890CA7F0000:error:0A000410:SSL routines:ssl ...

Fix a typing issue with TypeScript on a coding assistant

I'm struggling with typing a helper function. I need to replace null values in an object with empty strings while preserving the key-value relationships in typescript // from { name: string | undefined url: string | null | undefined icon: ...

Conditional Skipping of Lines in Node Line Reader: A Step-by-Step Guide

I am currently in the process of developing a project that involves using a line reader to input credit card numbers into a validator and identifier. If I input 10 numbers from four different credit card companies, I want to filter out the numbers from thr ...

Ensure that the Angular Material Autocomplete (mat-autocomplete) always appears below the input field

Is there any way to make the Angular Material Autocomplete(mat-autocomplete) always open at the bottom of the input field? Do I need to use a position configuration or can this be achieved with CSS techniques? ...

Utilize a union type variable within an async pipe when working with Angular

Within my template, I have implemented an async pipe for input binding using a union type variable : <app-mycomponent *ngSwitchCase="'myType'" [target]="myVar| async"></app-mycomponent> The variable myVar can be ...