Creating a completely dynamic button in Angular 6: A step-by-step guide

I have been working on creating dynamic components for my application, allowing them to be reusable in different parts of the program. At this point, I have successfully developed text inputs that can be dynamically added and customized using the componentFactory within a form, and they are functioning perfectly.

Now, I am facing the challenge of developing fully dynamic buttons that can also be customized when inserted into the targeted view (similar to the text inputs in the form). While I have managed to make most aspects generic and functional, I am encountering an issue with making the (click) function dynamic. I aim to specify the function to be triggered using the componentFactory, but I seem to be stuck at implementing this feature.

In my search for solutions, I have not come across any resources that address my specific problem. Below is the code for the component I have created 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 issue lies in the button.component.html file where the (click) directive does not work as expected. It triggers the following error:

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

While everything else is functioning correctly, the inability to make the button's click action entirely dynamic hinders its full customization. Unfortunately, I have not found a suitable resource to address this particular requirement.

EDIT: I have included the function responsible for 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;
}

Answer №1

To implement property binding and event binding, follow the example below.

app.component.html

<app-button description="Dynamic 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');
  }


}

Find the solution on stackblitz here

Answer №2

I successfully implemented the following example:

Child Component

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

  ngOnChanges() {
    if (this.callFunction) {
      this.callFunction();
    }
  }
}

Parent Template

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

This template uses property binding with [] to reference the function from the parent component.

Parent Component

export class AppComponent  {
  name = 'Angular';

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

You can view the working code on stackblitz here: https://stackblitz.com/edit/angular-function-input-property-deborahk

Upon running the code, you will see "called the function" printed in the console.

I haven't tested this with dynamically loaded components, so I cannot comment on how it would affect the functionality. However, the provided syntax works well for a standard component setup.

UPDATE

Defining an @Input property like this:

@Input() callFunction: Function;

Cannot be bound directly in HTML like this:

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

Instead, input properties should be set using property binding:

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

If event binding is required, an @Output property needs to be defined.

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

Compel a customer to invoke a particular function

Is there a way to ensure that the build method is always called by the client at the end of the command chain? const foo = new Foo(); foo.bar().a() // I need to guarantee that the `build` method is invoked. Check out the following code snippet: interface ...

Modify data in a table using Dialog Component in Angular Material

I need to implement a Material Dialog feature that allows users to update entries in a table by clicking on the "Change Status" button. Check out this functional snippet: https://stackblitz.com/edit/angular-alu8pa I have successfully retrieved data fr ...

The toggle button appears to be lacking in CSS styling

I'm trying to create a dropdown button using Bootstrap and ng-bootstrap in my Angular project. Here's the HTML I've written: <div ngbDropdown class="d-inline-block"> <button class="btn btn-outline-primary" id="dropdownBasic1" n ...

What is the process for injecting a template into the Kendo Upload's template?

Apologies if this question has already been addressed, but I am struggling to grasp the concept or find the answer. I am diving into templating in Angular (2/4) and seeking some assistance. I have developed a component that utilizes the kendo upload compo ...

Determine the overall sum of all items

Utilizing data fetched from my SQLite database, I'm utilizing *ngFor to display a comprehensive list of items with their respective names, prices, amounts, and totals. How can I calculate the grand total and display it at the bottom of the list? chec ...

The issue with Angular routing lies in the component content not displaying as expected

To showcase my project, I've created a demo on StackBlitz. I successfully implemented routing in my Angular application. You can find the code on StackBlitz. The sample consists of two components: AppComponent LoginComponent By default, when the ...

How can I iterate over a specific range in Angular (version 12 and higher) instead of looping through the entire array?

I need help with working with an array called bars stored in a foo property. In my HTML template, I am able to loop through the array like this: <div *ngFor="let bar of foo.bars"> <!-- do something with bar --> </div> Howeve ...

Modifying the array structure will deselect all individual <Input> elements that have been iterated

Hoping to create a system for adding/removing sub-items with buttons that increment/decrement slots in an array, where input fields are automatically added and removed: <div *ngFor="let item of itemsInNewOrder; let i = index"> <input [(ngModel) ...

Angular 7 ESRI loader search widget focus out bug: finding a solution

I am currently working on implementing an ESRI map using esri-loader in my Angular application. Everything seems to be working fine, but I am encountering an error when typing something into the search widget and then focusing out of it. "Uncaught Typ ...

Tips for navigating to a specific component within a single page

I'm facing an issue with scrolling to a specific component on the same page when a navbar item is clicked. Each component has a min-height of 100vh and there is a navbar at the top. I attempted using ViewPortScroller in the main component, but it does ...

The mock function will only be triggered if it is placed at the beginning of the file

In an attempt to simulate a React function component for the purpose of validating the properties passed to it, I encountered an interesting difference in behavior. When the mock is placed at the top of the file, everything works as expected: const mockTra ...

What is the best way to avoid curly braces in an Angular template?

If I need to show the following string, including the {{ }}, in an Angular 2+ template: Hello {{name}}, how are you?" Note: The entire string will be hardcoded, not from a variable. What is the optimal method to encode the curly braces so they are not ...

Modify the parent property's value within a derived Angular service

I'm utilizing Angular 9 and I have Service A along with Service B which extends Service A. Within Service A, there's a specific property that I aim to modify its value from within Service B. Surprisingly, the change only reflects in Service B and ...

Prevent selecting dates beyond the current date in Formly Datepicker within Angular

How can I disable future dates in Formly with Material datepicker in Angular? Despite searching online, I am unable to find a solution! export class AppComponent { form = new FormGroup({}); model: any = {}; options: FormlyFormOptions = {}; fields: ...

Has an official Typescript declaration file been created for fabric.js?

Currently, I have come across a Typescript definition for fabric.js on https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/fabric (https://www.npmjs.com/package/@types/fabric). However, its official status is unclear. Does anyone have more ...

NgFor is designed to bind only to Iterables like Arrays

After exploring other questions related to the same error, I realized that my approach for retrieving data is unique. I am trying to fetch data from an API and display it on the page using Angular. The http request will return an array of projects. Below ...

Getting started with html2canvas: A beginner's guide

So here's a seemingly simple question... I'm diving into new territory and stumbled upon http://html2canvas.hertzen.com with a straightforward tutorial. After successfully running the command npm install -g html2canvas, I hit a roadblock. Where e ...

Mastering the Art of Concise Writing: Tips to

Is there a way to write more concisely, maybe even in a single line? this.xxx = smt.filter(item => item.Id === this.smtStatus.ONE); this.yyy = smt.filter(item => item.Id === this.smtStatus.TWO); this.zzz = smt.filter(item => item.Id == ...

Issue with sending headers in HttpClient.post method in Angular 8

I have successfully implemented the following code: this.http.post (TGT_IP,body, {responseType: 'arraybuffer'}).subscribe( (val) => { console.log("POST call successful value returned in body", val); ...

The module "ng-particles" does not have a Container component available for export

I have integrated ng-particles into my Angular project by installing it with npm i ng-particles and adding app.ts import { Container, Main } from 'ng-particles'; export class AppComponent{ id = "tsparticles"; /* Using a remote ...