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

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

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:
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:

  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:

{
"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:

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

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:&#39;&#39;},
{value:GenderEnum.Male,text:&#39;Male},
{value:GenderEnum.Female,text:&#39;Female&#39;},
{value:GenderEnum.Other,text:&#39;Others&#39;}
]

And use ngValue instead value

&lt;label .. *ngFor=&quot;let gender of genders&quot; &gt;
&lt;ul&gt;
&lt;input type=&quot;radio&quot; [ngValue]=&quot;gender.value&quot; formControlName=&quot;gender&quot;&gt; 
{{gender.text}}
&lt;/ul&gt;
&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)

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

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) =&gt; {
if (response.error)
{
//do something, e.g. alert(response.error)
//or this.error=response.error
}
else
this.router.navigateByUrl(&#39;/shop&#39;),
}
error: error =&gt; 
{
this.errors = error.errors
}
});

}

But your service should return the "error"

register(values: any) {
return this.http.post&lt;User&gt;(this.baseUrl + &#39;account/register&#39;, values).pipe(
tap((res:any) =&gt; {
if (!res.error)
{
//store &quot;res.data.token&quot;
localStorage.setItem(&#39;token&#39;, 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]=&quot;$any(registerForm.controls[&#39;email&#39;])&quot;. You can make that your app-text-inputs becomes like

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

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:

确定