8

I am creating dynamic components for my application, which can be re-used in various parts of the program. So far I have been able to create text inputs that are dynamically added and customized using the componentFactory into a form and they work perfectly.

The next part is creating the fully dynamic buttons which can be customized when placing in the targeted view (just like the text inputs with the form). I have tried to make most of the things generic and they work ok, but the problem I seem to be having is making the (click) function dynamic. I want to add the function needing to be triggered using the componentFactory as well, but for some reason I am not able to do so.

I can't find any resource that would give me details of this specific problem I am having. Here is the component I have made so far:

button.component.ts

import { Component, OnInit, ViewEncapsulation, Input } from '@angular/core';
import { FormGroup } from '@angular/forms';

@Component({
  selector: 'app-button',
  templateUrl: './button.component.html',
  styleUrls: ['./button.component.scss'],
  encapsulation: ViewEncapsulation.None
})
export class ButtonComponent implements OnInit {

  @Input() group: FormGroup;
  @Input() type: string;
  @Input() description: string;
  @Input() class: string;
  @Input() data: string;
  @Input() callFunction: string;


  constructor() { }

  ngOnInit() {
  }

}

button.component.html

<div [formGroup]="group">
  <button type="{{ type }}" class="{{ class }}" (click)="{{callFunction}}">{{ description }}</button>
</div>

The (click) is not working in the button.component.html, it gives me the following error:

Parser Error: Got interpolation ({{}}) where expression was expected

everything else works, but I can't make the button fully dynamic unless this is catered for, and I can't find the resource that would fulfill my requirements.

EDIT I have added the function using which I am importing the component into my view:

  buildLoginButton(){
    let data = {
      type: "button",
      class: "btn btn-primary px-4",
      description: this.translate.transform("pages[login_page][login_form][buttons][login]"),
      callFunction: "login()", //I have tried this.login() as well
      group: this.userForm
      }
    const inputFactory = this.resolver.resolveComponentFactory(ButtonComponent);
    const loginButton = this.login_button.createComponent(inputFactory);
    loginButton.instance.group = data.group;
    loginButton.instance.type = data.type;
    loginButton.instance.class = data.class;
    loginButton.instance.description = data.description;
    loginButton.instance.callFunction = data.callFunction;
  }
7
  • have you tried without {{}} interpolation binding Commented Feb 25, 2019 at 6:09
  • Hi @TheParam, I'm not sure what you mean, could you please elaborate? Commented Feb 25, 2019 at 6:11
  • Try defining callFunction in your @Input property as an actual function instead of a string name of a function. Commented Feb 25, 2019 at 6:32
  • Hi @DeborahK, I have changed the property as you requested and used this.login() in my import function, but when I run the server it showed an error on load (which should be produced when I hit login), and the login button itself is unresponsive (nothing in the console as well when I click it). Here is a screenshot: pasteboard.co/I2L2EWv.png Commented Feb 25, 2019 at 6:46
  • Hi Updated answer and solution with fully working example please check stackblitz.com/edit/… Commented Feb 25, 2019 at 7:06

2 Answers 2

4

Here you need to property binding and event binding like below.

app.component.html

<app-button description="Dyanmic Button" 
  class="button" (callFunction)="onButtonClicked($event)" >
</app-button>

app.component.ts

export class AppComponent  {
  name = 'Angular';

  onButtonClicked(event) {
   console.log(event); // handle button clicked here.
  }
}

button.component.html

<div [formGroup]="group">
  <button [type]="type" [class]="class" (click)="onClick($event)">
   {{ description }}
  </button>
</div>

button.component.ts

import { Component, OnInit, ViewEncapsulation, Input, Output, EventEmitter } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';

@Component({
  selector: 'app-button',
  templateUrl: './button.component.html',
  styleUrls: ['./button.component.css'],
  encapsulation: ViewEncapsulation.None

})
export class ButtonComponent implements OnInit {

  @Input() group: FormGroup;
  @Input() type: string;
  @Input() description: string;
  @Input() class: string;
  @Input() data: string;
  @Output() callFunction = new EventEmitter();



  constructor() { }

  ngOnInit() {

    this.group = new FormGroup({
      firstName: new FormControl()
    });
  }


  onClick(event) {
    this.callFunction.emit('I am button');
  }


}

Here is solution on stackblitz

Sign up to request clarification or add additional context in comments.

1 Comment

Hi, thanks for the solution. The rest of the properties were being interpolated fine, the only problem I was having was with the (click) attribute. I tried removing the interpolation as you asked with the (click) but now the property is not displayed altogether when i inspect in the console. The same is happening in your example as well when I inspect the button.
3

I was able to make this example work:

Child Component

export class HelloComponent implements OnChanges {
  @Input() name: string;
  @Input() callFunction: Function;  // NOTE: The data type here

  // Just some code to call the function
  ngOnChanges() {
    // Ensure the @Input property has been set before trying to call it.
    if (this.callFunction) {
      this.callFunction();
    }
  }
}

Parent Template

<hello 
  name="{{ name }}"
  [callFunction]="myCallBackFunction"></hello>

Notice that is it using property binding with [] and referencing the function in the parent's component code.

Parent Component

export class AppComponent  {
  name = 'Angular';

  myCallBackFunction() {
    console.log('called the function')
  }
}

I have a working stackblitz here: https://stackblitz.com/edit/angular-function-input-property-deborahk

When you run the code you will see "called the function" in the console.

I have not used dynamically loaded components, so not sure how that impacts how this works. But wanted to provide the basic syntax that does work in a "normal" component.

UPDATE

Setting an @Input property like this:

@Input() callFunction: Function;

Cannot be bound like this:

<button type="{{ type }}" class="{{ class }}" (click)="{{callFunction}}">

Input properties are set using property binding:

<button type="{{ type }}" class="{{ class }}" [callFunction]="someFunction">

If you want to use event binding, you'll need to define an @Output property.

2 Comments

Hi, Thanks for the update, could you show how I would use the @Output property in my case (taking into account how I am importing the content in the edit section) ? Would greatly appreciate it.
Trying to call a function by its string name is beyond my experience. You could check out this: stackoverflow.com/questions/359788/… And see if anything in there helps.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.