Angular + ASP.NET WebApi使用表单构建器注册表单时出现问题。

huangapple go评论95阅读模式
英文:

Angular + ASP.NET WebApi register form issues using Form Builder

问题

Sure, here are the translated parts of your text:

  1. 我试图创建我的第一个项目,但仍然面临着许多与Angular相关的问题。
  2. 目前,我正在使用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:
  1. export class RegisterComponent implements OnInit {
  2. //@Output() cancelRegister = new EventEmitter();
  3. registerForm: FormGroup = new FormGroup({});
  4. complexPassword = "(?=^.{8,20}$)(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()_+}{":;'?/>.<,])(?!.*\s).*$";
  5. errors: string[] | null = null;
  6. email: string;
  7. genders = [
  8. {value:GenderEnum.None, text:'Prefer not to say'},
  9. {value:GenderEnum.Male, text:'Male'},
  10. {value:GenderEnum.Female, text:'Female'},
  11. {value:GenderEnum.Other, text:'Others'}
  12. ];
  13. constructor(private fb: FormBuilder, private accountService: AccountService, private router: Router) {}
  14. ngOnInit(): void {
  15. this.initializeForm();
  16. }
  17. initializeForm() {
  18. this.registerForm = this.fb.group({
  19. displayName: ['', [Validators.required, Validators.minLength(2), Validators.maxLength(50)]],
  20. email: ['', [Validators.required, Validators.minLength(5), Validators.maxLength(50), Validators.email],
  21. [this.validateEmailNotTaken()]],
  22. firstName: ['', [Validators.required, Validators.minLength(2), Validators.maxLength(50)]],
  23. lastName: ['', [Validators.required, Validators.minLength(2), Validators.maxLength(50)]],
  24. password: ['', [Validators.required, Validators.minLength(8), Validators.maxLength(20),
  25. Validators.pattern(this.complexPassword)]],
  26. dateOfBirth: [null],
  27. gender: [null],
  28. street: ['', [Validators.required, Validators.minLength(3), Validators.maxLength(50)]],
  29. postalCode: ['', [Validators.required, Validators.minLength(4), Validators.maxLength(20)]],
  30. city: ['', [Validators.required, Validators.minLength(3), Validators.maxLength(50)]],
  31. confirmPassword: ['', [Validators.required, this.matchValues('password')]],
  32. });
  33. this.registerForm.controls['password'].valueChanges.subscribe({
  34. next: () => this.registerForm.controls['confirmPassword'].updateValueAndValidity()
  35. })
  36. }
  37. matchValues(matchTo: string): ValidatorFn {
  38. return (control: AbstractControl) => {
  39. return control.value === control.parent?.get(matchTo)?.value ? null : {notMatching: true}
  40. }
  41. }
  42. passwordMatchValidator(group: AbstractControl) {
  43. return group.get('password').value === group.get('confirmPassword').value
  44. ? null : { mismatch: true };
  45. }
  46. onSubmit() {
  47. if (!this.registerForm.valid)
  48. return;
  49. this.accountService.register(this.registerForm.value).subscribe({
  50. next: (response:any) => {
  51. if (response.error)
  52. {
  53. this.errors = response.error;
  54. }
  55. else
  56. this.router.navigateByUrl('/shop');
  57. },
  58. error: error =>
  59. {
  60. this.errors = error.errors
  61. }
  62. });
  63. }
  64. validateEmailNotTaken(): AsyncValidatorFn {
  65. return (control: AbstractControl) => {
  66. return control.valueChanges.pipe(
  67. debounceTime(1000),
  68. take(1),
  69. switchMap(() => {
  70. this.email = control.value;
  71. return this.accountService.checkEmailExists(this.email).pipe(
  72. map(result => result ? {emailExists: true} : null),
  73. finalize(() => control.markAsTouched())
  74. )
  75. })
  76. )
  77. }
  78. }
  79. cancel() {
  80. //this.cancelRegister.emit(false);
  81. this.router.navigateByUrl('/shop')
  82. }
  83. private getDateOnly(dob: string | undefined) {
  84. if (!dob) return;
  85. let theDob = new Date(dob);
  86. return new Date(theDob.setMinutes(theDob.getMinutes()-theDob.getTimezoneOffset()))
  87. .toISOString().slice(0,10);
  88. }
  89. }
  • register template:
  1. <div class="d-flex justify-content-center mt-5">
  2. <div class="col-3">
  3. <form [formGroup]="registerForm" (ngSubmit)="onSubmit()">
  4. <div class="text-center mb-4">
  5. <h1 class="mb-3">Sign up</h1>
  6. </div>
  7. <div class="mb-3">
  8. <label style="margin-right: 10px; margin-bottom: 10px;">Gender (optional):
  9. </label><br>
  10. <label *ngFor="let gender of genders" >
  11. <ul>
  12. <input type="radio" [value]="gender.value" formControlName="gender">
  13. {{gender.text}}
  14. </ul>
  15. </label>
  16. </div>
  17. <app-text-input [formControl]="$any(registerForm.controls['displayName'])"
  18. [label]="'Display Name'"></app-text-input>
  19. <app-text-input [formControl]="$any(registerForm.controls['email'])" [label]="'Email'"></app-text-input>
  20. <app-text-input [formControl]="$any(registerForm.controls['password'])" [label]="'Password'"
  21. [type]="'password'"></app-text-input>
  22. <app-text-input [formControl]="$any(registerForm.controls['confirmPassword'])" [label]="'Confirm Password'"
  23. [type]="'password'"></app-text-input>
  24. <app-text-input [formControl]="$any(registerForm.controls['firstName'])"
  25. [label]="'First Name'"></app-text-input>
  26. <app-text-input [formControl]="$any(registerForm.controls['lastName'])"
  27. [label]="'Last Name'"></app-text-input>
  28. <app-date-picker [formControl]="$any(registerForm.controls['dateOfBirth'])"
  29. [label]="'Date of Birth'"></app-date-picker>
  30. <app-text-input [formControl]="$any(registerForm.controls['street'])" [label]="'Street'"></app-text-input>
  31. <app-text-input [formControl]="$any(registerForm.controls['postalCode'])"
  32. [label]="'Postal Code'"></app-text-input>
  33. <app-text-input [formControl]="$any(registerForm.controls['city'])" [label]="'City'"></app-text-input>
  34. <ul class="text-danger list-unstyled" *ngIf="errors">
  35. <li *ngFor="let error of errors">
  36. {{error}}
  37. </li>
  38. </ul>
  39. <div class="text-center">
  40. <button [disabled]="registerForm.invalid" class="btn btn-lg btn-primary my-3 me-4 col-5"
  41. type="submit">Sign up</button>
  42. <button class="btn btn-lg btn-light my-3 col-5" type="button" (click)="cancel()">Cancel</button>
  43. </div>
  44. </form>
  45. </div>
  46. </div>
  • account-service component
  1. export class AccountService {
  2. baseUrl = environment.apiUrl;
  3. private currentUserSource = new ReplaySubject<User | null>(1);
  4. currentUser$ = this.currentUserSource.asObservable();
  5. errorsAccount: string[] | null = null;
  6. constructor(private http: HttpClient, private router: Router) { }
  7. loadCurrentUser(token: string | null) {
  8. if (token === null) {
  9. this.currentUserSource.next(null);
  10. return of(null);
  11. }
  12. let headers = new HttpHeaders();
  13. headers = headers.set('Authorization', `Bearer ${token}`);
  14. return this.http.get<User>(this.baseUrl + 'account', {headers}).pipe(
  15. map((user: any) => {
  16. if (user) {
  17. localStorage.setItem('token', user.data.token);
  18. this.currentUserSource.next(user.data);
  19. return user;
  20. } else {
  21. return null;
  22. }
  23. })
  24. );
  25. }
  26. login(values: any) {
  27. return this.http.post<User>(this.baseUrl + 'account/login', values).pipe(
  28. map((user: any) => {
  29. localStorage.setItem('token', user.data.token);
  30. this.currentUserSource.next(user.data);
  31. })
  32. )
  33. }
  34. register(values: any) {
  35. return this.http.post<User>(this.baseUrl + 'account/register', values).pipe(
  36. tap((res:any) => {
  37. if (!res.error)
  38. {
  39. localStorage.setItem('token', res.data.token);
  40. this.currentUserSource.next(res.data);
  41. }
  42. })
  43. )
  44. }
  45. logout() {
  46. localStorage.removeItem('token');
  47. this.currentUserSource.next(null);
  48. this.router.navigateByUrl('/');
  49. }
  50. checkEmailExists(email: string) {
  51. return this.http.get<boolean>(this.baseUrl + 'account/emailExists?EmailToCheck=' + email);
  52. }
  53. }

