Obtain the title of the function currently running in Angular 2 with TypeScript

Seeking assistance for an Angular 2 application employing TypeScript.

In order to enhance my logging process, I am looking to dynamically retrieve and log the name of the class or component along with the function that triggered the logging action.

Currently, I have resorted to hard-coding these values within the code (written in TypeScript):

class MyService {
  getUsers() {
    console.log('MyService.getUsers()', 'I am here');
  }

However, I am eager to find a more efficient approach rather than hardcoding the class and function names.

I have successfully obtained the name of the class or component using:

console.log(MyService.name);

Nonetheless, I am now faced with the challenge of retrieving the name of the currently executing function. Any suggestions on how this can be achieved?

Answer №1

To utilize the functionalities in this answer, Lodash is required. However, you can easily replace that code with their equivalent JavaScript functions.

LogService.ts

import {FactoryProvider, Injectable, InjectionToken, Optional} from '@angular/core';
import * as _ from 'lodash';

const PREFIX_SEPARATOR = ':';

@Injectable()
export class LogService {
    /**
     * Global logger
     */
    public static log: LogService = new LogService();

    /**
     * Creates an injectable logger for module use.
     */
    public static provide(token: InjectionToken<LogService>): FactoryProvider {
        return {
            provide: token,
            useFactory: (log: LogService) => {
                let prefix = token.toString().replace(/^InjectionToken\s/, '').replace(/Logger$/, '').toUpperCase();
                return log.prefix(prefix);
            },
            deps: [LogService]
        };
    }

    /**
     * Retrieves stack trace if available when browser adds stack property.
     */
    private static getStackTrace(): string[] | undefined {
        try {
            let ex = new Error() as Object;
            if (ex.hasOwnProperty('stack') && _.isString(ex['stack'])) {
                return ex['stack'].split('\n');
            }
        } catch (err) {
            // do nothing
        }
        return void 0;
    }

    /**
     * Obtains constructor name from call stack, specific to Chrome browsers.
     */
    private static getConstructorName(depth: number): string | undefined {
        if (_.isUndefined(window['chrome'])) {
            return void 0;
        }
        let trace = LogService.getStackTrace();
        if (!trace || trace.length <= depth) {
            return void 0;
        }
        let str = trace[depth];
        if (!/\s+at\snew\s/.test(str)) {
            return void 0;
        }
        let match = /new ([$A-Z_][0-9A-Z_$]*)/i.exec(str);
        if (!match || match.length < 2) {
            return void 0;
        }
        return match[1].replace(/(Component|Directive|Service|Factory|Pipe|Resource|Module|Resolver|Provider)$/, '');
    }

    /**
     * Output prefix
     */
    private prefixName: string;

    /**
     * Flag for debugging activation.
     */
    private _debug: boolean = process.env.ENV && process.env.ENV === 'development';

    /**
     * Enables optional prefix provision.
     */
    public constructor(@Optional() prefix?: string) {
        this.prefixName = prefix;
    }

    /**
     * Generates a logger with automated prefix.
     */
    public withPrefix(): LogService {
        if (!this._debug) {
            return this;
        }
        return this.prefix(LogService.getConstructorName(4));
    }

    /**
     * Generates a new logger with specified prefix for all output messages.
     */
    public prefix(prefix?: string): LogService {
        return prefix
            ? new LogService((this.prefixName || '') + prefix + PREFIX_SEPARATOR)
            : this;
    }

    public get info(): Function {
        if (!console || !console.info) {
            return _.noop;
        }
        return this.prefixName
            ? console.info.bind(console, this.prefixName)
            : console.info.bind(console);
    }

    public get debug(): Function {
        if (!this._debug || !console || !console.log) {
            return _.noop;
        }
        return this.prefixName
            ? console.log.bind(console, this.prefixName)
            : console.log.bind(console);
    }

    public get warn(): Function {
        if (!console || !console.warn) {
            return _.noop;
        }
        return this.prefixName
            ? console.warn.bind(console, this.prefixName)
            : console.warn.bind(console);
    }

    public get error(): Function {
        if (!console || !console.error) {
            return _.noop;
        }
        return this.prefixName
            ? console.error.bind(console, this.prefixName)
            : console.error.bind(console);
    }
}

LogService.module.ts

In your ngModule, export this service as a singleton and then invoke forRoot() in your main module. Import this module normally in other modules.

@NgModule({
})
export class LogModule {
    public static forRoot() {
        return {
            ngModule: LogModule,
            providers: [
                LogService
            ]
        };
    }
}

Creating loggers per module

You can create loggers with module-specific prefixes by establishing an injectable token for each module.

This example pertains to a UI module.

export const UI_LOGGER = new InjectionToken<LogService>('UILogger');

@NgModule({
    providers: [
        LogService.provide(UI_LOGGER)
    ]
})
export class UIModule {
}

You can now inject this new logger into your UI components.

@Component({})
export class MyComponent {
    private log: LogService; // logger with constructor name

