7

I am trying to figure out how I can add events to an element after it has been added to the DOM.

Right now, I have something like this:

import {Component} from 'angular2/core';

@Component({
    selector   : 'sub-child',
    template   : '<form [innerHTML]="html"></form>'
})

export class SubChildClass
{
    private html:string;
    private buttonText:string;

    constructor()
    {
        this.buttonText = 'A new button';
        this.create();
    }

    private create()
    {
        this.html = "<button (click)='new()'>" + this.buttonText + "</button>"
    }

    private new()
    {
        this.buttonText = "Text Changed";
    }
}

The part that doesn't work is this:

this.html = "<button (click)='new()'>" + this.buttonText + "</button>"

Angular 2 doesn't know what to do with (click)='new()' at this point. I am not really sure what is the right way to do this.

What I would expect is to be able to add HTML to the DOM at a later point with some corresponding events.

I haven't worked with Angular 1. But it sounds like this used to be the equivalent of $compile in Angular 1. Some other post recommend using Dynamic Content Loader for stuff like this. But that doesn't seem like the right answer for this use case. So any help or guidance is appreciated. Thanks,

Alexander Abakumov
  • 13,617
  • 16
  • 88
  • 129
khollenbeck
  • 16,028
  • 18
  • 66
  • 101
  • 2
    why are you not adding the button directly in the template? The idea of angular 2 is that you try to avoid as much as you can to create dom objects yourself and let angular do it for you. You add the component you want directly to the template and if you want to show or appear dinamically, you use something like `*ngIf="flag"` to tell him when to render and when not to, for example. – Langley Feb 04 '16 at 19:44
  • Like I mentioned, the idea in Angular is that those dynamic elements are defined in the template and by using structural attributes like `ngIf`, `ngFor`, `ngClass`, you can alter their appearance or behavior. So your logic changes the model, and the view reacts to those changes. If you show your real case it might be easier to tell you what would be the "angular way" to do it. If you really want to create them yourself take a look at this: https://angular.io/docs/ts/latest/api/core/ElementRef-class.html and make sure you read the warning, not like you can miss it. – Langley Feb 04 '16 at 19:50
  • "What I would expect is to be able to add HTML to the DOM at a later point with some corresponding events" Yes it is possible, however the code in your example is not HTML, its Angular Template syntax https://angular.io/docs/ts/latest/guide/template-syntax.html . – Langley Feb 04 '16 at 19:59
  • Angular wasn't made to be used this way so even though it's possible it won't be as easy as doing it the right way. You might have to control the events yourself with: https://angular.io/docs/ts/latest/api/core/EventEmitter-class.html can't say the real answer without the real case, in your posted example, the solution is to add the code to the template. – Langley Feb 04 '16 at 19:59
  • I have to strongly agree with @Langley on this one. This is really **not** a good way to be going about this problem, *sigh*... I have posted an example of how it is *possible* but as you can see, you lose *a lot* when you drop down to this level. In reality you'd be better off just writing the site or the form in jQuery inside of your angular2 component and controlling it the jQuery way. Please take some time to consider the alternatives or other ways of thinking about this issue and you'll probably come to a better solution using nested components or other ways. – SnareChops Feb 04 '16 at 20:15
  • I'd be happy to hop into a chat room to discuss your use case more in depth if you would be willing to explore other options. – SnareChops Feb 04 '16 at 20:16

2 Answers2

9

Updated Suggestion

import {Component} from '@angular/core';

@Component({
  selector: 'special-button-component',
  template: '<button type="button" (click)="changeText()">{{buttonText}}</button>'
})
export class SpecialButtonComponent{
  public buttonText: string = 'A New Button';
  public changeText(): void{
    this.buttonText = 'Text Changed';
  }
}

@Component({
  selector   : 'sub-child',
  template   : '<form><special-button-component></special-button-component></form>'
})
export class SubChildClass{}

Original Answer (Does not work anymore due to the removal of BrowserDomAdapter from Angular 2)

