Enhancing Angular Models with Property Decorators

In my Angular 8 application, I faced an issue where the backend model and frontend model are not identical. For example, the backend model stores dates in SQL format while I needed them in a JavaScript friendly format on the frontend.

To tackle this problem, instead of creating a new property in the class for mapping the date, I considered using a decorator for the date property. Here's how it looks:

Method #1: Conventional Approach - Introduce a new created property with the correct date format:

export class Message {
    id: number;
    message: string;
    visitor: Visitor;

    createdAt: string; /* Backend model created date */
    created: Date; /* Frontend JavaScript date */
}

/* API Call in Service */

public getMessages(visitor_id: number) : Observable<Messages>  {
    return this.httpClient.get<Messages>(`${API_URL}/api/SampleData/Messages?visitor=${visitor_id}`).pipe(

      map(v => {
        v.model.map(i => {
          i.created = moment(i.createdAt.replace('T', ' '), 'YYYY-MM-DD HH:mm:ss').toDate() ;
          return i;
        })
        return v;
      })

    );
  }

Method #2: Cleaner Approach Using Property Decorators:


export class Message {
    id: number;
    message: string;
    visitor: Visitor;

    @DateTransform()
    createdAt: string;
}

function DateTransform() {
  return function (target: any, key: string) {
    Object.defineProperty(target, key, { 
      configurable: false,
      get: () => {
        console.log('trying to get value:' + key); /* This line doesnt fire */
        return moment(key.replace('T', ' '), 'YYYY-MM-DD HH:mm:ss').toDate() 
      }
    });
  }
}

/* And in the component html */

<span class="kt-chat__datetime">{{ message.createdAt | amTimeAgo }}</span>

The second method appears to be more appropriate, however, the getter function is not triggered, and the component template still displays the old value. Hence, I have the following questions:

  1. Why is the getter not being executed for the desired functionality?
  2. Can the getter return a different type (date) rather than the original string?
  3. Lastly, is utilizing decorators the correct approach in this scenario?

Thank you

Answer №1

If you're facing an issue, try utilizing typescript decorators as a solution;

Your query seems to have been resolved already, you can refer to this resource for more information: How to Create a Simple Typescript Metadata Annotation

Answer №2

If you want to avoid manual conversion, consider utilizing the class-transformer library to automatically convert JSON data into objects. This library also allows for customization using the @Transform annotation to tailor how values are transformed.

Take a look at this example:

import {plainToClass} from "class-transformer";

class User {
    id: number;
    firstName: string;
    lastName: string;

    @Type(() => Date)
    @Transform(value => moment(value), { toClassOnly: true })
    date: Moment;
}

const fromPlainUser = {
    unkownProp: 'hello there',
    firstName: 'Umed',
    lastName: 'Khudoiberdiev',
    date: '2013-02-08 09:30 '
}

console.log(plainToClass(User, fromPlainUser))

// User {
//   unkownProp: 'hello there',
//   firstName: 'Umed',
//   lastName: 'Khudoiberdiev',
//   date: Date Object
// }

For more examples and information, refer to the README section of the library.

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

Enforcing custom validation with Angular 2 upon keyup event

