The @HostListener in Angular2 does not function correctly within a component that is inherited from another component

My Angular2-based client-side application has a base class:

abstract class BaseClass {
    @HostListener('window:beforeunload') beforeUnloadHandler() {
        console.log('bla');
    }
}

and two similar derived classes:

@Component({
    selector:  'derived-one',
    templateUrl:  './templates/app/+derived-one/derived-one.component.html'
})
export class DerivedOne extends BaseClass {
}

@Component({
    selector:  'derived-two',
    templateUrl:  './templates/app/+derived-two/derived-two.component.html'
})
export class DerivedTwo extends BaseClass {
}

The issue is that beforeUnloadHandler works fine in DerivedOne but not at all in DerivedTwo.

Although it may be difficult to pinpoint the exact cause based on the provided information, any suspicions about what could be causing this unusual behavior would be appreciated.

Some additional notes:

If I use the following :

abstract class BaseClass
    constructor(){
        window.onbeforeunload = function(){
            console.log('bla');
        }
    }
}

everything works fine, but I prefer an Angular2-based solution;

If I write

abstract class BaseClass {
    beforeUnloadHandler() {
        console.log('bla');
    }
}

and in derived-two.component.html

<div (window.beforeunload)="beforeUnloadHandler()"></div>

everything works fine too, but it seems like a workaround;

Again, if I write

abstract class BaseClass {
    beforeUnloadHandler() {
        console.log('bla');
    }
}

and

@Component({
    selector:  'derived-two',
    host: {'window:beforeunload': 'beforeUnloadHandler' }
    templateUrl:  './templates/app/+derived-two/derived-two.component.html'
})
export class DerivedTwo extends BaseClass {
}

it won’t work.

Finally, if I use @HostListener in both DerivedTwo and DerivedOne, it works, but I want to avoid duplicating code.

Hopefully, this information provides enough context to work with (or at least generate some hypotheses).

Answer №1

Welcome to Update 2.3.0

Get ready for the new feature of object inheritance for components.

To delve deeper, check out this commit here

Previous Version Overview

1) Consider a scenario where you have a class:

abstract class BaseClass {
  @HostListener('window:beforeunload') beforeUnloadHander() {
    console.log('bla');
  }
}

This will work as expected

Check out this Plunker Example (add whitespace in editor and observe console)

Although, keep in mind that Angular2 has limitations with full inheritance - Discover more here about an issue with binding and @ViewChild

It's intriguing why the @HostListener solution didn't initially work

In particular, if you add a property decorator to your derived component, it won't function properly. For example, consider the code snippet below:

abstract class BaseClass {
  @HostListener('window:beforeunload') beforeUnloadHander() {
    console.log(`bla-bla from${this.constructor.name}`);
  } 
} 

@Component({
    selector:  'derived-one',
    template:  '<h2>derived-one</h2>'
})
export class DerivedOne extends BaseClass {
   @Input() test;
}

View this Plunker demo

The resulting JavaScript transformation will be like:

[JavaScript code snippet transformed]

We're specifically interested in these lines:

 __decorate([
    core_1.HostListener('window:beforeunload'), 
      __metadata('design:type', Function), 
      __metadata('design:paramtypes', []), 
      __metadata('design:returntype', void 0)
 ], BaseClass.prototype, "beforeUnloadHander", null);

 ... 
 __decorate([
   core_1.Input(), 
   __metadata('design:type', Object)
 ], DerivedOne.prototype, "test", void 0);

HostListener and Input are property decorators (propMetadata key). This way defines two metadata entries - on BaseClass and on DerivedOne https://i.stack.imgur.com/HZ87R.png https://i.stack.imgur.com/4q9od.png

When Angular2 extracts metadata from DerivedOne class, it will only utilize its own metadata:

https://i.stack.imgur.com/Pb4VK.png

To access all metadata, consider creating a custom decorator such as:

[Custom Decorator code snippet]

Here's a functional demo link

2) If you follow this approach:

abstract class BaseClass
  constructor(){
    window.onbeforeunload = function(){
      console.log('bla');
    };
  }
}

It will only be invoked once as you're overriding the window.onbeforeunload handler every time. Instead, use the following implementation:

abstract class BaseClass {
 constructor(){
    window.addEventListener('beforeunload', () =>{
      console.log(`bla-bla from${this.constructor.name}`);
    })
  }
}  

Plunker Example regarding this

3) Lastly, if your base class appears as demonstrated below:

abstract class BaseClass {
  beforeUnloadHander() {
     console.log(`bla-bla from${this.constructor.name}`);
  }
}

