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

Unable to establish a simulated environment and trial period for experimentation

I encountered the following errors during the test: TypeError: Cannot read properties of undefined (reading 'subscribe') Error: <toHaveBeenCalled> : Expected a spy, but got Function. I am having trouble understanding these errors. Here i ...

Angular 2 - The creation of cyclic dependencies is not allowed

Utilizing a custom XHRBackend class to globally capture 401 errors, I have encountered a dependency chain issue in my code. The hierarchy is as follows: Http -> customXHRBackend -> AuthService -> Http. How can this problem be resolved? export class Custom ...

Using the "Range" Input Type in Angular's Reactive Forms

I'm having difficulties with styling the background of my slider/range in reactive forms within Angular. The input value updates correctly, but the issue arises when I try to type in the input field - the range changes, but the color (green) remains s ...

Tips on updating icons automatically

Incorporating Ionic Angular, Font Awesome, and Stripe into my app, I encounter a challenge. I aim to dynamically change the icon on the HTML page based on data received from the server in the .ts page. This is the current setup: <ion-item *ngFor="let ...

Can Next.js accommodate server-side redirection with internationalization?

I'm working on a small Next.js app that has pages accessible only to logged in users. To manage the authenticated routes, I created an HOC (withAuth) that handles redirection on the server side to prevent page flashing on the client side. Everything i ...

Encountering an Unexpected Index Error with ngFor in Angular 4/5

I am struggling to create a list of inputs and I can't seem to get ngFor to work properly. <div *ngFor="let q of questions; let i = index" class="col-3"> <div class="group"> <input [(ngModel)]="q" [class.ng-not-empty]="q.length & ...

Data that changes dynamically on a chart

When making a rest call to fetch data, I aim to populate the pieChartData with the obtained information. However, I am facing difficulties in achieving this task. Can someone guide me on how to accomplish this? import { Component, OnInit} from '@angu ...

When working with Angular and NGRX, I often wonder if opening my app in multiple tabs within the same browser will result in all tabs pointing to a single NGRX instance

Is it possible to access my localhost:9000 in multiple tabs? If I open it in 3 tabs, will all those 3 tabs point to a single NGRX instance or will each tab of localhost have its dedicated NGRX instance? I haven't had the chance to test this yet. ...

Is there a way to retrieve the Angular-Redux store in a child module?

Within my Angular application, I utilize angular-redux for managing the application state. In my main module, I have defined the redux store in the following manner: export class MainModule { constructor(private ngRedux: NgRedux<MainAppState>, ...

Error message received when making an API call in React Native for Android and saving the response to a local database: 'Error: Network

Despite using axios and promises to make a call to a local database API, I am facing difficulties reaching the endpoint as I constantly receive a 'Error: Network Error' feedback in the console without any further explanation on the root cause of ...

Data fetched by Next.js is failing to display on the web page

After writing a fetch command, I was able to see the data in the console.log but for some reason it is not showing up in the DOM. export default async function links(){ const res = await fetch('https://randomuser.me/api/'); const data = ...

Incorporating ng2-bootstrap into an Angular2 project with SystemJS starter template

I am attempting to integrate the ng2-bootstrap modal functionality into a component of my project that is built using the angular2-starter. Below is my systemjs.conf.js configuration: /* * This config is only used during development and build phase onl ...

Creating a unique custom selector with TypeScript that supports both Nodelist and Element types

I am looking to create a custom find selector instead of relying on standard javascript querySelector tags. To achieve this, I have extended the Element type with my own function called addClass(). However, I encountered an issue where querySelectorAll ret ...

Using RouterModule.forRoot() in Angular 2 CLI compiler/ngc command: A step-by-step guide

Assume I have the appRoutingModule shown below: export const routes: Route[] = [ { path: '', component: ApplicationlistComponent } ]; @NgModule( { imports: [ ApplicationsModule, RouterModule.forRoot( routes ) ], exports: [ Route ...

A script object must only permit one property at a time

I am unfamiliar with TypeScript and I have an object named obj with 3 properties: a, b, c. However, it is important to note that b and c cannot exist together in the same object. So, my object will either be: obj = { a: 'xxx', b: 'x ...

Differences between Angular TS Lint onInit and ngOnInit

My TS Lint issue warned me to implement the OnInit interface and included a link to this page: https://angular.io/docs/ts/latest/guide/style-guide.html#!#09-01 I'm curious, what sets apart `onInit` from `ngOnInit`? Both seem to work just fine for me. ...

Is it possible in Angular to directly bind the emitted output of a component to a property?

My primary application component communicates with its sub components using @Output decorated properties on the subcomponent. The output properties utilize an EventEmitter<>(). Typically, these properties emit a simple boolean or number. I want to di ...

What are the steps to fix errors when deploying a MEAN stack application on Heroku?

Upon building my Angular app with a Node.js API and deploying it on Heroku, I encountered an error. Despite this, the app runs smoothly with no errors when tested on my local server. Below are the logs from Heroku: C:\Users\PC>heroku logs -a= ...

gulp-angular2 task is malfunctioning

Currently, I am in the process of working on a gulpfile and have written the following task: var tsProject = ts.createProject('app/Resources/public/angular/tsconfig.json'); gulp.task('angular-2', function () { var tsResul ...

Angular `build` is encountering an error when dealing with externals that are declared using `webpack`

When working with Angular, I successfully load jquery from an external source. However, after the build process, I encounter a troubling error message: Uncaught ReferenceError: jQuery is not defined. It's worth noting that my build does download the j ...