Problems:

  1. 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
  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?

Angular + ASP.NET WebApi使用表单构建器注册表单时出现问题。

  1. 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.
    Angular + ASP.NET WebApi使用表单构建器注册表单时出现问题。

DevTools/Network/Payload:

Angular + ASP.NET WebApi使用表单构建器注册表单时出现问题。

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:

  1. {
  2. "data":
  3. {
  4. "displayName":"Thomas",
  5. "dateOfBirth":null,
  6. "gender":null,
  7. "email":"tom@test2.com",
  8. "token":"someTokenGenerated"
  9. },
  10. "error":null
  11. }

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:

Angular + ASP.NET WebApi使用表单构建器注册表单时出现问题。

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中使用一个简单的数组

  1. genders = [
  2. { value: GenderEnum.None, text: ' ' },
  3. { value: GenderEnum.Male, text: '男' },
  4. { value: GenderEnum.Female, text: '女' },
  5. { value: GenderEnum.Other, text: '其他' }
  6. ]

并且使用ngValue而不是value

  1. <label .. *ngFor="let gender of genders" >
  2. <ul>
  3. <input type="radio" [ngValue]="gender.value" formControlName="gender">
  4. {{gender.text}}
  5. </ul>
  6. </label>

或者直接使用Object.keys和过滤器以及索引(这很有效,因为第一个元素的值为0,而您正在使用带有数字的枚举)

  1. genders = Object.keys(GenderEnum).filter((x: any) => isNaN(x))
  1. <label .. *ngFor="let gender of genders; let i=index" >
  2. <ul>
  3. <input type="radio" [ngValue]="i" formControlName="gender">
  4. {{gender}}
  5. </ul>
  6. </label>