I am having trouble with the code below: this.form = fb.group({ username: ['', Validators.compose([Validators.required])], fullName: ['', Validators.compose([Validators.required])], password: ['', Vali ...

Clear all events from an HTML element and its descendants with TypeScript

Each time the page loads, I have HTML from an API that is constantly changing. Is there a way to strip away all events attached to it? The original HTML looks like this: <div id="content"> <h2 onclick="alert('hi');">Test 1< ...

The lack of change detection triggering in Angular 8's UI observables could be due to the default setting of ChangeDetection

I am facing a situation where I have a container component with a service injected into it. The service holds an observable, and I am binding this observable to a child component's input property in the container component's template using an asy ...

Monitoring mouse events on a child element with an attached directive in Angular 7

Is it possible to capture mouse events that occur only within the child element of the directive's host element using @HostListener()? <div listnerDirective class="parent"> <div class="child> <------ Listen for mouse events here ...

Is it possible to choose the inverse of a user-defined type in Angular?

Is it possible to retrieve the opposite of a specified custom type within a variable using Typescript? For example, if I define a type like this: type Result = 'table' | 'grid'; Then any variable with the type Result can only be assign ...

Learn how to effortlessly move a file into a drag-and-drop area on a web page with Playwright

I am currently working with a drag-zone input element to upload files, and I am seeking a way to replicate this action using Playwright and TypeScript. I have the requirement to upload a variety of file types including txt, json, png. https://i.stack.img ...

Arranging React Grid Items with Stylish Overlapping Layout

Is there a way to create a react-grid-layout with 100 grid points width, while ensuring that the grid items do not overlap? https://i.sstatic.net/CQiVh.png (Reducing the number of columns can prevent overlap, but sacrifices the 100-point width resolution ...

Error: Unable to locate namespace 'google' in TypeScript

I am currently working on an angular-cli project. ~root~/src/typings.json { "globalDevDependencies": { "angular-protractor": "registry:dt/angular-protractor#1.5.0+20160425143459", "jasmine": "registry:dt/jasmine#2.2.0+20160621224255", "sele ...

ngx-charts - Horizontal Bar Charts dynamically adjust bar thickness when data is updated

Using Angular and ngx-charts to display data fetched from the server works perfectly on initial load trigger with the ngOnInit() method calling getStats() function. Everything loads as expected: https://i.sstatic.net/8OsbD.png However, upon clicking the ...

Instructions for disabling editing for a specific cell within an inline editable row in primeNG

I am currently using PrimeNG DataTable with Angular, where the rows are editable as shown in the example in the documentation: https://www.primefaces.org/primeng/#/table/edit. However, I am facing an issue where I want to exclude one cell from being editab ...

The ts-node encountered an issue with the file extension in this message: "TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension

When using ts-node, I encountered the following error: $ ts-node index.ts TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for /home/projects/node-hddds8/index.ts I attempted to remove "type": "module& ...

Tips for importing a non-namespaced module TypeScript definition into my custom types

When working with my custom types, I want to utilize the GraphQLSchema from the graphql module. If I simply write: interface MyThing { schema: GraphQLSchema } It doesn't reference the actual GraphQLSchema definition from the module (it's just ...

When you click, you will be directed to the specific details of the object

I have a recipe component that displays a list of recipes from my database and a recipe-detail component that should show the details of a selected recipe. What I aim to achieve is that when someone clicks on a recipe name, they are routed to the recipe-de ...

The template literal expression is being flagged as an "Invalid type" because it includes both string and undefined values, despite my cautious use of

I am facing an issue with a simple component that loops out buttons. During the TypeScript build, I encountered an error when calling this loop: 17:60 Error: Invalid type "string | undefined" of template literal expression. In my JSX return, I ...

Leveraging the ng-template in a broader context

I need to create a recursive menu from JSON data with a specific structure. The JSON looks like this: this.menus = [{ "id": 1, "title": "Azure", "class": "fa-cloud", "url": "#", "menu": [{ "id": 121, ...

Using a template literal with the keyof operator as an interface property

Imagine a scenario where we have a basic interface: interface Schedule { interval: "month" | "day"; price: number; } We can create a template-literal type with TypeScript version 4.4: type PrefixedScheduleKey = `schedule__${keyof S ...

Guide to encoding an array of objects into a URI-friendly query string using TypeScript

Just getting started with typescript and looking for some help. I have an input array structured like this: filter = [ { field : "eventId", value : "123" }, { field : "baseLocation", value : "singapore" } ] The desired format for ...

Displaying an observable array in Angular 8 using console.log

Having an issue displaying the response from an observable on the console in my Angular project. It seems to be coming back as undefined when retrieved from a service. Even when attempting to loop through the data, I get an error stating that it is not ite ...

Converted requests from loopback to angular2 resulted in an error message, as the script's MIME type ('text/html') was determined to be non-executable

Currently, I have set up loopback as the backend API and angular2 as the frontend for my project. Loopback is serving my angular2 frontend without any issues. However, whenever I refresh a page, loopback seems to struggle with handling the URL because tha ...

Tips for resolving the 'NullInjectorError: StaticInjectorError' issue in Angular 8 resolver

UPDATE: The issue only appears in development mode and disappears when running ng build or ng serve with the --prod flag. While working on a web application, I encountered a setback following an upgrade to Angular 8. In one of my components, I started see ...