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;
}
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With