Эта статья изначально была размещена по адресу https://www.blog.duomly.com/angular-course-with-building-a-banking-application-with-tailwind-css-lesson-4-user-registration.

Почти три недели мы вместе шаг за шагом создаем финтех-приложение Angular 9.

Мы уже прошли настройку проекта и создание шаблона формы входа, затем создали логику для нашей формы входа, а на прошлой неделе отобразили данные пользователя в шаблоне.

Сегодня пришло время урока 4, где мы займемся регистрацией пользователей.

Если вы не начали со мной, не стесняйтесь пройти все уроки до сих пор. Или вы можете получить код из предыдущего урока и сразу перейти к этому уроку. Вот ссылка на наш репозиторий Github для этого курса Angular.

Код к уроку 3

Есть еще одна вещь, которую вы должны знать. Вместе с этим курсом по интерфейсу Angular мы создаем курс для серверной части, поэтому вы можете использовать их оба для создания полностью работающего приложения. Вот ссылка на бэкэнд Golang Course.

Тем временем мы также создаем Курс Node.js, но он все еще на несколько шагов позади.

Ну и конечно, как всегда, вот видеоверсия этого урока.

Итак, откройте код в вашем любимом редакторе кода, запустите ваш бэкенд и начнем с регистрации пользователя в Angular 9.

1. Создайте новый компонент

Начнем с создания нашей регистрационной формы. Вначале мы будем использовать Angular CLI для создания компонента регистрации.

$ ng generate component register

Когда все будет готово, нам нужно посетить Tailwind CSS и получить код формы!

2. Шаблон регистрационной формы здания

Мы собираемся создать форму, похожую на нашу форму входа в систему, она будет почти такой же, только нам нужно добавить еще несколько полей, таких как электронная почта и подтверждение пароля.

Давайте откроем файл register.component.html и создадим следующий код.

<div id="register-container" class="flex container mx-auto items-center justify-center">
  <div class="w-full max-w-xs">
    <form class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
      <img src="../../assets/logo.png" class="logo" />
      <div class="mb-4">
        <label class="block text-gray-700 text-sm font-bold mb-2" for="username">
          Username
        </label>
        <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="username" type="text" placeholder="Username">
      </div>
      <div class="mb-4">
        <label class="block text-gray-700 text-sm font-bold mb-2" for="email">
          Email
        </label>
        <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="email" type="email" placeholder="Email">
      </div>
      <div class="mb-4">
        <label class="block text-gray-700 text-sm font-bold mb-2" for="password">
          Password
        </label>
        <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="password" type="password" placeholder="******************">
      </div>
      <div class="mb-4">
        <label class="block text-gray-700 text-sm font-bold mb-2" for="confirm-password">
          Confirm Password
        </label>
        <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline" id="confirm-password" type="password" placeholder="******************">
      </div>
      <div class="flex items-center justify-between">
        <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline" type="button">
          Sign In
        </button>
      </div>
      <div class="flex items-center justify-between mt-4">
        <p class="inline-block align-baseline text-sm text-blue-500">Already have an account? 
          <a class="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800">
            Sign in
          </a>
        </p>
      </div>
    </form>
    <p class="text-center text-gray-500 text-xs">
      &copy;2020 Banking App by Duomly. All rights reserved.
    </p>
  </div>
</div>

Хорошо, теперь давайте откроем файл register.component.scss и поместим туда следующие стили.

#register-container {
  min-height: 100vh;
  min-width: 100vw;
  color: white;
  position: relative;
  background-image: url('../../assets/background.png');
  background-size: cover;
  background-repeat: no-repeat;
  background-position: center;
  .logo {
    max-height: 60px;
    margin: auto;
    margin-bottom: 30px;
  }
  .notification {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
  }
}

Отлично, когда этот шаг завершен, пришло время установить маршрут для нашего компонента и сделать его видимым.

3. Настройка маршрута

