i need create a form after send request to database and get reponse from server and fill form with that data .
i write this code for send request and fill form from that response :
data: any;
accountSettingFG: FormGroup;
constructor(private formBuilder: FormBuilder, private accountsettingService: AccountSettingService) {
}
ngOnInit(): void {
this.FetchData().then(res=>{
this.InitialForm();
});
}
InitialForm(): void {
this.accountSettingFG = this.formBuilder.group({
twoFactorAuthentication: [this.data.twoFactorAuthentication],
email: [this.data.email],
sms: [this.data.sms]
})
}
FetchData() {
let promiss = new Promise((resolve, reject) => {
this.accountsettingService.GetListItem('/SiteOption/AccountSecurity')
.toPromise()
.then(res => {
this.data= res.result['accountSecuritySettingsModel']
resolve();
}, msg => {
reject(msg);
})
})
return promiss;
}
and this is my Html Code :
<form
class="form"
id="postform"
[formGroup]="accountSettingFG"
(ngSubmit)="onSubmit()"
autocomplete="off"
>
<mat-slide-toggle color="primary" formControlName="twoFactorAuthentication">
{{ "SETTING.ACCOUNT_SCURITY.TOW_FACTOR_ACUTHENTICATION" | translate }}
</mat-slide-toggle>
<mat-slide-toggle color="primary" formControlName="email">
{{ "SETTING.ACCOUNT_SCURITY.EMAIL" | translate }}
</mat-slide-toggle>
<mat-slide-toggle color="primary" formControlName="sms">
{{ "SETTING.ACCOUNT_SCURITY.SMS" | translate }}
</mat-slide-toggle>
</form>
but when i run the project it show me this error :
ERROR Error: formGroup expects a FormGroup instance. Please pass one in.
Example:
<div [formGroup]="myGroup">
<input formControlName="firstName">
</div>
In your class:
this.myGroup = new FormGroup({
firstName: new FormControl()
});
at Function.missingFormException (forms.js:2283)
at FormGroupDirective._checkFormPresent (forms.js:7490)
at FormGroupDirective.ngOnChanges (forms.js:7280)
at checkAndUpdateDirectiveInline (core.js:33257)
at checkAndUpdateNodeInline (core.js:46077)
at checkAndUpdateNode (core.js:46016)
at debugCheckAndUpdateNode (core.js:47039)
at debugCheckDirectivesFn (core.js:46982)
at Object.updateDirectives (account-option.component.html:3)
at Object.debugUpdateDirectives [as updateDirectives] (core.js:46970)
whats the problem ? how can i solve this problem ?
I'd suggest initialising the form with empty values before sending the request. This should avoid the issue. Therefore, you need to update the form when your response is received.
I'm not familiar with Angular > 1. Might be that it's introducing other issues. Anyway, I hope it helps.
data: any;
accountSettingFG: FormGroup;
constructor(private formBuilder: FormBuilder, private accountsettingService: AccountSettingService) {
}
ngOnInit(): void {
this.accountSettingFG = this.formBuilder.group({
twoFactorAuthentication: [''],
email: [''],
sms: ['']
});
this.FetchData().then(res => {
this.InitialForm();
});
}
InitialForm(): void {
this.accountSettingFG.setValue(this.data);
}
FetchData() {
return this.accountsettingService.GetListItem('/SiteOption/AccountSecurity')
.toPromise()
.then(res => {
return res.result['accountSecuritySettingsModel'];
})
}
Related
I am working with Angular reactive forms and async validation and instead of a normal value I want to send Json file for validation.
My form Looks like
createGoogleCloudForm = new FormGroup(
{
name: new FormControl('', [Validators.required, Validators.pattern(RegexUtil.cloudName)]),
organizationId: new FormControl(''),
config: new FormControl(),
//zones: new FormControl({ value: '', disabled: true }, Validators.required),
},
undefined,
//[GoogleCloudCredentialsValidator.checkGoogleCloudCredentials(this.cloudCredentialsCheckerService)]);
My HTML file looks like
<form class="form" [formGroup]="createGoogleCloudForm" (ngSubmit)="createGoogleCloudCredential()">
<div class="image-upload">
<label for="background-upload" class="button button--outline display--inline-flex">Choose Config File</label>
<span class="file-name">
{{ googleCloudFormData?.get('config')?.name | truncate: 40:'...'
}}
</span>
<input
type="file"
id="background-upload"
class="hidden-input"
formControlName="config"
(change)="fileChange($event.target.files, 'config')"
/>
</div>
</div>
<div class="display--flex justify--content--end">
<button class="button button--primary display--inline-flex" type="submit" [disabled]="!createGoogleCloudForm.valid">
<svg-icon key="circle-plus"></svg-icon> Add Cloud Credentials
</button>
</div>
</form>
The file change method is like
fileChange(files: File[], controlName: string) {
console.log(files);
if (files && files.length > 0) {
this.googleCloudFormData.append(controlName, files[0]);
console.log('adding');
this.createGoogleCloudForm.setAsyncValidators([
GoogleCloudCredentialsValidator.checkGoogleCloudCredentials(this.cloudCredentialsCheckerService),
]);
}
}
The Async Validator is
static checkGoogleCloudCredentials(cloudCredentialsCheckerService: CloudCredentialsCheckerService): AsyncValidatorFn {
return (control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> =>
// half a second delay to prevent BE requesting after each user's key stroke
timer(500).pipe(
switchMap(() => cloudCredentialsCheckerService.checkGoogleCloud(control.get('config').value)),
map(() => null),
catchError(() => of({ invalidGoogleCloudCredentials: true })),
);
}
and the service
checkGoogleCloud(file: File[]) {
return this.http.post(`${this.baseUrl}/google`, { file });
}
The problem is when I upload the file the async validator does not get fired, I want to send the file with the request.
Any ideas?
// register-page.ts
this.registerationForm = new FormGroup(
{
username: new FormControl(null,
[
Validators.required,
Validators.minLength(3),
Validators.maxLength(30),
Validators.pattern('^[a-zA-Z-0123456789]*$'),
]
),
// accountService.ts
validateUsername(username: string): Observable<any> {
return this.httpManager.post(authServer + "username-validator", new ValidateUsernameRequest(username)).pipe(
map(
(response: Response) => {
return response.data;
}
)
);
}
// register-page.html
<ion-item [ngClass]="username==null ? 'at-beginning':''">
<ion-label position="floating">Kullanıcı Adı</ion-label>
<ion-input name="username" formControlName="username" inputmode="text" class="ion-text-lowercase"
placeholder="Kullanıcı Adı" (onchange)=(checkUsername($event)) (keypress)=(onKeyUp($event)) (keydown.space)="$event.preventDefault()">
</ion-input>
</ion-item>
<div class="err" *ngIf="formControls.username.errors">
<div *ngIf="formControls.username.errors.required">Kullanıcı adı zorunlu.</div>
<div *ngIf="formControls.username.errors.minlength">Kullanıcı adınız çok kısa.</div>
<div *ngIf="formControls.username.errors.maxlength">Kullanıcı adınız çok uzun.</div>
<div *ngIf="formControls.username.errors.pattern">Kullanıcı adınız özel karakter içeremez.</div>
<div *ngIf="formControls.username.errors.checkUsername">Kullanıcı adınız alınmış.</div>
</div>
I've tried to code a validator for the username that checks its availability whenever the user make changes on the input. I've fight for it two days but I'm just stuck. I understood that I need to use an async function that subscribes the data came from accountService.validateUseranem(control.value) but somehow I failed to make it work.
Can someone help me on this?
I did the following and fixed my own problem.
Created username-validator.directive.ts
import { AbstractControl, AsyncValidatorFn, ValidationErrors } from '#angular/forms';
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { AccountService } from 'src/app/services/yon/auth/account.service';
export function existingUsernameValidator(userService: AccountService): AsyncValidatorFn {
return (control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> => {
return userService.validateUsername(control.value).pipe(map(
(data) => {
console.log("The result is : " + data)
return (!data) ? { "usernameExists": true } : null;
}
));
};
}
Then used it inside of the validators of username
this.registerationForm = new FormGroup(
{
username: new FormControl(null, {
validators: [
Validators.required,
Validators.minLength(3),
Validators.maxLength(20),
],
asyncValidators: [existingUsernameValidator(this.accountService)],
updateOn: 'change'
}),
email: new FormControl(null, {
validators: [
Validators.required,
Validators.email],
asyncValidators: [existingEmailValidator(this.accountService)],
updateOn: 'change'
}),
like this
Also;
updateOn: 'change'
Understands if the input changes on that control and whenever the input changes, it checks the value for validation.
And becasue that I need to send request to API and use that value returns as a response , my validator needed to be an asynchronous validator. Also the sync and aysnc validators need to be splitted as far as I understand, like I did.
I am developing an online store for a client using Angular/Spring Boot. To keep things simple and to the point, I am storing the users shopping cart data in localStorage which is working fine. However, for the shopping cart to be updated with the users products, I have to refresh the page. I would like the app to just update without having to refresh the page. Im sure this is probably very trivial, but I cant get it to work!
I have tried looking at a few different sources :
Refreshing Page with Angular
Angular Source
I have tried using the this.router.routeReuseStrategy.shouldReuseRoute = function () { return false; }; within the components constructor but again didnt have any luck.
The main bits of code are as follows:
CartService - On construction we fetch the data from localStorage:
constructor(private http: HttpServiceService) {
this.getCartDetailsByUser();
}
getCartDetailsByUser() {
let data = JSON.parse(localStorage.getItem("products"));
this.cartObj = data;
if (data !== null) {
this.cartQty = data.length;
console.log(data);
}
}
When the user clicks "Add to Cart", this function is called, which adds the data to the local storage, but i need to update the DOM with whats in the cart and the cart length!
addCart(product) {
let products = [];
console.log(product);
if (localStorage.getItem("products")) {
products = JSON.parse(localStorage.getItem("products"));
}
products.push({
productId: product.productId,
image: product.image,
price: product.price,
});
localStorage.setItem("products", JSON.stringify(products));
}
I dont want to manually refresh the whole page for something so simple, I would just like the DOM to update with no need to refresh.
The header.component.html is what displays the shopping cart with its quantity etc
<div class="cart cart box_1 checkout-count-wrap">
<form action="#" method="post" class="last">
<button
class="w3view-cart"
type="submit"
(click)="openCheckoutModel()"
name="submit"
value=""
>
<p class="total_count_checkout">{{cart_qty}}</p>
<i class="fa fa-cart-arrow-down" aria-hidden="true"></i>
</button>
</form>
</div>
With the linked .ts class (header.component.ts). In the constructor, I get the qty which gets passed to the html above.
constructor(
private router: Router,
private cartService: CartServiceService,
private http: HttpServiceService
) {
this.cartService.cartServiceEvent.subscribe((data) => {
this.cart_qty = this.cartService.getQty();
});
}
When the user clicks on the shopping cart on the DOM, the following code is executed which gives a popup with the products etc in the cart:
openCheckoutModel() {
this.cartObj = this.cartService.getCartOBj();
this.cartTotalPrice = this.cartService.cartTotalPrice;
this.mainDialogType = "checkout";
}
Which in turn then displays the data in the shopping cart.
ALL of this works correctly apart from the cart not updating on the fly!!
Any help would be greatly appreciated!! :D
**** EDITED ANSWER ****
OK, so from the answer on the question, I have made a few small changes with the Observable pattern.. This half works - It updates the quantity of the cart on the fly (incrementing the number + 1 when a user clicks 'Add to Cart'). It also stores the product in the json object in localStorage as it did before. However, the actual items in the cart now do not show on the DOM, where as they did before. The data definitely exists with the correct products added being stored in localStorage, but now there seems to be some issues with the DOM displaying what's in it (productName, price etc)
Will this be due to another Observable being needed to track the item data in the cart? This may become clearer when I share my code (full classes)..
So here is the checkout-component.ts with the method being highlighted with *****
import { Component, OnInit } from "#angular/core";
import { CartServiceService } from "../service/cart-service.service";
import { HttpServiceService } from "../http-service.service";
import { Router } from "#angular/router";
#Component({
selector: "app-checkout",
templateUrl: "./checkout.component.html",
styleUrls: ["./checkout.component.css"],
})
export class CheckoutComponent implements OnInit {
cartObj = [];
cartTotalPrice: any;
pay_type = "cash_on_delivery";
delivery_address = "";
constructor(
private router: Router,
private cartService: CartServiceService,
private http: HttpServiceService
) {}
ngOnInit() {
this.getCartDetailsByUser();
//below function will be triggerd from when removing and qty is changing..
this.cartService.cartServiceEvent.subscribe((data) => {
this.cartObj = this.cartService.getCartOBj();
this.cartTotalPrice = this.cartService.cartTotalPrice;
});
}
qtyChange(qty, cartObj) {
var request = {
cartId: cartObj.id,
quantity: qty,
price: cartObj.price * qty,
};
this.http
.postRequestWithToken("api/addtocart/updateQtyForCart", request)
.subscribe(
(data: any) => {
this.cartService.getCartDetailsByUser(); //for updating in the application..
},
(error) => {
alert("Error while fetching the cart Details");
}
);
}
getCartDetailsByUser() {
let data = JSON.parse(localStorage.getItem("products"));
this.cartObj = data;
this.cartTotalPrice = this.getTotalAmounOfTheCart();
console.log("Cart Obj", this.cartObj);
console.log("Total", this.cartTotalPrice);
}
// getCartDetailsByUser(){
// this.http.postRequestWithToken("api/addtocart/getCartsByUserId",{}).subscribe((data:any)=>{
// this.cartObj = data;
// this.cartTotalPrice = this.getTotalAmounOfTheCart();
// },error=>{
// alert("Error while fetching the cart Details");
// })
// }
getTotalAmounOfTheCart() {
let obj = this.cartObj;
let totalPrice = 0;
for (var o in obj) {
totalPrice = totalPrice + parseFloat(obj[o].price);
}
return totalPrice.toFixed(2);
}
removeCartById(cartObj) {
if (confirm("Are you sure want to delete..?")) {
let id = cartObj.id;
this.cartService.removeCart(id);
}
}
checkoutCart() {
if (this.delivery_address == "") {
alert("Delivery address should not be empty");
return;
}
if (this.pay_type == "cash_on_delivery") {
let request = {
total_price: this.cartTotalPrice,
pay_type: "COD",
deliveryAddress: this.delivery_address,
};
this.http
.postRequestWithToken("api/order/checkout_order", request)
.subscribe(
(data: any) => {
alert("checkout process completed.Your Order is processed..");
this.cartService.getCartDetailsByUser();
this.router.navigate([""]);
},
(error) => {
alert("Error while fetching the cart Details");
}
);
} else {
alert("Payment Integration is not yet completed.");
}
}
}
Then the corresponding checkout-component.html
<div style="display: block;" id="w3lssbmincart">
<ul>
<li *ngFor="let cart of cartObj" class="sbmincart-item sbmincart-item-changed">
<div class="sbmincart-details-name">
<a class="sbmincart-name">{{cart.name}}</a>
</div>
<div class="sbmincart-details-quantity">
<select [(ngModel)]="cart.qty" (change)="qtyChange($event.target.value,cart)">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
<option>6</option>
<option>7</option>
<option>8</option>
<option>9</option>
<option>10</option>
</select>
</div>
<div class="sbmincart-details-remove">
<button (click)="removeCartById(cart)" type="button" class="sbmincart-remove" data-sbmincart-idx="0">×</button>
</div>
<div class="sbmincart-details-price">
<span class="sbmincart-price">{{cart.price}}</span>
</div>
</li>
</ul>
<div class="sbmincart-footer">
<div class="sbmincart-subtotal radio-wrap">
<span><input [(ngModel)]="pay_type" value="cash_on_delivery" type="radio" name="pay_type" /><span class="radio_text">Cash on Delivery</span></span>
<span><input [(ngModel)]="pay_type" value="online" name="pay_type" type="radio"/><span class="radio_text">Online</span></span>
</div>
<div class="sbmincart-subtotal">
<textarea placeholder="Enter the Delivery address" [(ngModel)]="delivery_address"></textarea>
</div>
<div class="sbmincart-subtotal">
Subtotal: <span class="price">${{cartTotalPrice}}</span>
</div>
<div>
<button (click)="checkoutCart()">Place Order</button>
</div>
</div>
<input type="hidden" name="bn" value="sbmincart_AddToCart_WPS_US">
</div>
</div>
</div>
</div>
Which simply loops over the cartObj stored within the service class.
I modified the header-component.ts (where the cart exists) to look like this:
import { Component, OnInit } from "#angular/core";
import { HttpServiceService } from "../http-service.service";
import { CartServiceService } from "../service/cart-service.service";
import { timingSafeEqual } from "crypto";
import { Router } from "#angular/router";
#Component({
selector: "app-header",
templateUrl: "./header.component.html",
styleUrls: ["./header.component.css"],
})
export class HeaderComponent implements OnInit {
isOpenLoginDialog = false;
currentDropDownMenu = "";
dialogType = "login";
mainDialogType = "";
isLogin = false;
mobile = "123456789";
password = "test";
cartObj = [];
cart_qty = 0;
cartTotalPrice = 0;
register = { name: "", email: "", mobile: "", password: "", re_password: "" };
welcomeUsername = "";
items$ = this.cartService.items$;
constructor(
private router: Router,
private cartService: CartServiceService,
private http: HttpServiceService
) {
let request = {};
this.http.postRequest("api/status", request).subscribe(
(data) => {
console.log("test", data);
},
(error) => {
alert("Server connection error " + error);
}
);
this.cartService.cartServiceEvent.subscribe((data) => {
this.cart_qty = this.cartService.getQty();
this.cartObj = this.cartService.getCartOBj();
});
}
logout() {
this.http.logout();
this.isLogin = false;
}
ngOnInit() {}
checkout_btn() {
this.router.navigate(["checkout"]);
}
openCheckoutModel() {
this.cartObj = this.cartService.getCartOBj();
this.cartTotalPrice = this.cartService.cartTotalPrice;
this.mainDialogType = "checkout";
}
openDialog() {
this.mainDialogType = "login";
}
dialogTypeInside(type) {
if (this.dialogType != type) this.dialogType = type;
}
closeDialog() {
this.mainDialogType = "";
}
curentDropDown(currentDropdownMenuName) {
if (this.currentDropDownMenu == currentDropdownMenuName) {
this.currentDropDownMenu = "";
} else {
this.currentDropDownMenu = currentDropdownMenuName;
}
}
}
Notice the openCheckoutModel() method which gets called which should pass the data down into the html component.
The data definitely exists as I am printing it out via console.log in the service class. The screen shot is below:
The front end DOM also shows there are 4 items in the cart:
However, the html does not display the actual data on the cart page:
From what I can see, i am looping over the data that definitely exists, however it does not show on the browser. I also get no errors :(
I hope this makes sense!
For peace of mind and for performance reasons, I would recommend you to go the RxJS way and turn things observable.
Short answer:
Here is a stackblitz example I could quickly make for you:
https://stackblitz.com/edit/angular-ivy-kgpicq
Long Answer:
I would recommend you structure your class as:
class CartService {
constructor() {
let cartItems = JSON.parse(localstorage.getItem('products'));
if (!cartItems) {
cartItems = []
}
this.itemsSubject.next(cartItems);
}
private itemsSubject = new BehaviorSubject<Product[]>([]);
items$ = itemsSubject.asObservable();
addToCart(item: Product) {
this.items$.pipe(
take(1),
map((products) => {
products.push(item);
localstorage.setItem('products', JSON.stringify(products));
},
).subscribe();
}
}
In your component class:
class ProductsPageComponent {
constructor(private cartService: CartService) {}
items$ = this.cartService.items$;
}
In your template:
<div class="cart cart box_1 checkout-count-wrap">
<form action="#" method="post" class="last">
<button class="w3view-cart" type="submit" (click)="openCheckoutModel()" name="submit" value="">
<p class="total_count_checkout">{{(items$ | async).length}}</p>
<i class="fa fa-cart-arrow-down" aria-hidden="true"></i>
</button>
</form>
</div>
AJAX
You can achieve this with ajax in plain javascript or with jQuery. My preference would go to jQuery.
Some code:
$.get( "/your_link", function( data ) {
$( ".result" ).html( data );
});
I would say you could write a another function in your controller that responds with the cart items. Then you could call the $.get whenever you would like to refresh your items. There's a lot of info on this so don't hesitate to look it up :)
https://api.jquery.com/jQuery.get/
Subscribe
Another solution might be to put a timeout on your subscribe. (this is some code from a project of mine which needed similar functionality to yours.)
In component.ts:
getUsers(): void {
// polling
timer(0, 2500)
.subscribe(() => {
this.userService.getUsers()
.subscribe(data => this.users = data);
});
}
In service.ts:
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.usersUrl);
}
HTML
<form nz-form [formGroup]="formGroup">
<nz-form-item>
<nz-form-label nzFor="username">
<span translate>Username</span>
</nz-form-label>
<nz-form-control nzHasFeedback>
<input nz-input name="username" type="text" id="username" formControlName="username"
[placeholder]="'Enter Username' | translate" (keydown.space)="$event.preventDefault()" />
<nz-form-explain *ngIf="
(formGroup.get('username')?.dirty && formGroup.get('username')?.errors) ||
formGroup.get('username')?.pending
">
<ng-container *ngIf="formGroup.get('username')?.hasError('required')">
Username is required
</ng-container>
<ng-container *ngIf="formGroup.get('username')?.hasError('duplicated')">
Username already exists. Please try different username
</ng-container>
<ng-container *ngIf="formGroup.get('username')?.pending">
Validating...
</ng-container>
</nz-form-explain>
</nz-form-control>
</nz-form-item>
</form>
***TS****
import { FormGroup, FormBuilder, Validators, FormControl, ValidationErrors } from '#angular/forms';
userNameAsyncValidator = (control: FormControl) =>
new Observable((observer: Observer<ValidationErrors | null>) => {
setTimeout(() => {
if (control.value === 'JasonWood') {
observer.next({ error: true, duplicated: true });
} else {
observer.next(null);
}
observer.complete();
}, 1000);
});
createFormGroup(data?: Partial<User>) {
const dataObj = {
id: [data && data.id],
username: [
{
value: (data && data.username) || '',
disabled: data instanceof User
},
[Validators.required],
[this.userNameAsyncValidator]
]
}
return this.fb.group(dataObj);
}
user.module
imports: [
CommonModule,
UsersRoutingModule,
FormsModule,
ReactiveFormsModule,
PageTableFilterModule,
CellRenderersModule,
NzDrawerModule,
NzFormModule,
NzInputModule,
NzSelectModule,
NzDividerModule
]
how to fix the error: ERROR TypeError: Cannot read property 'validate' of undefined.
What I want is username have a validation which is already exists and username is required.
I added the userNameAsyncValidator, after I added in username then it start causing an error which is the ERROR TypeError: Cannot read property 'validate' of undefined. but when I try to remove the userNameAsyncValidator in username it doesn't have any error.
how to fix it?
ERROR:
at normalizeAsyncValidator (forms.js:930)
at Array.map (<anonymous>)
at composeAsyncValidators (forms.js:2150)
at coerceToAsyncValidator (forms.js:2501)
at new FormControl (forms.js:3236)
at FormBuilder.push../node_modules/#angular/forms/fesm5/forms.js.FormBuilder.control (forms.js:6462)
at FormBuilder.push../node_modules/#angular/forms/fesm5/forms.js.FormBuilder._createControl (forms.js:6502)
at forms.js:6488
at Array.forEach (<anonymous>)
at FormBuilder.push../node_modules/#angular/forms/fesm5/forms.js.FormBuilder._reduceControls (forms.js:6487)```
Since this.userNameAsyncValidator is async validator add userNameAsyncValidator as third paramter to username control. another change is move your form creation inside ngOnInit or constructo like this:
Try this:
constructor(private fb: FormBuilder) {
this.validateForm = this.createFormGroup();
}
createFormGroup(data?: Partial<User>) {
const dataObj = {
id: [data && data.id],
username: [
{
value: (data && data.username) || '',
disabled: data instanceof User
},
[Validators.required],
[this.userNameAsyncValidator]
]
}
return this.fb.group(dataObj);
}
In my case, when upgrading to Angular 11. The following validators array was breaking the unit tests:
[, Validators.required]
Notice there is nothing before the comma. Removing the comma fixed the issue for me.
In my case i had to remove FormsModule and ReactiveFormModule from imports to work
I am using Ionic2 rc4. I have a login form.
As you can see, the Sign In button is disabled.
My problem is it should only be disabled when the form is invalid. However, when the form is valid, i.e. there is an Email and Password, it should not be disabled.
When I enter an Email and Password it stays disabled, but if I switch focus off the browser, and then back to it, it is enabled. It is as if the page is not refreshing to the correct status.
Question
Is there a way to get this enabled immediately as the form is valid?
loginemail.html
<ion-header>
<ion-navbar>
<button ion-button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
<ion-title>Login</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<form [formGroup]="loginForm" (ngSubmit)="submit()">
<ion-item>
<ion-label floating>Email</ion-label>
<ion-input type="text" formControlName="email" id="email" [(ngModel)]="personModel.emailAddress"></ion-input>
</ion-item>
<control-messages class="error-box" [control]="loginForm.controls.email"></control-messages>
<ion-item>
<ion-label floating>Password</ion-label>
<ion-input type="password" formControlName="password" id="password"></ion-input>
</ion-item>
<control-messages class="error-box" [control]="loginForm.controls.password"></control-messages>
<br/>
<ion-buttons>
<button ion-button class="form-button-text" type="submit" [disabled]="!loginForm.valid" block round>Sign In</button>
</ion-buttons>
</form>
<br/><br/>
<p (click)="forgotPassword()" class="small-text">Forgot email or password?</p>
<br/><br/><br/><br/>
<button ion-button color="light" (click)="register()" color="dark" clear block round class="form-button-text">Quick Sign up</button>
</ion-content>
loginemail.ts
import { Component, Input, Inject, forwardRef } from '#angular/core';
import { NavController, NavParams, ViewController, AlertController, MenuController, Events, Loading, LoadingController } from 'ionic-angular';
import { FirebaseAuth } from 'angularfire2';
import { ValidationService } from '../validation/validationService';
import { FormBuilder, FormControl, FormGroup, Validators } from '#angular/forms';
import { RegisterPage } from '../register/register';
import { ForgotPage } from '../forgot/forgot';
import { PersonModel } from '../model/personModel';
import { PersonService } from '../service/personService';
import { UtilityService } from '../utils/utilityService';
import { PersonPage } from '../person/person';
#Component({
templateUrl: 'loginemail.html'
})
export class LoginEmailPage {
public loginForm: FormGroup;
public errorMessage: string;
public personModel: PersonModel = null;
public personService: PersonService = null;
public personLoggedIn: boolean = false;
public menu: MenuController = null;
public utilityService: UtilityService = null;
public events: Events = null;
public loading: Loading = null;
public alertCtrl: AlertController = null;
public fireAuth: firebase.auth.Auth;
public userProfile: firebase.database.Reference;
#Input() control: FormControl;
constructor(#Inject(forwardRef(() => UtilityService)) utilityService, public auth: FirebaseAuth, menu: MenuController, public nav: NavController,
public navParams: NavParams, public builder: FormBuilder, public viewCtrl: ViewController, alertCtrl: AlertController,
personService: PersonService, events: Events, public loadingCtrl: LoadingController) {
this.fireAuth = firebase.auth();
this.userProfile = firebase.database().ref('/userProfile');
this.loginForm = builder.group({
'email': ['', [Validators.required, Validators.minLength(3), Validators.maxLength(55), ValidationService.emailValidator, (control) => ValidationService.personEmailNotExists(control, this.personService)]],
'password': ['', [Validators.required, Validators.minLength(5), Validators.maxLength(45), ValidationService.passwordValidator]]
});
this.alertCtrl = alertCtrl;
this.events = events;
this.utilityService = utilityService;
this.menu = menu;
this.personModel = this.navParams.get('personModel');
if (!this.personModel) {
this.personModel = new PersonModel();
}
this.personService = personService;
}
submit() {
this.loading = this.loadingCtrl.create({
content: 'Please wait...'
});
if (this.loginForm.dirty && this.loginForm.valid) {
this.loading.present().then(() => {
this.checkCredentials(this.loginForm.value.email, this.loginForm.value.password).then(() => {
if (this.personLoggedIn === true) {
this.loginFirebaseUser(this.loginForm.value.email, this.loginForm.value.password).then((authData) => {
let user: firebase.User = this.fireAuth.currentUser;
if (!user) {
this.auth.subscribe((authData) => {
this.login(authData.auth);
});
} else {
this.login(user);
}
}).catch((error) => {
console.error('Error trying to login ', error);
this.loading.dismiss().then(() => {
this.doAlert(error.message);
});
});
}
this.loading.dismiss();
});
});
}
}
login(firebaseUser: firebase.User): void {
let promise: Promise<any> = this.utilityService.login(this.personModel, firebaseUser, this.nav, this.auth, this.fireAuth, false);
if (promise) {
promise.then(() => {
let data = {
person: this.personModel
}
this.events.publish('push:notifications', data);
this.loading.dismiss().then(() => {
if (this.navParams.get('fromReview')) {
this.nav.pop();
} else if (this.navParams.get('fromChat')) {
this.nav.pop();
} else {
this.nav.setRoot(PersonPage);
}
});
}, error => {
this.utilityService.logout(this.auth, this.fireAuth).then(() => {
this.utilityService.setUpMenuItems();
this.auth.logout();
});
this.loading.dismiss().then(() => {
let alert = this.alertCtrl.create({
message: error.message,
buttons: [
{
text: "Ok",
role: 'cancel'
}
]
});
alert.present();
});
});
} else {
this.loading.dismiss();
}
}
checkCredentials(email: string, password: string): any {
let promiseUsername: Promise<PersonModel> = this.personService.getPersonByEmail(email);
return promiseUsername.then((personModel: PersonModel) => {
if (personModel.emailAddress != email) {
this.doAlert('Email does not exist.');
} else {
if (personModel.emailAddress === this.loginForm.value.email) {
this.personModel = personModel;
this.personLoggedIn = true;
} else {
this.personLoggedIn = false;
this.doAlert('Password does not match Username.');
}
}
});
}
doAlert(msg: string) {
this.loading.dismiss().then(() => {
let alert = this.alertCtrl.create({
title: 'Login',
subTitle: msg,
buttons: ['Dismiss']
});
alert.present().then(() => {
this.loading.dismiss();
});
});
}
register() {
this.nav.push(RegisterPage, {
})
}
forgotPassword() {
this.nav.push(ForgotPage, {
personModel: this.personModel
});
}
loginFirebaseUser(email: string, password: string): firebase.Promise<boolean> {
return this.fireAuth.signInWithEmailAndPassword(email, password).then(() => {
console.log('signInWithEmailAndPassword', email, password);
}).catch((error)=> {
console.error('Error signInWithEmailAndPassword', email, password, error.name, error.message);
throw new Error(error.message);
});
}
}
UPDATE
As per advise below, I have tried wrapping the firebase call in a Promise, but this makes no difference unfortunately.
return new Promise<any>(() => {
this.loginFirebaseUser(this.loginForm.value.email, this.loginForm.value.password).then((authData) => {
let user: firebase.User = this.fireAuth.currentUser;
if (!user) {
this.auth.subscribe((authData) => {
this.login(authData.auth);
});
} else {
this.login(user);
}
}).catch((error) => {
console.error('Error trying to login ', error);
this.loading.dismiss().then(() => {
this.doAlert(error.message);
});
});
});
Try setting a name to your email and password field
<ion-input type="text" formControlName="email" id="email" name="email" [(ngModel)]="personModel.emailAddress"></ion-input>
<ion-input type="password" formControlName="password" name="password" id="password"></ion-input>
That way loginForm has references to those elements for it's validtion.
I was having the same problem.
Looks like using firebase along with Ionic 2 breaks forms, inputs, buttons, etc.
I've already opened a github issue for this and i'm not the first one.
What i've done as a workaround is encapsulate every call to firebase within a promisse (see the link for an example) and it solved for me. There is some issues with using auth.onAuthStateChangedin app.components too, i haven't tried, but maybe putting it inside a Observable may be a goof ideia for this too.
So, the problem is using the firebase promisses along with ionic2/angular2, the typescript promisses works fine and doesn't breaks you app.