Using Angular 2's custom Class Decorator for convenient access to the Dependency Injection Service

I'm looking to implement a class decorator called TopicClass that will add both a property and a function to the decorated component. The catch is that the function needs to be able to access an injected Service. So, how can I achieve this?

So far, my attempts have been unsuccessful:


@Component({
  selector: 'home',
  styleUrls: ['./home.component.css'],
  templateUrl: './home.component.html'
})
@TopicClass('home')
export class HomeComponent {
  constructor(private topicService: TopicService) { }
}

The issue arises when trying to access the injected Service using the inserted function ngAfterViewInit.


export function TopicClass(title: string) {
  return function (target: Function) {
    const original = target;

    function construct(constructor, args) {
      const c: any = function () {
        return constructor.apply(this, args);
      }
      c.prototype = constructor.prototype;

      const newInstance = new c();
      newInstance['topic'] = new Topic(title, '');

      newInstance['ngAfterViewInit'] = () => {
        newInstance['topicService'].setTopic(newInstance['topic']);
      }
      return newInstance;
    }

    const ctor: any = (...args) => {
      console.log("Service: " + original.prototype.topicService);
      return construct(original, args);
    };

    ctor.prototype = original.prototype;
    return ctor;
  }
}

The problem lies in the fact that newInstance['topicService'] is undefined.

To test this scenario, I've set up a simple Angular project at: https://github.com/ptea/angular-class-decorator-test

You can find the service setup at: https://github.com/ptea/angular-class-decorator-test/blob/master/src/app/services/topic.service.ts

In an attempt to replicate the issue with a basic TypeScript program, I achieved the desired outcome:


newInstance['printStreet'] = () => {
  console.log(`printFirstnameStreet: ${newInstance['firstname']}, ${newInstance['street']}`);
}

Here's the link to the TypeScript program: https://github.com/ptea/angular-class-decorator-test/blob/master/dashboard.ts

If you have any ideas or solutions to this dilemma, please share!

Answer №1

The main issue causing the TopicService to be undefined is the lack of injection into the component. By altering the decorator in your code, you are essentially overriding the constructor of the HomeComponent and eliminating its ability to receive injected services like TopicService. This disrupts Angular's Dependency Injection process and results in the service not being properly injected when the Component is created.

A more effective approach would be to avoid modifying the constructor of the HomeComponent and instead incorporate the service logic within the ngOnInit method. The ngOnInit method is suitable for this task as it is called once during the component's lifecycle with no parameters, making it straightforward to wrap inside another function. You can also utilize the ngAfterViewInit function to interact with the service at a later stage if needed.

If you wish to achieve the desired outcome, consider adjusting the TopicClassDecorator as shown below:

export function TopicClass(title: string) {
  return function (target: Function) {

    let targetNgOnInit = target.prototype.ngOnInit;
    target.prototype.ngOnInit = function (){
      this.topic = new Topic(title, 'subTitle');

      if(targetNgOnInit){
        targetNgOnInit.apply(target);
      }
    }

    let targetNgAfterViewInit = target.prototype.ngAfterViewInit;        
    target.prototype.ngAfterViewInit = function (){
      this.topicService.setTopic(this.topic);

      if(targetNgAfterViewInit){
        targetNgAfterViewInit.apply(target);
      }
    }

    return target;
  }
}

Check out this Demo Plunkr to see everything in action.

Answer №2

After much consideration, I ultimately decided to implement the solution below instead of overriding the ngOnInit function:


export function TopicClass(title: string) {
  return function (target: Function) {
    target.prototype.topic = new Topic(title);

    let targetNgAfterViewInit = target.prototype.ngAfterViewInit;        
    target.prototype.ngAfterViewInit = function () {
      this.topicService.setTopic(this.topic);

      if(targetNgAfterViewInit){
        targetNgAfterViewInit.apply(target);
      }
    }
  }
}

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

Error in Typescript: Issue with Object.fromEntries Typescript Error