Чтобы настроить маршрут для нашего нового компонента, давайте откроем файл app-routing.module.ts, и нам нужно импортировать наш вновь созданный компонент.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AuthGuardGuard } from './services/guards/auth-guard.guard';
import { LoginComponent } from './login/login.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { RegisterComponent } from './register/register.component';

const routes: Routes = [
  { path: '', component: LoginComponent },
  { path: 'register', component: RegisterComponent },
  { path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuardGuard] }
];
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Когда ваши маршруты будут выглядеть так, как показано в приведенном выше коде, мы добавим ссылки в LoginComponent и RegisterComponent. Начнем с файла register.component.html, где мы собираемся отправить пользователя на форму входа, если у него уже есть учетная запись.

<div class="flex items-center justify-between mt-4">
   <p class="inline-block align-baseline text-sm text-blue-500">Already have an account? 
       <a class="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800" [routerLink]="['/']">
          Sign in
       </a>
   </p>
</div>

Теперь мы сделаем то же самое, но добавим ссылку, которая позволит пользователям перейти к регистрационной форме для настройки учетной записи. Откроем файл login.component.html и проведем небольшой рефакторинг.

<div class="flex items-center justify-between mt-4">
    <p class="inline-block align-baseline text-sm text-blue-500">Don't have an account? 
          <a class="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800" [routerLink]="['/register']">
            Sign up
       </a>
    </p>
</div>

Отлично, теперь ваши формы входа и регистрации должны выглядеть так, как показано ниже.

Теперь мы начнем создавать логику для нашей функции регистрации.

4. Обработка входных данных

Пришло время создать функцию, которая сохранит входные значения из нашей регистрационной формы. Откройте файл registe.component.ts и начнем с определения следующих значений.

export class RegisterComponent implements OnInit {
  username = '';
  email = '';
  password = '';
  confirmPassword = '';
  constructor() { }
  ngOnInit(): void {}
  onKey(event: any, type: string) {
    if (type === 'username') {
      this.username = event.target.value;
    } else if (type === 'email') {
      this.email = event.target.value;
    } else if (type === 'password') {
      this.password = event.target.value;
    } else if (type === 'confirmPassword') {
      this.confirmPassword = event.target.value;
    }
  }
}

Теперь мы можем добавить эту функцию в наш шаблон. Вернемся к register.component.html и добавим функцию к каждому входу.

<form class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
      <img src="../../assets/logo.png" class="logo" />
      <div class="mb-4">
        <label class="block text-gray-700 text-sm font-bold mb-2" for="username">
          Username
        </label>
        <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="username" type="text" placeholder="Username" (keyup)="onKey($event, 'username')">
      </div>
      <div class="mb-4">
        <label class="block text-gray-700 text-sm font-bold mb-2" for="email">
          Email
        </label>
        <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="email" type="email" placeholder="Email" (keyup)="onKey($event, 'email')">
      </div>
      <div class="mb-4">
        <label class="block text-gray-700 text-sm font-bold mb-2" for="password">
          Password
        </label>
        <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="password" type="password" placeholder="******************" (keyup)="onKey($event, 'password')">
      </div>
      <div class="mb-4">
        <label class="block text-gray-700 text-sm font-bold mb-2" for="confirm-password">
          Confirm Password
        </label>
        <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline" id="confirm-password" type="password" placeholder="******************" (keyup)="onKey($event, 'confirmPassword')">
      </div>
      <div class="flex items-center justify-between">
        <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline" type="button">
          Sign In
        </button>
      </div>
      <div class="flex items-center justify-between mt-4">
        <p class="inline-block align-baseline text-sm text-blue-500">Already have an account? 
          <a class="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800" [routerLink]="['/']">
            Sign in
          </a>
        </p>
      </div>
    </form>

Наша форма обновлена ​​прямо сейчас, поэтому мы можем выполнить проверку наших входных данных.

5. Проверка