If you really want to bind to a non-template DOM element that has been injected then you can set up an event listener the good 'ol fashioned way using some rather undocumented features of Angular2

import {Component, ElementRef} from 'angular2/core';
import {BrowserDomAdapter} from 'angular2/platform/browser';

@Component({
  selector   : 'sub-child',
  template   : '<form [innerHTML]="html"></form>'
})
export class SubChildClass
{
  private html:string;
  private buttonText:string;

  constructor(private elementRef: ElementRef, private domAdapter: BrowserDomAdapter)
  {
    this.buttonText = 'A new button';
    this.create();
  }

  private create()
  {
    let button = this.domAdapter.createElement('button');
    button.innerHtml = this.buttonText;
    this.domAdapter.on(button, 'click', this.new.bind(this));
    this.domAdapter.appendChild(this.elementRef.nativeElement, button);
  }

  private new()
  {
    let button = this.domAdapter.querySelector(this.elementRef.nativeElement, 'button');
    button.innerHTML = "Text Changed";
  }
}

But what's the point... why use angular in the first place if we just drop down to this level. I also am using Angular2 in an Enterprise Application in production. And my choice for this functionality would be to create the button itself as a Component, and then use the DynamicComponentLoader to inject the element in.

SnareChops
  • 13,175
  • 9
  • 69
  • 91
  • thanks for the response.. Creating the button as a component is an interesting idea. So assuming I create a form consisting of textareas, inputs, buttons, links, ect in any given order. You think the best route would be to treat each of those as a component and inject them into my app as needed? – khollenbeck Feb 04 '16 at 20:17
  • For a general answer, probably yes, though it could depend on your use case. – SnareChops Feb 04 '16 at 20:21
  • I have same problem with 'routerLink'. Please see my question : http://stackoverflow.com/q/37421007/1579102 – MortezaDalil May 25 '16 at 07:26
  • @SnareChops do you have any idea where BrowserDomAdapter has been moved to in Angular 2.0.0-rc.4? – Katana24 Aug 15 '16 at 09:47
  • @Katana24 My understanding is that it has been removed in favor of using `Renderer` – SnareChops Aug 15 '16 at 21:48
  • @Eusthace due to the removal of BrowserDomAdapter in the angular library, your only viable option to do something like this would be create a Component for the DOM you would like to inject, then use the ComponentFactoryResolver and ViewContainerRef to inject the component into your Component. – SnareChops Dec 04 '16 at 23:04
2

I use ngFor to repeat DOM. When you press click button, add item to array. Example: + Component:

export class AbcComponent {
  billingInforArr: RegisterBillingInfoModel[] = [];

  months: any[] = [];
  years: any[] = [];

  constructor(private fb: FormBuilder, private router: Router) {
    for (let i = 1; i <= 12; i++) {
      this.months.push({ value: i, text: i });
    }

    let today = new Date();
    let year = today.getFullYear();
    for (let i = 1; i <= 10; i++) {
      this.years.push({ value: year + i, text: year + i });
    }

    if (localStorage.getItem('listCard') != null) {
      this.billingInforArr = JSON.parse(localStorage.getItem('listCard'));
    }
  }
  addCard(value: any) {
    this.billingInforArr.push({ Id: Date.now().toString(), NameOnCard: value.name, CardNumber: value.cardNumber, ExpMonth: value.expMonth, ExpYear: value.expYear, Expire: `${value.expMonth}/${value.expYear}` });
    localStorage.setItem('listCard', JSON.stringify(this.billingInforArr));
  }
  removeCard(value: any) {
    console.log(value);
    console.log(this.billingInforArr);

  }
}
  • HTML:

<div class="form-group list-credit" *ngFor="let item of billingInforArr">
  <div class="content-credit">
    <i class="material-icons remove" (click)="removeCard(item.Id)">cancel</i>
  </div>
</div>
Truc
  • 386
  • 4
  • 12