Reputation: 893
I have created a generic button component in my application that would execute a particular function from the parent component it is imported into.
In this specific example I have a login form which will call a login()
function upon clicking of that button.
The problem I seem to be having is that every time I click the login button it executes the method twice instead of once. I am not sure why this is happening. There is a forgot password button right next to it which has also been derived from the same template but should have different functionality since I have bound a different function to the click
directive but when I click that link it runs the login()
function again but this time once.
Here is a screenshot of my login form to get an idea of the layout:
Here is my code:
button.component.ts
import { Component, ViewEncapsulation, Input, Output, OnInit, EventEmitter } from '@angular/core';
import { FormGroup, FormControl } 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;
@Output() callFunction = new EventEmitter();
constructor(){ }
ngOnInit(){
this.group = new FormGroup({});
}
onClick(event){
this.callFunction.emit(event);
}
}
button.component.html
<div [formGroup]="group">
<button [type]="type" [class]="class" (click)="onClick($event)">{{ description }}</button>
</div>
login.component.ts
import { Component, OnInit, ViewChild, ViewContainerRef, AfterContentInit, ComponentFactoryResolver } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { OauthService } from 'src/app/services/oauth/oauth.service';
import { LoggerService } from 'src/app/services/logger/logger.service';
import { ToastrService } from 'ngx-toastr';
import { TranslatePipe } from 'src/app/pipes/translate/translate.pipe';
import { TextFieldComponent } from 'src/app/components/core/text-field/text-field.component';
import { ButtonComponent } from 'src/app/components/core/button/button.component';
/**
* This component is rendered at the start of application, it provides the UI
* & functionality for the login page.
*/
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
/**
* This class is used to build a login form along with initialization of validators
* as well as authenticate the user, and reroute upon success
*/
export class LoginComponent implements OnInit, AfterContentInit{
@ViewChild('username', { read: ViewContainerRef }) username;
@ViewChild('password', { read: ViewContainerRef }) password;
/**
* This property initializes the formGroup element.
*/
userForm: FormGroup;
/**
* The constructor initializes Router, FormBuilder, OauthService, LoggerService, ToastrService
* & TranslatePipe in the component.
*/
constructor(private router: Router,
private fb: FormBuilder,
private oauth: OauthService,
private logger: LoggerService,
private toastr: ToastrService,
private translate: TranslatePipe,
private resolver: ComponentFactoryResolver) { }
/**
* This is the hook called on the initialization of the component, it initializes
* the form.
*/
ngOnInit() {
this.buildForm();
}
/**
* This method initialized the the formGroup element. Its properties and the validators.
*
* @method buildForm
* @return
*/
buildForm() {
this.userForm = this.fb.group({
'username': ['', Validators.required],
'password': ['', Validators.required],
});
}
/**
* This method returns the values of the form controls.
*
* @return
*/
get form() { return this.userForm.controls; }
/**
* This method is triggered on success, it reroutes the user to main page.
*
* @return
*/
onSuccess() {
let result = this.translate.transform("pages[login_page][responses][success]");
this.logger.info(result);
this.toastr.success(result);
this.router.navigate(['main']);
}
/**
* This method is triggered when user clicks log-in, it calls the aunthenication method
* from oauth service.
*
* @return
*/
login() { console.log("i am called00");
this.oauth.authenticateUser(this.form.username.value, this.form.password.value, this.onSuccess.bind(this));
}
ngAfterContentInit() {
this.buildUserField();
this.buildPasswordField();
}
/**
* This function builds the username field, imports the TextFieldComponent
*
*/
buildUserField(){
let data = {
type: "text",
class: "form-control",
placeholder: this.translate.transform("pages[login_page][login_form][placeholders][username]"),
id: "username",
autoComplete: "username",
formControlName: "username",
group: this.userForm
}
const inputFactory = this.resolver.resolveComponentFactory(TextFieldComponent);
const usernameField = this.username.createComponent(inputFactory);
usernameField.instance.group = data.group;
usernameField.instance.type = data.type;
usernameField.instance.class = data.class;
usernameField.instance.placeholder = data.placeholder;
usernameField.instance.id = data.id;
usernameField.instance.autoComplete = data.autoComplete;
usernameField.instance.formControlName = data.formControlName;
}
/**
* This function builds the password field, imports the TextFieldComponent
*
*/
buildPasswordField(){
let data = {
type: "password",
class: "form-control",
placeholder: this.translate.transform("pages[login_page][login_form][placeholders][password]"),
id: "password",
autoComplete: "password",
formControlName: "password",
group: this.userForm
}
const inputFactory = this.resolver.resolveComponentFactory(TextFieldComponent);
const passwordField = this.password.createComponent(inputFactory);
passwordField.instance.group = data.group;
passwordField.instance.type = data.type;
passwordField.instance.class = data.class;
passwordField.instance.placeholder = data.placeholder;
passwordField.instance.id = data.id;
passwordField.instance.autoComplete = data.autoComplete;
passwordField.instance.formControlName = data.formControlName;
}
/**
* This function builds the login button functionality, imports the ButtonComponent
*
*/
buildLoginButton(){
this.login();
}
/**
* This function builds the forgot password link functionality, imports the ButtonComponent
*
*/
buildForgotPasswordButton(){
}
/**
* This function builds the sign up button functionality, imports the ButtonComponent
*
*/
buildSignUpButton(){
}
}
login.component.html
<div class="app-body">
<main class="main d-flex align-items-center">
<div class="container center">
<div class="row">
<div class="col-md-8 mx-auto">
<div class="card-group">
<div class="card p-4">
<div class="card-body">
<form [formGroup]="userForm" (submit)="login()">
<h1>{{ 'pages[login_page][login_form][labels][login]' | translate }}</h1>
<p class="text-muted">{{ 'pages[login_page][login_form][labels][sign_in]' | translate }}</p>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text"><i class="icon-user"></i></span>
</div>
<div #username> </div>
</div>
<div class="input-group mb-4">
<div class="input-group-prepend">
<span class="input-group-text"><i class="icon-lock"></i></span>
</div>
<div #password> </div>
</div>
<div class="row">
<div class="col-6">
<app-button description="{{ 'pages[login_page][login_form][buttons][login]' | translate }}" class="btn btn-primary px-4" (callFunction)="buildLoginButton()"></app-button>
<!-- <button type="button" class="btn btn-primary px-4" (click)="login()">{{ 'pages[login_page][login_form][buttons][login]' | translate }}</button> -->
</div>
<div class="col-6 text-right">
<app-button description="{{ 'pages[login_page][login_form][urls][forgot_password]' | translate }}" class="btn btn-link px-0" (callFunction)="buildForgotPasswordButton()"></app-button>
<!-- <button type="button" class="btn btn-link px-0">{{ 'pages[login_page][login_form][urls][forgot_password]' | translate }}</button>-->
</div>
</div>
</form>
</div>
</div>
<div class="card text-white bg-primary py-5 d-md-down-none" style="width:44%">
<div class="card-body text-center">
<div>
<h2>{{ 'pages[login_page][sign_up_panel][labels][sign_up]' | translate }}</h2>
<p>{{ 'pages[login_page][sign_up_panel][labels][new_account]' | translate }}</p>
<app-button description="{{ 'pages[login_page][sign_up_panel][buttons][register]' | translate }}" class="btn btn-primary active" (callFunction)="buildSignUpButton()"></app-button>
<!-- <button type="button" class="btn btn-primary active mt-3">{{ 'pages[login_page][sign_up_panel][buttons][register]' | translate }}</button> -->
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
SIDE NOTE: (Although this is not technically part of the question title) I am using CoreUI for styling the form, which is imported into my global styling file and worked fine when I was not using reusable components for text fields and buttons, but now when I have started using those the child components somehow are not inheriting the CoreUI styles, just plain bootstrap, if someone could pretty please help me with that I would really appreciate it :)
Upvotes: 0
Views: 4364
Reputation: 845
Your button have type "text
". That is not a valid value.
It should be "button|submit|reset
" (see doc for "type" attribute)
Here, it default to "submit", hence calling the submit callback of your form (login()
).
You should change the type of the button to button
.
Upvotes: 1
Reputation: 3451
in your form you have a submit callback for login()
<form [formGroup]="userForm" (submit)="login()">
and in your submit button which has a callback buildLoginButton
which is calling login()
is the reason why your issue occurred.
doing your form like this should resolve your problem
<form [formGroup]="userForm">
Upvotes: 3