Прежде чем мы отправим вызов API на наш сервер, мы должны убедиться, что значения, передаваемые в форме, имеют правильный формат и сохранены для нашего приложения. Потому что, если вы помните урок SQL инъекции моего друга, нашему приложению может быть опасно отправлять что-либо на сервер без проверки.

Давайте откроем файл register.component.ts и создадим следующий код.

export class RegisterComponent implements OnInit {
  username = '';
  email = '';
  password = '';
  confirmPassword = '';
  valid = {
    username: true,
    email: true,
    password: true,
  };
  constructor() { }
  ngOnInit(): void {}
  validate(type: string): void {
    const usernamePattern = /^[\w-.]*$/;
    const emailPattern = /\S+@\S+\.\S+/;
    if (type === 'username') {
      if (this.username.length < 5) {
        this.valid.username = false;
      } else {
        this.valid.username = usernamePattern.test(this.username);
      }
    } else if (type === 'email') {
      this.valid.email = emailPattern.test(this.email);
    } else if (type === ('confirmPassword' || 'password')) {
      if (this.password !== this.confirmPassword) {
        this.valid.password = false;
      } else {
        this.valid.password = true;
      }
    }
  }
  onKey(event: any, type: string) {
    if (type === 'username') {
      this.username = event.target.value;
    } else if (type === 'email') {
      this.email = event.target.value;
    } else if (type === 'password') {
      this.password = event.target.value;
    } else if (type === 'confirmPassword') {
      this.confirmPassword = event.target.value;
    }
    this.validate(type);
  }
}

Чтобы завершить нашу проверку, мы должны сообщить пользователям, в каких полях есть ошибки, и для этого мы будем отображать сообщения об ошибках условно. Давайте перейдем к файлу register.component.html и убедимся, что ваш код выглядит так.

<form class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
      <img src="../../assets/logo.png" class="logo" />
      <div class="mb-4">
        <label class="block text-gray-700 text-sm font-bold mb-2" for="username">
          Username
        </label>
        <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="username" type="text" placeholder="Username" (keyup)="onKey($event, 'username')">
        <p *ngIf="!valid.username" class="text-red-500 text-xs italic">Username can consist of letters and numbers only and the minimum length is 5!</p>
      </div>
      <div class="mb-4">
        <label class="block text-gray-700 text-sm font-bold mb-2" for="email">
          Email
        </label>
        <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="email" type="email" placeholder="Email" (keyup)="onKey($event, 'email')">
        <p *ngIf="!valid.email" class="text-red-500 text-xs italic">Your email is not correct!</p>
      </div>
      <div class="mb-4">
        <label class="block text-gray-700 text-sm font-bold mb-2" for="password">
          Password
        </label>
        <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="password" type="password" placeholder="******************" (keyup)="onKey($event, 'password')">
      </div>
      <div class="mb-4">
        <label class="block text-gray-700 text-sm font-bold mb-2" for="confirm-password">
          Confirm Password
        </label>
        <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline" id="confirm-password" type="password" placeholder="******************" (keyup)="onKey($event, 'confirmPassword')">
        <p *ngIf="!valid.password" class="text-red-500 text-xs italic">Passwords are different!</p>
      </div>
      <div class="flex items-center justify-between">
        <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline" type="button">
          Sign In
        </button>
      </div>
      <div class="flex items-center justify-between mt-4">
        <p class="inline-block align-baseline text-sm text-blue-500">Already have an account? 
          <a class="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800" [routerLink]="['/']">
            Sign in
          </a>
        </p>
      </div>
    </form>

Отлично, когда эта часть будет выполнена, нам нужно будет провести некоторый рефакторинг, так как наше приложение растет, и мы хотели бы, чтобы оно было умным и удобным.

6. Рефакторинг

Теперь мы изменим нашу службу входа в систему. Мы хотели бы обрабатывать все функции, которые применяются к пользователю, в одном сервисе, поэтому я хотел бы переименовать его в UserService. Вы можете сделать это вручную или использовать любой специальный плагин для вашего редактора кода. Не забудьте проверить все импорты, когда это будет сделано.

