The Angular Property Decorator ensures that only one instance of a property is created per Class Type

I have implemented a Property Decorator that creates an Observable with static getter/setter for each property.

Usage of the decorator looks like this:

class Test {
    @ObservableProperty(DEFAULT_CATS) 
    cats: number;

    @ObservableProperty(DEFAULT_PIGS) 
    pigs: number;
}

The code for the decorator itself is as follows:

export function ObservableProperty(defaultValue = null): any {
    return (target, key, descriptor) => {
        const accessor = `${key}$`;
        target[accessor] = new BehaviorSubject(defaultValue);

        return Object.assign({}, descriptor, {
            get: function() {
                return this[accessor].getValue();
            },
            set: function(value: any) {
                this[accessor].next(value);
            },
        });
    };
}

Everything functions correctly with one instance of the Test component. However, when using two instances, the test fails.

fdescribe('ObservableProperty Decorator', () => {
    let test: Test;
    let doppleganger: Test;

    beforeEach(() => {
        test = new Test();
        doppleganger = new Test();
    });

    it('should create different observables for each props', () => {
        expect(test['cats$'] === doppleganger['cats$']).toBe(false);
    });
})

Due to the way the decorator operates on the prototype of component instances, the created variables are the same for both instances.

Is there a workaround for this issue using the decorator? If not, what would be a more elegant alternative?

Answer №1

After pondering for a day, I have finally found the solution to this question.