当API返回"error"时,您的提交应该考虑到这一点。请注意,响应是ok的。

  1. onSubmit() {
  2. // 发送之前检查是否有效
  3. if (!this.registerForm.valid)
  4. return;
  5. this.accountService.register(this.registerForm.value).subscribe({
  6. next: (response: any) => {
  7. if (response.error)
  8. {
  9. // 做一些事情,例如 alert(response.error)
  10. // 或者 this.error=response.error
  11. }
  12. else
  13. this.router.navigateByUrl('/shop'),
  14. },
  15. error: error =>
  16. {
  17. this.errors = error.errors
  18. }
  19. });
  20. }

但是您的服务应该返回"error"

  1. register(values: any) {
  2. return this.http.post<User>(this.baseUrl + 'account/register', values).pipe(
  3. tap((res: any) => {
  4. if (!res.error)
  5. {
  6. // 存储 "res.data.token"
  7. localStorage.setItem('token', res.data.token);
  8. this.currentUserSource.next(res.data);
  9. }
  10. })
  11. )
  12. }

注意:如果不需要更改Observable的响应,请使用"tap"而不是"map"。

注意2:我看到许多[formControl]="$any(registerForm.controls['email'])"。您可以使您的app-text-inputs变为

  1. formControl!: FormControl;
  2. @Input('formControl') set _formControl(value: AbstractControl){
  3. this.formControl = value as FormControl;
  4. }

这样,您可以使用[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

  1. genders = [
  2. {value:GenderEnum.None,text:&#39;&#39;},
  3. {value:GenderEnum.Male,text:&#39;Male},
  4. {value:GenderEnum.Female,text:&#39;Female&#39;},
  5. {value:GenderEnum.Other,text:&#39;Others&#39;}
  6. ]

And use ngValue instead value

  1. &lt;label .. *ngFor=&quot;let gender of genders&quot; &gt;
  2. &lt;ul&gt;
  3. &lt;input type=&quot;radio&quot; [ngValue]=&quot;gender.value&quot; formControlName=&quot;gender&quot;&gt;
  4. {{gender.text}}
  5. &lt;/ul&gt;
  6. &lt;/label&gt;

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)

  1. genders=Object.keys(GenderEnum).filter((x:any)=&gt;isNaN(x))
  2. &lt;label .. *ngFor=&quot;let gender of genders;let i=index&quot; &gt;
  3. &lt;ul&gt;
  4. &lt;input type=&quot;radio&quot; [ngValue]=&quot;i&quot; formControlName=&quot;gender&quot;&gt;
  5. {{gender}}
  6. &lt;/ul&gt;
  7. &lt;/label&gt;

Your submit can take account when the API return the "error". Be careful, the response is ok

  1. onSubmit() {
  2. //check if is valid before send
  3. if (!this.registerForm.valid)
  4. return;
  5. this.accountService.register(this.registerForm.value).subscribe({
  6. next: (response:any) =&gt; {
  7. if (response.error)
  8. {
  9. //do something, e.g. alert(response.error)
  10. //or this.error=response.error
  11. }
  12. else
  13. this.router.navigateByUrl(&#39;/shop&#39;),
  14. }
  15. error: error =&gt;
  16. {
  17. this.errors = error.errors
  18. }
  19. });

}

But your service should return the "error"

  1. register(values: any) {
  2. return this.http.post&lt;User&gt;(this.baseUrl + &#39;account/register&#39;, values).pipe(
  3. tap((res:any) =&gt; {
  4. if (!res.error)
  5. {
  6. //store &quot;res.data.token&quot;
  7. localStorage.setItem(&#39;token&#39;, res.data.token);
  8. this.currentUserSource.next(res.data);
  9. }
  10. })
  11. )
  12. }

NOTE: use "tap" instead of "map" if you needn't change the response of an Observable.

NOTE2: I see many [formControl]=&quot;$any(registerForm.controls[&#39;email&#39;])&quot;. You can make that your app-text-inputs becomes like

  1. formControl!:FormControl
  2. @Input(&#39;formControl&#39;) set _formControl(value:AbstractControl){
  3. this.formControl=value as FormControl;
  4. }

In this way you can use [formControl]=&quot;registerForm.controls[&#39;email&#39;]&quot;

huangapple
  • 本文由 发表于 2023年5月7日 06:33:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/76191470.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定