Еще одна вещь, которую нам нужно изменить, это наш файл proxy.conf.json.

Давайте откроем его и изменим ваш код на тот, что здесь.

{
  "/api/*": {
    "target": "http://localhost:8888",
    "secure": false,
    "logLevel": "debug",
    "changeOrigin": true,
    "pathRewrite": {
      "^/api": ""
    }
  }
}

Когда вы закончите это, перезапустите приложение с помощью ng serve.

7. Функция register()

Мы можем начать создавать функцию регистрации в UserService. Давайте откроем только что отредактированный файл user.service.ts и начнем с изменения значения URL.

export class UserService {
  url: any = 'http://localhost:4200/api/';
  ...
}

Нам нужно внести изменения в нашу функцию login(), поскольку мы изменили URL-адрес, чтобы его можно было использовать повторно.

login(Username: string, Password: string): any {
    this.http.post(`${this.url}login`, { Username, Password }, httpOptions).toPromise().then((res: any) => {
        ...
    })
}

И, наконец, мы можем создать нашу функцию register(), которая будет очень похожа на функции login().

register(Username: string, Email: string, Password: string): any {
    this.http.post(`${this.url}register`, { Username, Email, Password }).toPromise().then((res: any) => {
      if (res && res.jwt) {
        sessionStorage.setItem('jwt', res.jwt);
        this.errorSubject.next(null);
        if (res.data) {
          this.userSubject.next(res.data);
        }
        this.router.navigateByUrl('dashboard');
      } else if (res.Message) {
        this.errorSubject.next(res.Message);
      }
    });
  }

Здорово! Пришло время реализовать наш вызов API в файле register.component.ts. Также давайте реализуем прослушивание ошибки в методе ngOnInit().

export class RegisterComponent implements OnInit {
  error = null;
  ...
  ngOnInit(): void {
    this.userService
      .errorSubject
      .subscribe(errorMessage => {
        this.error = errorMessage;
      });
  }
  onRegister(): void {
    if (this.valid.username && this.valid.email && this.valid.password) {
      this.userService
        .register(this.username, this.email, this.password);
    }
  }
}

Отлично, давайте добавим функцию onRegister() в наш шаблон register.component.html.

<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline" type="button" (click)="onRegister()">
  Sign In
</button>

Отлично, пришло время для последнего шага — добавления уведомления об ошибке в шаблон.

8. Уведомление об ошибке

В наш файл register.component.html мы собираемся добавить уведомление, как в форме входа, которое будет отображаться, если будет возвращена ошибка.

<div *ngIf="error" class="notification bg-indigo-900 text-center py-4 lg:px-4">
  <div class="p-2 bg-indigo-800 items-center text-indigo-100 leading-none lg:rounded-full flex lg:inline-flex" role="alert">
    <span class="flex rounded-full bg-indigo-500 uppercase px-2 py-1 text-xs font-bold mr-3">ERROR</span>
    <span class="font-semibold mr-2 text-left flex-auto">{{error}}</span>
  </div>
</div>

И вуаля! Регистрация готова, и вы можете протестировать ее!
Не забудьте запустить серверную часть.

Вывод

Если вам не удалось правильно получить код и возникли какие-либо проблемы, помните, что вы можете проверить код для этого урока в нашем репозитории Github.

Угловой курс — Урок 4 — Код

Кроме того, не забывайте следить за нашими бэкэнд-курсами Курс Golang и Курс Node.js, где новые функции появляются каждую неделю, чтобы мы могли продолжать внедрять их на фронтенде.

На следующих уроках мы будем запрашивать пользовательские данные из бэкенда и создавать денежные переводы.

Оставайтесь с нами и дайте нам знать в комментариях, что еще вы хотели бы построить!

Спасибо за чтение,

Анна из Дуомли