The main issue preventing me from accessing the instance was the arrow function used in the decorator definition. I made a change from:

 return (target, key, descriptor) => {

to

return  function (target, key) {

This modification allowed me to access the instance from within the getter/setter using this.

Next, I needed to determine where to initialize the BehaviorSubject. Initializing it in the getter or setter of the main property wouldn't suffice (I wanted to access this.cats$ without first accessing this.cats).

I resolved this by introducing a new getter for cats$, which stores the variable in a secret property and creates it if it doesn't already exist.

Below is the final code snippet!

export function ObservableProperty(defaultValue = null): any {
    return  function (target, key) {
        const accessor = `${key}$`;
        const secret = `_${key}$`;

        Object.defineProperty(target, accessor, {
            get: function () {
                if (this[secret]) {
                    return this[secret];
                }
                this[secret] = new BehaviorSubject(defaultValue);
                return this[secret];
            },
            set: function() {
                throw new Error('You cannot set this property in the Component if you use @ObservableProperty');
            },
        });

        Object.defineProperty(target, key, {
            get: function () {
                return this[accessor].getValue();
            },
            set: function (value: any) {
                this[accessor].next(value);
            },
        });
    };
}

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

Securing access to a RESTful web service in AngularJS 2

I'm looking to access an API that returns a JSON file, but I'm unsure of how to include the authentication in my HTTP header. Here's my TypeScript service code: import {Http} from 'angular2/http'; import 'rxjs/add/operator/ma ...

Displaying a message indicating no results have been found in the typeahead dropdown using Angular Bootstrap

Is there a way to display a "No Results Found" message in the typeahead search results if no matching data is found? Any suggestions on how to achieve this? See the attached reference screenshot for an example of the message. https://i.sstatic.net/fDpJZ.p ...

It is not possible to install Angular CLI on a Mac computer

My Mac computer is currently running macOS Ventura 13.1 I managed to install node (v18.12.1) & npm (8.19.2) successfully on the system. However, when I attempted to run npm install -g @angular/cli to install Angular CLI, it resulted in the following e ...

Here's a new take on the topic: "Implementing image change functionality for a specific div in Angular 8 using data from a loop"

I need to create a list with dynamic data using a loop. When I click on any item in the list, I want the image associated with that item to change to a second image (dummyimage.com/300.png/09f/fff) to indicate it's active. This change should persist e ...

What is the method for extracting user input from a text box on a webpage?

Having trouble with retrieving the value from a text box in my search function. SearchBar Component import { Component, OnInit, Input } from '@angular/core'; @Component({ selector: 'app-search', templateUrl: './search.compon ...

Function 'Once' in Typescript with Generics

Currently, I am utilizing a feature called Once() from FP. In TypeScript, I need to define the types for this function but have been struggling with the implementation. Here's what I have attempted so far: const once = <T, U>(fn: (arg: T) => ...

When employing a string union, property 'X' exhibits incompatibility among its types

In my code, I have two components defined as shown below: const GridCell = <T extends keyof FormValue>( props: GridCellProps<T>, ) => { .... } const GridRow = <T extends keyof FormValue>(props: GridRowProps<T>) => { ... & ...

Tips for managing multiple events within a single component in Angular 4

Currently, I am working on developing a user interface layer for an application using Angular 4. The page layout I have consists of displaying data in tables based on search criteria. At the moment, I have code that displays the data in one HTML file (rec ...

Retrieving a nested type based on a particular condition while keeping track of its location

Given an object structure like the one below: type IObject = { id: string, path: string, children?: IObject[] } const tree = [ { id: 'obj1' as const, path: 'path1' as const, children: [ { id: &ap ...

What is the method for modifying the label of the "No Filter" choice in the PrimeNG Column Filter?

I am looking to customize the label for the "No Filter" option by translating it to "Sin Filtro" in Spanish. I have some knowledge about using the FilterMatchMode from FilterService to update filter match modes labels, like so: export const Filter ...

Error: Router service provider not found in Angular 2 RC5!

Having trouble with using this.router.navigate. Here is the content of my app.module.ts file: import {NgModule, NgModuleMetadataType} from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; im ...

Unable to halt the Observable interval

I've exhausted all possible solutions I could find, but I can't seem to stop the code inside the subscribe function of this interval. Could it be that I'm using the interval incorrectly? var subject = new Subject(); var interval = Observabl ...

Oops! It seems like there was an issue with trying to access a property that doesn't exist

Whenever I try to insert a new line into my table, I encounter the following error message: ERROR TypeError: Cannot read property 'Nom' of undefined at Object.eval [as updateDirectives] (MedecinsComponent.html:43) at Object.debugUpdateDirect ...

Consistentize Column Titles in Uploaded Excel Spreadsheet

I have a friend who takes customer orders, and these customers are required to submit an excel sheet with specific fields such as item, description, brand, quantity, etc. However, the challenge arises when these sheets do not consistently use the same colu ...

What causes a delay in HTTP calls in Chrome when it is in the "Stalled" or "Initial Connection" state? Is it possible for these states to generate multiple threads for the same request?

My application uses Angular on the client side and Java (Spring Boot) on the backend. Occasionally, during some network calls, the waterfall chart gets stuck in either the "Stalled" or "Initial Connection" state. When this happens, I have noticed in my log ...

Unable to modify data with ionic and firebase in child node format

I am encountering an issue when updating data with Ionic Firebase using the following code. Instead of rewriting the previous data, it simply creates new data entries. Here is the code snippet: updateLaporan() { this.id =this.fire.auth.cur ...

Utilizing i18next for both a custom Typescript library and a host simultaneously: a step-by-step guide

Currently, I am in the process of developing a typescript library that is designed to take in an object and generate an excel file. This library is intended for use with multiple React applications. Each React application, or host, will provide its own obj ...

(iOS) Detecting input from keys with non-ascii characters captured

I am attempting to subscribe to physical keyboard events (excluding non-ASCII keys) in my app developed using the Ionic Framework (issue arises when trying to access a page launched by ionic serve, deploying the app on my iOS device, or running it in an iO ...

Unable to locate useTabListState type in React Aria

I've been experimenting with react-aria tabs and attempting to incorporate types into their demo. However, I'm uncertain about which type to utilize for the props within the tabs component. The useTabListState function utilizes TabListStateOptio ...

Conduct surveillance on the service function call within the constructor

I am currently facing a challenge with trying to monitor a service function call that is executed in the constructor. The test is straightforward, simply aiming to confirm that the function call is indeed made. beforeEach(async(() => { TestBed.con ...