Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a fully dynamic button in angular 6?

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;
  }
like image 782
Muhammad Hamza Avatar asked Feb 25 '19 06:02

Muhammad Hamza


2 Answers

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

like image 117
TheParam Avatar answered Nov 14 '22 01:11

TheParam


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.

like image 31
DeborahK Avatar answered Nov 14 '22 02:11

DeborahK