    public constructor(@Inject(UI_LOGGER) log: LogService) {
        this.log = log.withPrefix();
    }
}

By calling log.withPrefix(), you create a fresh logger with the constructor name in addition to the module name.

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

Displaying grouped arrays efficiently in Angular

I have received data from an API in the form of an array with objects structured like so: [ {"desc":"a", "menu": 1},{"desc":"b", "menu": 2},{"desc":"c", "menu": 1}, ...

Generate a new data structure by pairing keys with corresponding values from an existing

Imagine having a type named Foo type Foo = { a: string; b: number; c: boolean; } Now, I am looking to create a type for an object containing a key and a value of a designated type T. The goal is for the value's type to be automatically determin ...

Different ways to invoke a general method with a deconstructed array as its argument

Calling a Generic Method with Deconstructed Array Parameters As of today, the only method to ensure typed safe inherited parameters is by using a deconstructed array and explicitly defining its type. This allows calling the parent method by utilizing the ...

Is it possible for me to make the default export anonymous?

Why bother naming the export if you already have a file with the default export name? This seems redundant and goes against the DRY principle. While there is a rule that discourages anonymous default exports, how can we enforce an error when someone does ...

"Create a separate function for the pipeable operator in RXJS for enhanced code

After working on some code, I came up with the following implementation this.form.valueChanges.pipe( take(1), map(val => // doSomething), exhaustMap(val => // someInner observable logic return of({someValue}) ) ).subscrib ...

Utilizing TypeScript with an express server and socket.io: 'App' type does not have property 'server'

Exploring a classic approach to setting up an expressjs server with socketio: this.app = express(); this.server = http.createServer(this.app); this.io = socket(this.server); Encountering errors when using TypeScript: src/server/ts/app.ts(23, ...

Is it possible for a React selector to retrieve a particular data type?

As a newcomer to React and Typescript, I am currently exploring whether a selector can be configured to return a custom type. Below is a basic selector that returns a user of type Map<string, any>: selectors/user.ts import { createSelector } from ...

Factory function with type constraints and default parameter causing TS2322 error

I have a base class that requires some parameters to be passed... class BaseClass<ItemType> { // Some irrelevant parameters omitted for simplicity... constructor(__items: Iterable<ItemType>) {} } Now, I want to create a factory func ...

The server's response is unpredictable, causing Json.Parse to fail intermittently

I have encountered a strange issue that is really frustrating. It all started when I noticed that my Json.Parse function failed intermittently. Here is the code snippet in question: const Info = JSON.parse(response); this.onInfoUpdate(Info.InfoConfig[0]); ...

Retrieve the specific type of property from a generic data structure

I am currently working on a project where I need to determine the type of property within a given Type: type FooBarType { foo: string, bar: number } The function would be structured like this: getType<K extends keyof T>(key: K): string. The ...

Create a new function and assign it to "this" using the button inside ngFor loop

I am working with a li tag that has a *ngFor directive: <li *ngFor="let items of buttons"> <button (click)="newMap(items.id, $event)"> {{ items.name }} </button> </li> The buttons array looks like this: buttons = [ {nam ...

Typescript is requesting an index signature for a nested object that has been validated by Zod and is being received from an API request

This project uses Typescript 4.4.4, Next.js 11.1.2, and Zod 3.9.3. Typescript is throwing an error that says: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type <Review Type> ...

Storing redux dispatch action using the useRef hook in Typescript

Currently, I am attempting to store the redux action dispatch in a React reference using useRef. My goal is to be able to utilize it for aborting actions when a specific button is clicked. Unfortunately, I am facing challenges with assigning the correct ty ...

Using regular expressions in TypeScript to declare modules

Is there a more efficient method to declare multiple modules in TypeScript? An example of the code I am trying to simplify is: declare module '*.png'; declare module '*.jpg'; declare module '*.gif'; declare module '*.svg ...

Encountering build errors with Angular 2 version 2.0.0-beta.9

I recently updated my Angular2 project in visual studio from version 2.0.0-beta.0 to version 2.0.0-beta.9 and encountered build errors. The first error message reads as follows: Cannot find name 'SetConstructor'. This issue is occurring with ...

Retrieve data from a database using Angular

I am using Symfony as the Backend and Angular as the Frontend. I am attempting to showcase a list of all users from my postgresql database in a table. However, the browser only displays the information passed within the tags. ![display example](View post ...

Is there a way to utilize const assertions to retrieve the explicit types from objects nested at various levels?

In reference to this question, the previous structure had a depth of 2: const grandkids = { Karen: { Ava: ['Alice', 'Amelia'], Emma: ['Sarah'], }, Mary: { Sophia: ['Grace'], }, } as const; To ext ...

The width of mat-table columns remains static even with the presence of an Input field

I'm currently working on an Angular component that serves the dual purpose of displaying data and receiving data. To achieve this, I created a mat-table with input form fields and used {{element.value}} for regular data display. Each column in the tab ...

The triggering of Angular Change Detection does not occur when using nested ngFor loops

Currently, I'm deeply engrossed in a substantial Angular project that utilizes NgRx Store. One interesting feature of the app is an infinite scrolling list that displays skeleton items at the end, which are later replaced by real items once the reques ...

Edit the CSS styles within a webview

When loading the page in NativeScript using web viewing, I encountered a need to hide certain elements on the page. What is the best way to apply CSS styles to HTML elements in this scenario? Are there any alternatives that could be considered? I have been ...