Ensure you use the correct syntax (you're missing brackets) in the decorator property:

host: {'(window:beforeunload)': 'beforeUnloadHander()' }

See this Plunker Example

We hope this information is beneficial for you!

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

Trigger a change event for a Material Checkbox by referencing its unique identifier

<div *ngFor="let cus of deselectedList | keyvalue" (click)="clickCheckBox('customer_'+cus.key+'_checkbox')"> {{cus.key}} <mat-checkbox id="customer_{{cus.key}}_checkbox" (change ...

Having trouble installing sub npm dependencies for a private npm module

I've created an npm private repository using Sinopia and published an app that is a complete end-to-end application built with Angular2 on the UI side. The package.json file contains dependencies such as @angular/core and animations. However, when I ...

Guide to slicing strings specifically with numerical characters at the end

I've encountered a challenge. I need to slice the last two characters in a string, but only for strings that contain numbers. I attempted using "nome": element.nome.slice(0,-2) and now I require some sort of validation. However, figuring out how to do ...

Is there an npm module for filtering cities by country?

Looking to implement city or state selection based on country in my Angular project. Is there a suitable npm package or API that can help achieve this functionality? ...

Utilizing absolute path in Typescript: A comprehensive guide

I am currently working on a project written in Typescript running on NodeJS. Within the project, I have been using relative paths to import modules, but as the project grows, this approach is becoming messy. Therefore, I am looking to convert these relativ ...

What is the best way to test for errors thrown by async functions using chai or chai-as-promised?

Below is the function in question: async foo() : Promise<Object> { if(...) throw new Error } I'm wondering how I should go about testing whether the error is thrown. This is my current approach: it("checking for thrown error", asy ...

How can I reduce the delay in Angular 5 to a minimum?

The following code represents a simple request function: getItem() { return this.http .post<ItemModel>(`${this.apiUrl}/${methodUrl}`, {}) .subscribe(response => { ... }); } Depending on the duration of the ...

Seeking ways to ensure my component maximizes available space using Tailwind CSS

I am currently utilizing Tailwind CSS to style a Angular application. The setup is fairly straightforward: app.component.html is responsible for displaying a navigation bar and footer, while the components are shown in the remaining space with an overflow- ...

By default, the ejs-datetimepicker in @syncfusion/ej2-angular-calendars will have an empty input field

I incorporated a datetime picker from @syncfusion/ej2-angular-calendars into my project. However, I noticed that the datetime picker displays the current date and time by default in its input field. Instead, I would like the input field to be empty when ...

Tips for updating the color of checkboxes in an Angular application

I'm looking to update the appearance of a checkbox when it is disabled. app.component.html: <input type="checkbox" class="custom-control-input" [disabled]="item.disabled" [checked]="item.checked"> The cu ...

Tips on ensuring that only one Angular Material expansion panel expands at a time

I have designed a mat expansion panel and I would like to ensure that only one panel can be expanded at a time. In other words, I want it so that when one record is expanded and I click on another record of the mat expansion, the previously expanded reco ...

Show image using Typescript model in Angular application

In my Angular (v8) project, I have a profile page where I typically display the user's photo using the following method: <img class="profile-user-img" src="./DemoController/GetPhoto?Id={{rec.Id}}" /> However, I am considering an alternative ap ...

Angular2: Implementing a nested subscription with a secondary subscription within a loop

I am still discovering the world of Angular2, just a week into it! Currently dealing with 2 API calls in my project. The initial API call fetches an array (queryResults) of JSON objects. (1st Subscribe) Showcasing that array in the view. Iterating throu ...

Creating eight equal sections within HTML <div> elements using Bootstrap

I am brand new to Angular. I need to design something like this: https://i.sstatic.net/Zcb9i.png However, I'm struggling to find the right solution. Here is what I have so far: https://i.sstatic.net/7hsrk.png. Having been a backend developer, I&ap ...

Is there a method to categorize an array of objects by a specific key and generate a new array of objects based on the grouping in JavaScript?

Suppose I have an array structured like this. It contains objects with different values but the same date. [ { "date": "2020-12-31T18:30:00.000Z", "value": 450 }, { "date": "20 ...

employing constructor objects within classes

I am attempting to utilize a class with a constructor object inside another class. How should I properly invoke this class? For example, how can I use Class 1 within Class 2? Below is an instance where an object is being created from a response obtained f ...

The outcome of a promise is an undefined value

I am facing an issue where I need to access the result of my API call outside the promise, but the value I receive is always undefined. Within the OrderService : public async getOrderPrice(device: string) : Promise<any> { this.urlOrderPrice = th ...

I'm encountering an issue where the 'switchMap' property is not recognized on the 'Observable<User>' type

I encountered an issue while attempting to run code in git bash. The problem arises from 'switchMap' not being executed and displaying the error message: "error TS2339: Property 'switchMap' does not exist on type 'Observable'" ...

NGRX: Issue with http retry interceptor preventing failure action from triggering

Incorporating NGRX into my project, I am looking to implement simple GET requests to an API that are retried up to five times. The reason behind this is occasional throttling from Azure Cosmos-DB (free-tier). To achieve this, I have set up an http-interce ...

Encountering login difficulties during initial login attempts may result in automatic logout upon page refresh within Angular+(Node/express).js framework

After attempting to log in for the first time, I noticed that the authentication process in AuthService sets the loggedInStatus to true. However, in AuthGuard, the loggedIn status is set to false, preventing navigation to the dashboard. Additionally, the c ...