英文:
Angular + ASP.NET WebApi register form issues using Form Builder
问题
Sure, here are the translated parts of your text:
- 我试图创建我的第一个项目,但仍然面临着许多与Angular相关的问题。
- 目前,我正在使用Form Builder创建注册表单。
如果您需要更多的翻译,请提供要翻译的具体部分。
英文:
I try to create my first project but still facing many problems with Angular.
Currently I am busy with register form using Form Builder.
Firstly I paste my code:
- register component:
export class RegisterComponent implements OnInit {
//@Output() cancelRegister = new EventEmitter();
registerForm: FormGroup = new FormGroup({});
complexPassword = "(?=^.{8,20}$)(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()_+}{":;'?/>.<,])(?!.*\s).*$";
errors: string[] | null = null;
email: string;
genders = [
{value:GenderEnum.None, text:'Prefer not to say'},
{value:GenderEnum.Male, text:'Male'},
{value:GenderEnum.Female, text:'Female'},
{value:GenderEnum.Other, text:'Others'}
];
constructor(private fb: FormBuilder, private accountService: AccountService, private router: Router) {}
ngOnInit(): void {
this.initializeForm();
}
initializeForm() {
this.registerForm = this.fb.group({
displayName: ['', [Validators.required, Validators.minLength(2), Validators.maxLength(50)]],
email: ['', [Validators.required, Validators.minLength(5), Validators.maxLength(50), Validators.email],
[this.validateEmailNotTaken()]],
firstName: ['', [Validators.required, Validators.minLength(2), Validators.maxLength(50)]],
lastName: ['', [Validators.required, Validators.minLength(2), Validators.maxLength(50)]],
password: ['', [Validators.required, Validators.minLength(8), Validators.maxLength(20),
Validators.pattern(this.complexPassword)]],
dateOfBirth: [null],
gender: [null],
street: ['', [Validators.required, Validators.minLength(3), Validators.maxLength(50)]],
postalCode: ['', [Validators.required, Validators.minLength(4), Validators.maxLength(20)]],
city: ['', [Validators.required, Validators.minLength(3), Validators.maxLength(50)]],
confirmPassword: ['', [Validators.required, this.matchValues('password')]],
});
this.registerForm.controls['password'].valueChanges.subscribe({
next: () => this.registerForm.controls['confirmPassword'].updateValueAndValidity()
})
}
matchValues(matchTo: string): ValidatorFn {
return (control: AbstractControl) => {
return control.value === control.parent?.get(matchTo)?.value ? null : {notMatching: true}
}
}
passwordMatchValidator(group: AbstractControl) {
return group.get('password').value === group.get('confirmPassword').value
? null : { mismatch: true };
}
onSubmit() {
if (!this.registerForm.valid)
return;
this.accountService.register(this.registerForm.value).subscribe({
next: (response:any) => {
if (response.error)
{
this.errors = response.error;
}
else
this.router.navigateByUrl('/shop');
},
error: error =>
{
this.errors = error.errors
}
});
}
validateEmailNotTaken(): AsyncValidatorFn {
return (control: AbstractControl) => {
return control.valueChanges.pipe(
debounceTime(1000),
take(1),
switchMap(() => {
this.email = control.value;
return this.accountService.checkEmailExists(this.email).pipe(
map(result => result ? {emailExists: true} : null),
finalize(() => control.markAsTouched())
)
})
)
}
}
cancel() {
//this.cancelRegister.emit(false);
this.router.navigateByUrl('/shop')
}
private getDateOnly(dob: string | undefined) {
if (!dob) return;
let theDob = new Date(dob);
return new Date(theDob.setMinutes(theDob.getMinutes()-theDob.getTimezoneOffset()))
.toISOString().slice(0,10);
}
}
- register template:
<div class="d-flex justify-content-center mt-5">
<div class="col-3">
<form [formGroup]="registerForm" (ngSubmit)="onSubmit()">
<div class="text-center mb-4">
<h1 class="mb-3">Sign up</h1>
</div>
<div class="mb-3">
<label style="margin-right: 10px; margin-bottom: 10px;">Gender (optional):
</label><br>
<label *ngFor="let gender of genders" >
<ul>
<input type="radio" [value]="gender.value" formControlName="gender">
{{gender.text}}
</ul>
</label>
</div>
<app-text-input [formControl]="$any(registerForm.controls['displayName'])"
[label]="'Display Name'"></app-text-input>
<app-text-input [formControl]="$any(registerForm.controls['email'])" [label]="'Email'"></app-text-input>
<app-text-input [formControl]="$any(registerForm.controls['password'])" [label]="'Password'"
[type]="'password'"></app-text-input>
<app-text-input [formControl]="$any(registerForm.controls['confirmPassword'])" [label]="'Confirm Password'"
[type]="'password'"></app-text-input>
<app-text-input [formControl]="$any(registerForm.controls['firstName'])"
[label]="'First Name'"></app-text-input>
<app-text-input [formControl]="$any(registerForm.controls['lastName'])"
[label]="'Last Name'"></app-text-input>
<app-date-picker [formControl]="$any(registerForm.controls['dateOfBirth'])"
[label]="'Date of Birth'"></app-date-picker>
<app-text-input [formControl]="$any(registerForm.controls['street'])" [label]="'Street'"></app-text-input>
<app-text-input [formControl]="$any(registerForm.controls['postalCode'])"
[label]="'Postal Code'"></app-text-input>
<app-text-input [formControl]="$any(registerForm.controls['city'])" [label]="'City'"></app-text-input>
<ul class="text-danger list-unstyled" *ngIf="errors">
<li *ngFor="let error of errors">
{{error}}
</li>
</ul>
<div class="text-center">
<button [disabled]="registerForm.invalid" class="btn btn-lg btn-primary my-3 me-4 col-5"
type="submit">Sign up</button>
<button class="btn btn-lg btn-light my-3 col-5" type="button" (click)="cancel()">Cancel</button>
</div>
</form>
</div>
</div>
- account-service component
export class AccountService {
baseUrl = environment.apiUrl;
private currentUserSource = new ReplaySubject<User | null>(1);
currentUser$ = this.currentUserSource.asObservable();
errorsAccount: string[] | null = null;
constructor(private http: HttpClient, private router: Router) { }
loadCurrentUser(token: string | null) {
if (token === null) {
this.currentUserSource.next(null);
return of(null);
}
let headers = new HttpHeaders();
headers = headers.set('Authorization', `Bearer ${token}`);
return this.http.get<User>(this.baseUrl + 'account', {headers}).pipe(
map((user: any) => {
if (user) {
localStorage.setItem('token', user.data.token);
this.currentUserSource.next(user.data);
return user;
} else {
return null;
}
})
);
}
login(values: any) {
return this.http.post<User>(this.baseUrl + 'account/login', values).pipe(
map((user: any) => {
localStorage.setItem('token', user.data.token);
this.currentUserSource.next(user.data);
})
)
}
register(values: any) {
return this.http.post<User>(this.baseUrl + 'account/register', values).pipe(
tap((res:any) => {
if (!res.error)
{
localStorage.setItem('token', res.data.token);
this.currentUserSource.next(res.data);
}
})
)
}
logout() {
localStorage.removeItem('token');
this.currentUserSource.next(null);
this.router.navigateByUrl('/');
}
checkEmailExists(email: string) {
return this.http.get<boolean>(this.baseUrl + 'account/emailExists?EmailToCheck=' + email);
}
}
Problems:
- How to send Gender as integer in Form Builder form that will be accepted by backend and database?
Because now it is sending as string.
"gender": "2"
instead of"gender": 2
- I am trying to display Gender Enum items as radio buttons which can be chosen by user by they are duplicated in template. How to fix it?
- In backend I have implemented checking whether provided email is already in use.
Unfortunately when email is already taken I cannot display response from backend in frontend.
DevTools/Network/Payload:
I tried to wrap object which I am getting back from API in (object: any).
When I provide free email I got response like this:
{
"data":
{
"displayName":"Thomas",
"dateOfBirth":null,
"gender":null,
"email":"tom@test2.com",
"token":"someTokenGenerated"
},
"error":null
}
Gender and Date of Birth are optional properties.
Please understand I'm not a professional developer but keeping learning.
Sorry for maybe redundant amount of code but I wanted to clarify my issues as much as it possible.
Thanks in advance for any advice!
EDIT:
After changes according to Eliseo answer I got still some issues:
But [value] works as well - binding numbers to gender.
In register form when I am typing email which already exists (adam@test.com) still not displaying under the email label that the "email is already taken".
答案1
得分: 1
以下是您要翻译的内容:
在.ts文件中的枚举是"complicates",例如,可以在.ts中使用一个简单的数组
genders = [
{ value: GenderEnum.None, text: ' ' },
{ value: GenderEnum.Male, text: '男' },
{ value: GenderEnum.Female, text: '女' },
{ value: GenderEnum.Other, text: '其他' }
]
并且使用ngValue而不是value
<label .. *ngFor="let gender of genders" >
<ul>
<input type="radio" [ngValue]="gender.value" formControlName="gender">
{{gender.text}}
</ul>
</label>
或者直接使用Object.keys和过滤器以及索引(这很有效,因为第一个元素的值为0,而您正在使用带有数字的枚举)
genders = Object.keys(GenderEnum).filter((x: any) => isNaN(x))
<label .. *ngFor="let gender of genders; let i=index" >
<ul>
<input type="radio" [ngValue]="i" formControlName="gender">
{{gender}}
</ul>
</label>
当API返回"error"时,您的提交应该考虑到这一点。请注意,响应是ok的。
onSubmit() {
// 发送之前检查是否有效
if (!this.registerForm.valid)
return;
this.accountService.register(this.registerForm.value).subscribe({
next: (response: any) => {
if (response.error)
{
// 做一些事情,例如 alert(response.error)
// 或者 this.error=response.error
}
else
this.router.navigateByUrl('/shop'),
},
error: error =>
{
this.errors = error.errors
}
});
}
但是您的服务应该返回"error"
register(values: any) {
return this.http.post<User>(this.baseUrl + 'account/register', values).pipe(
tap((res: any) => {
if (!res.error)
{
// 存储 "res.data.token"
localStorage.setItem('token', res.data.token);
this.currentUserSource.next(res.data);
}
})
)
}
注意:如果不需要更改Observable的响应,请使用"tap"而不是"map"。
注意2:我看到许多[formControl]="$any(registerForm.controls['email'])"
。您可以使您的app-text-inputs变为
formControl!: FormControl;
@Input('formControl') set _formControl(value: AbstractControl){
this.formControl = value as FormControl;
}
这样,您可以使用[formControl]="registerForm.controls['email']"
。
英文:
The enums in .ts are "complicates", see, e.g. this link about it you can use in .ts a simple array
genders = [
{value:GenderEnum.None,text:''},
{value:GenderEnum.Male,text:'Male},
{value:GenderEnum.Female,text:'Female'},
{value:GenderEnum.Other,text:'Others'}
]
And use ngValue instead value
<label .. *ngFor="let gender of genders" >
<ul>
<input type="radio" [ngValue]="gender.value" formControlName="gender">
{{gender.text}}
</ul>
</label>
Or use Object.keys and filter directly and the index (this works well because the first element gets the value 0 and you're using enum with numbers)
genders=Object.keys(GenderEnum).filter((x:any)=>isNaN(x))
<label .. *ngFor="let gender of genders;let i=index" >
<ul>
<input type="radio" [ngValue]="i" formControlName="gender">
{{gender}}
</ul>
</label>
Your submit can take account when the API return the "error". Be careful, the response is ok
onSubmit() {
//check if is valid before send
if (!this.registerForm.valid)
return;
this.accountService.register(this.registerForm.value).subscribe({
next: (response:any) => {
if (response.error)
{
//do something, e.g. alert(response.error)
//or this.error=response.error
}
else
this.router.navigateByUrl('/shop'),
}
error: error =>
{
this.errors = error.errors
}
});
}
But your service should return the "error"
register(values: any) {
return this.http.post<User>(this.baseUrl + 'account/register', values).pipe(
tap((res:any) => {
if (!res.error)
{
//store "res.data.token"
localStorage.setItem('token', res.data.token);
this.currentUserSource.next(res.data);
}
})
)
}
NOTE: use "tap" instead of "map" if you needn't change the response of an Observable.
NOTE2: I see many [formControl]="$any(registerForm.controls['email'])"
. You can make that your app-text-inputs becomes like
formControl!:FormControl
@Input('formControl') set _formControl(value:AbstractControl){
this.formControl=value as FormControl;
}
In this way you can use [formControl]="registerForm.controls['email']"
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论