In my TypeScript code, I have a function that utilizes Object.fromEntries to simplify a complex response object and organize it by using a substring of the child object key. let Newresult = res.map(object => Object.fromEntries(Object.entries(object).ma ...

Retrieving deeply nested objects within an array of objects in MongoDB

Here is the structure of my database: [ { "title": "man", "articlesType": [ { "title": "shoes", "articles": [ { ...

Chrome does not support the browser name feature in the Javascript window navigator

Many believe that the javascript navigator function can be used to retrieve all browser-related properties of a particular machine. I attempted to do so myself, which you can find here: <!DOCTYPE html> <html> <body> <p id="demo" ...

The addition of input fields on keyup creates problems in the initial field of each row

I am currently working with a table and attempting to calculate the sums as follows: td(1) + td(2) + td(3) = td(4), td(5) + td(6) + td(7) = td(8), td(9) + td(10) + td(11) = td(12). This is the code I have implemented: $(document).ready(function () { ...

What is the alternative method for duplicating an array in javascript without resorting to JSON.stringify or JSON.parse?

In this scenario, I have an array labeled as fruit. My goal is to duplicate it into a new array called fruits2, but without maintaining any reference connections between them. The provided example shows how simply assigning the original array to a new var ...

Automatically fill out form fields by selecting data from a spreadsheet

I successfully created a web application using Google Apps Script (GAS) that sends data on submission to Spreadsheet A. Furthermore, I have implemented a select option that dynamically fetches data from another spreadsheet B ("xxx") in column A. Below is ...

What is the process for creating a fade out effect with JavaScript?

https://i.sstatic.net/1qgWt.png Is there a way to create a fade out effect in JavaScript? In the image provided, you can see that when you click on a day, a box slides down. If you click on another day, the previous box slides back up while a new box slid ...

Using JavaScript to trigger actions via links or buttons inside a table will only function properly in the first row

After multiple consecutive Ajax requests to refill an HTML table, there seems to be a strange issue. The links in the first row of the table are functioning properly and call JavaScript functions, but for any subsequent rows, the links or buttons stop work ...

Ensure Website Accessibility by Implementing Minimum Resolution Requirements

Is it possible to create a website that only opens on screens with a resolution of at least 1024 x 768, and displays an error message on unsupported resolutions? I've tried using JavaScript to achieve this, but haven't had any success. Any assis ...

Establish characteristics of a class according to a general parameter

class Component { } class ShallowWrapper { } // creating a TestContainer class with generic types T and P class TestContainer<T extends Component, P extends object> { constructor(reactElement: T, selectors: P) { // iterating over select ...

How can I create a dropdown menu that is dependent on another dropdown menu using Ajax in my Laravel application?

I have two dropdown fields that are dependent on each other - Class & Section. I am trying to Select * from sections where class_id=selected Class Id. Although I attempted to achieve this using java script, it doesn't seem to work for me. Here are ...

Angular 2's Cache Module

Is it possible to cache a component? Imagine this scenario: you're on a page with search results, and you decide to click on a result to view more details (which takes you to another page). If you were to use the browser's back button to return ...

What is the best way to reset an animation back to its original state after clicking the button again?

Forgive my lack of understanding, but I can't seem to figure this out. I have a navigation bar centered both horizontally and vertically with a button inside. When the button is clicked, the nav bar animates to the top of the page with a slight margin ...

Passing an object as a string in Angular2 select box

I'm currently working on an Angular2 component where I am looping through an array of objects to populate options for a form select box. The issue I'm facing is that when the select box option is changed, I call a handler function to pass the ch ...

Guide to updating the object value within an array in React

Is there a way to toggle the value of a specific object key called "clicked" to true using the spread operator in React? I want to be able to press a button and update the object's value. let questions = [ { title: "What do I want to learn ...

Move your cursor over the image to activate the effect, then hover over it again to make the effect disappear

Looking to enhance my images with hover effects. Currently, all the images are in grayscale and I'd like to change that so that when you hover over an image, it reverts to full color and remains that way until hovered over again. I've noticed so ...

What is the best way to assign values to multiple form elements simultaneously?

My FormBuilder-built form contains numerous control elements, and I am seeking a more efficient method to set their values based on server responses. Currently, I am handling it in the following manner: this.form.controls['a'].setValue(data.a); ...

When attempting to insert, no action occurs with mongoose

Here is the schema I am using: module.exports = function (mongoose) { var playlist = mongoose.Schema({ title: String, artist: String, album: String, time: Date }); return mongoose.model('playlist', pl ...

When the page loads, the jQuery accordion menu will be closed by default

Looking to optimize my vertical accordion menu by adding interactive features. Successfully implemented functionality and CSS styling for the menu. An issue arises on page load with one category being automatically open, even if I remove the "open" class ...

How to remove a material form field in a dynamic form with a single parameter

Deleting the single param mat form field proves to be a challenge, but deleting the multiple param form field is successful. <ng-container *ngSwitchCase="'input'"> <ng-container *ngIf="param.allowMultiple; else singlePar ...