Banx. Проектирование регистрации на Angular

Aleksandr Serenko
F.A.F.N.U.R
Published in
13 min readJan 23, 2022
Проектирование регистрации на Angular

Данная статья посвящена проектированию процесса регистрации и ее реализации. В статье будет разобран механизм регистрации, а также приведены и разобраны основные части системы.

Так как статья является частью серии статей, можно ознакомиться с предыдущими статьями, но это не обязательно, так как все что описано в статье, можно реализовать на основе нового приложения angular (без использования монорепозитория Nx).

О процессе регистрации

В финтехе процесс регистрации немного сложнее по сравнению с обычными сайтами или сервисами.

Особенно трепетно к регистрации подходят альтернативные лендеры (МФО), банковский сектор (ипотечное кредитование, потребительское кредитование) и различные финансовые биржи (фондовые биржи, криптовалютные биржы).

Есть две ключевые задачи, которые преследуют подобного рода сервисы:

  • идентифицировать пользователя;
  • на основании предоставленной информации определить доступные лимиты пользователя.

Грубо процесс регистрации можно поделить на 3 части:

  • Заполнение опросника (персональные данные, сведения о работе и доходе).
  • Сбор дополнительных данных (информация об устройстве, геопозиция, ip, фото клиента, соц. сети).
  • Анализ собранных данных и принятие решения о клиенте.

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

Реализуем следующий сценарий регистрации. Пользователь:

  1. Заходит в приложение и начинает процесс регистрации.
  2. Заполняет анкету.
  3. Разрешает доступ к получению информации об устройстве и положению.
  4. Добавляет свои социальные сети.
  5. Дожидается принятия решения.
  6. Получает результат.

Пользователь может получить либо одобрение, либо отказ. В случае одобрения пользователь становится клиентом, и может пользоваться услугами иначе, пользователь может попробовать снова зарегистрироваться, в надежде получить одобрение.

Реализация регистрации

Сценарий регистрации:

  • Пользователь заходит на страницу регистрации и получает processId или использует ранее полученный.
  • Далее пользователь шаг за шагом проходит регистрацию, указывая требуемые данные.

Будем реализовывать регистрацию исходя из двух постулатов:

  • необходимы повторные регистрации;
  • регистрация может меняться от страны к стране*.

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

Второй постулат говорит о том, что процесс регистрации в каждой стране может быть свой. Для унификации процесса, выделим процесс регистрации (RegistrationProcess) как самостоятельную сущность, которая будет заимплементирована в каждой стране по своему.

RegistrationProcess будет включать в себя:

  • processId — id регистрации.
  • finished — признак того, что регистрация завершена
  • steps — массив шагов регистрации
  • user — id клиента

Отметим, так как используется JWT в качестве авторизации, а во время регистрации его нет, то тогда для того чтобы пройти регистрацию нужен ID, который будет выполнять роль сессии.

Также если пользователь завершил шаги регистрации, которые позволяют создать клиента, то в RegistrationProcess будет содержать id клиента.

Шаги регистрации состоят из:

  • id — ID шага
  • name — названия шага
  • finishedAt — дата завершения шага.

Процесс регистрации будет включать следующие шаги:

  • Form — шаг заполнения анкеты;
  • Data — шаг сбора данных об устройстве клиента;
  • Social — шаг привязки социальных сетей и сервисов;
  • User — шаг создания клиента (в UI будет показываться спиннер);
  • Decision — шаг принятия решения о клиенте;
  • Conversion — шаг отправки данных в аналитические системы и сервисы (google, yandex);
  • Finish — шаг завершения регистрации.

Имея абстракции для процесса регистрации и шагов регистрации можно описать API:

### Get process
GET http://localhost:3000/registration/process
Content-Type: application/json
Accept: application/json

В результате должно получится следующее:

{
"processId": "d769903f-a163-4d2f-99c2-8f2a912a9e96",
"finished": false,
"steps": [
{
"id": 1,
"name": "form",
"finishedAt": null
},
{
"id": 2,
"name": "data",
"finishedAt": null
},
{
"id": 3,
"name": "social",
"finishedAt": null
},
{
"id": 4,
"name": "user",
"finishedAt": null
},
{
"id": 5,
"name": "decision",
"finishedAt": null
},
{
"id": 6,
"name": "conversion",
"finishedAt": null
},
{
"id": 7,
"name": "finish",
"finishedAt": null
}
],
"user": null
}

Так как в качестве бекенда используется nestjs, приведем простенькую реализацию процесса регистрации:

Как видно из реализации, для работы с базой используется TypeORM, которая была настроена ранее в статье — Создание API NestJS+TypeOrm+Mariadb.

Сущность процесса регистрации представлена следующим образом:

import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';

export interface FinishedStep<T extends string = string> {
name: T;
finishedAt: string;
}

@Entity({
name: 'registration_process',
})
export class RegistrationProcessEntity {
@PrimaryGeneratedColumn()
id!: number;

@Column({ type: 'tinytext', unique: true })
process!: string;

@Column({ type: 'tinytext', nullable: true })
user!: number | null;

@Column({ type: 'json', nullable: true, name: 'finished_steps' })
finishedSteps!: Record<string, FinishedStep>;

@Column({ type: 'datetime', nullable: true, name: 'finished_at' })
finishedAt!: string;

@CreateDateColumn({ name: 'created_at' })
createdAt!: string;

@UpdateDateColumn({ name: 'updated_at', nullable: true })
updatedAt!: string;
}

Как видно из сущности, в базе будут хранится только завершенные шаги.

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

Для получения процесса регистрации используется метод getProcess:

  async getProcess(processId?: string) {
const processPrevious = processId
? await this.repo.findOne({ process: processId })
: undefined;

const process = !processId || !processPrevious
? await this.repo.save(this.repo.create({ process: uuidv4() }))
: processPrevious;

return {
processId: process.process,
finished: !!process?.finishedAt,
steps: this.getSteps(process),
user: process.user ?? null,
};
}

Если процесса регистрации не существует, то тогда генерируется новый, иначе получаем данные из базы.

В контроллере просто вызываем метод сервиса получения процесса регистрации:

@Controller()
export class RegistrationProcessController {
constructor(private readonly registrationProcessService: RegistrationProcessService) {}

@Get('registration/:processId?')
async getProcess(@Param() params: { processId?: string }): Promise<RegistrationProcessDto> {
return this.registrationProcessService.getProcess(params?.processId);
}
}

Реализуем получения процесса регистрации в Angular.

Создадим RegistrationProcessApiService, который будет запрашивать процесс регистрации:

Функция castRegistrationProcess добавляет особую фронтовую функциональность в виде подшагов.

export function castRegistrationProcess(payload: RegistrationProcessDto): RegistrationProcess {
return {
processId: payload.processId,
finished: payload.finished,
steps: Object.values(payload.steps).map((step) => ({ ...step, subStep: null })),
user: payload.user,
};
}

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

Для работы с процессом шагов создадим RegistrationProcessStateModule :

Данный модуль подключит сервис работы с API, также реализует Ngrx state для работы с процессом регистрации.

RegistrationProcessState включит в себя следующие redux экшены:

Здесь можно выделить следующие цепочки:

  • load (success|failure)— загрузка процесса регистрации;
  • selectStep(success|failure) — определение шага регистрации;
  • selectSubStep(success|falire) — определение подшага регистрации;
  • navigateToNextStep — обновление процесса регистрации посредством загрузки шагов регистрации;
  • restartProcess — сброс процесса регистрации (эквивалентно началу новой регистрации).

Отметим, что после успешной загрузки процесса регистрации идет автоматический запуск цепочки определения шага регистрации. При появлении события navigateToNextStep будет запущена цепочка загрузки процесса регистрации, а после успешного определения шага, будет вызвано событие навигации на конкретный шаг регистрации.

Редьюсер будет следующим:

В данном случае будем отдельно хранить каждый шаг в хранилище с помощью @ngrx/entity (правда не понятно зачем).

Сам state помимо шагов включит следующие поля:

export interface RegistrationProcessState extends EntityState<RegistrationStepEntity> {
readonly selected: number | null;
readonly loaded: boolean;
readonly subStep: string | null;
readonly processId: string | null;
readonly finished: boolean;
}
  • selected — id выбранного шага;
  • loaded — признак того, что процесс регистрации был загружен;
  • subStep — активный под шаг;
  • processId — id регистрации;
  • finished — признак того, что регистрация завершена.

Реализация цепочек событий однообразна:

Разберем цепочку загрузки процесса регистрации:

loadProcess$ = createEffect(() =>
this.actions$.pipe(
ofType(RegistrationProcessActions.loadProcess),
withLatestFrom(this.store.pipe(select(RegistrationProcessSelectors.selectProcessId))),
fetch({
id: () => 'registration-process-load',
run: (action, processId: string | null) =>
this.platformService.isBrowser
? this.registrationProcessApiService
.load(processId)
.pipe(map((payload) => RegistrationProcessActions.loadProcessSuccess({ payload })))
: undefined,
onError: (action, error) =>
this.loggerService.logEffect({ context: { action, error } }, RegistrationProcessActions.loadProcessFailure({ payload: error })),
})
)
);

В данном случае, получаем из хранилища processId. Вызываем метод load(), передав туда processId. В случае успеха вызываем событие loadProcessSuccess(), иначе loadProcessFailure().

Для упрощения работы с хранилищем, добавим фасад RegistrationProcessFacade:

Фасад предоставит доступ к свойствам state, а также позволит вызывать требуемые события, такие как загрузка процесса регистрации или его сброса.

Шаг заполнения анкеты

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

Одна из главных частей регистрации — это опросник. Составим следующий опросник:

Так как форма содержит более 30 полей, разобьем форму на несколько шагов:

  • персональные данные (personal) — ФИО, паспортные данные, телефон и почта;
  • информация о семье (family) — семейный статус, адрес, дополнительный контакт;
  • сведения о занятости (employment) — занятость (работа, учеба, пенсия);
  • дополнительные вопросы (additional)— дополнительные вопросы.

В данном случае получим следующие шаги в форме заполнения опросника:

export enum RegistrationFormSubSteps {
Personal = 'personal',
Sms = 'sms',
Family = 'family',
Employment = 'employment',
Additional = 'additional',
}

Реализуем API для сохранения и валидации формы.

Сущность формы будет следующей:

Как видно из примера, сущность содержит настройки для ORM сопоставления и создания соответствующих полей в базе данных.

Для валидации данных создадим DTO, в котором определим валидаторы:

В данном случае, context в аннотациях позволяет передавать контекст, для валидации, groups определяет для какого шага, нужно проверить поля.

Например, следующая аннотация добавляет проверку на то, что поле должно быть Integer и должно быть на шаге дополнительных вопросов:

@IsInt({
context: { errorCode: RegistrationErrorCode.IsInt },
groups: [RegistrationFormSubSteps.Additional],
})
minimalDesiredAmount!: number;

Для всех типов ошибок определены соответствующие типы:

Для того, чтобы заставить nestjs выводить валидационные ошибки в нужном формате, добавим handler:

Когда происходит ошибка для конкретного поля, перебираются все ошибки и формируется массив ошибок для поля. В результате получится ответ вида:

POST http://localhost:3000/registration/f46a708b-6953-4272-bc3c-eb4001dc82d0/form/validate/personal
Content-Type: application/json
Accept: application/json

{
"lastName": "Ivan",
"middleName": "Ivanovich",
"birthdate": "2021-11-04T08:14:38.000Z"
}

Ответ:

{
"firstName": {
"isLength": 1001,
"isNotEmpty": 1000
},
"gender": {
"isEnum": 1005,
"isNotEmpty": 1000
},
"mobilePhone": {
"isLength": 1001,
"isNotEmpty": 1000
},
"email": {
"isEmail": 1004,
"isNotEmpty": 1000
},
"agreement": {
"isBoolean": 1002,
"isNotEmpty": 1000
},
"passportSeriesNumber": {
"isLength": 1001,
"isNotEmpty": 1000
},
"passportIssueCode": {
"isLength": 1001,
"isNotEmpty": 1000
},
"passportIssueName": {
"isLength": 1001,
"isNotEmpty": 1000
},
"passportIssueDate": {
"isNotEmpty": 1000
},
"passportBirthplace": {
"isLength": 1001,
"isNotEmpty": 1000
}
}

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

Реализация в данном случае избыточна и можно было бы обойтись простым массивом строк ошибок для каждого поля:

"firstName": ['isLength', 'isNotEmpty'],

Если форма валидна то ее необходимо сохранить. Для записи в базу будем также использовать TypeORM:

  • getForm — метод по processId возвращает заполненную форму
  • saveForm — метод сохраняет форму по processId.

Отметим, что в базе будем хранить вводимые значения, то есть часть полей формы могут быть не заполнены и поэтому в интерфейсах и имплементации типы полей содержат NULL. Но это только со стороны backend’а.

Для интеграции валидации и сохранения данных создадим контроллер, который все свяжет вместе:

В примере можно наблюдать как для трансформации ошибок используется pipe:

@Post(`registration/:process/form/validate/personal`)
@UsePipes(
new ValidationPipe({
transform: true,
groups: [RegistrationFormSubSteps.Personal],
exceptionFactory: (validationErrors) => registrationFormExceptionFactory(validationErrors),
})
)
async validateFormPersonal(@Param() params: RegistrationFormParams, @Body() form: RegistrationFormDto): Promise<void> {
return this.registrationFormService.saveForm(params.process, form).then(() => {
if (form.mobilePhone) {
return this.registrationOtpService.create(params.process, form.mobilePhone).then();
}
});
}

Аналогично работают и все остальные методы для валидации данных.

Подключим все в модуле:

Теперь Angular может использовать следующее API:

export const REGISTRATION_FORM_API_ROUTES = {
create: (processId: string): string => `/registration/${processId}/form/create`,
load: (processId: string): string => `/registration/${processId}/form`,
validate: (processId: string, step?: string): string => `/registration/${processId}/form/validate${step ? '/' + step : ''}`,
validateUnique: (processId: string): string => `/registration/${processId}/form/validate-unique`,
};
  • create — метод для завершения заполнения формы;
  • load — получение данных, которые были сохранены ранее на сервере;
  • validate — валидация формы;
  • validateUnique — метод для валидации уникальных полей.

Реализуем RegistrationFormApiService, который сможет обращаться к выше описанным endpoint’aм.

Добавим сопутствующие интерфейсы для работы с API формы регистрации:

Реализуем RegistrationFormState:

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

Как видно из примера, есть 4 цепочки событий:

  • Загрузка сохраненной формы (loadForm);
  • Валидация формы (validateForm);
  • Валидация поля (validateField);
  • Завершение заполнения формы (createForm).

Также есть несколько одиночных событий:

  • init + restore — событие, которое возьмет данные из локального хранилища и запишет в State. Это необходимо для того, чтобы данные которые не были сохранены на сервере не потерялись и пользователю не пришлось еще раз их заполнять.
  • updateForm — событие обновление формы, которая храниться в state.

Редьюсер тривиален:

В хранилище храним саму форму и ее состояния изменения:

export interface RegistrationFormState {
form: RegistrationForm | null;
formLoading: boolean;
formLoaded: boolean;
formCreating: boolean;
formValidating: boolean;
}

Цепочки событий, как и в случае с процессом регистрации реализуются с помощью эффектов:

Из важного, что при инициализации формы регистрации, берутся данные из localStorage и добавляются в state:

init$ = createEffect(() =>
this.actions$.pipe(
ofType(RegistrationFormActions.init),
withLatestFrom(this.localAsyncStorage.getItem<RegistrationForm | null>(RegistrationFormKeys.Form).pipe(take(1))),
fetch({
id: () => 'registration-form-init',
run: (action, form: RegistrationForm | null) => RegistrationFormActions.restore({ payload: { form } }),
onError: (action, error) => this.loggerService.logEffect({ context: { action, error } }),
})
)
);

Также при завершении заполнения опросника происходит вызов загрузки процесса регистрации и навигации к следующему шагу регистрации:

createFormSuccess$ = createEffect(() =>
this.actions$.pipe(
ofType(RegistrationFormActions.createFormSuccess),
fetch({
id: () => 'registration-create-form-success',
run: () => navigateToNextStep(),
onError: (action, error) => this.loggerService.logEffect({ context: { action, error } }),
})
)
);

Фасад также предоставляет доступ к свойствам и запуску событий:

Создания макета для шагов регистрации

Так как API готово и есть state, который будет запрашивать и передавать данные, создадим лейаут.

Так как макет будет общим для всех шагов регистрации, разместим его в regisration/ui/layout.

Так как шаблон для страниц регистрации не содержит уникальной логики, то его можно подключить на уровне навигации.

Так как под шаги регистрации будут иметь схожую функциональность — отображение формы и кнопок вперед, назад, то создадим компонент, который назовем — RegistrationCard, разместив его в regustration/ui/card.

Добавим вспомогательные компоненты для кнопок, заголовка и контента:

Для простоты использования подключим дополнительные компоненты и экспортируем их:

Также в процессе регистрации может возникнуть ошибка — недоступно API, ошибка сервера и прочее.

Создадим компонент, который будет показывать сообщение, который будет просить пользователя обратиться в службу поддержки:

Как и предыдущие общие (shared) компоненты, разместим компонент ошибки в registration/ui/step-error.

Создания страниц регистрации

Теперь создадим и подключим страницы регистрации.

Для этого будем придерживаться следующей структуры:

/registration/${step}/${subStep}

В данном случае, для регистрации получим следующие пути:

/registration - главный путь регистрации. При переходе на него идет редирект на актуальный шаг регистрации
/registration/form - шаг опросника
/registration/form/personal - под шаг опросника
/registration/form/sms - под шаг опросника
/registration/form/family - под шаг опросника
/registration/form/employment - под шаг опросника
/registration/form/additional - под шаг опросника
/registration/data - шаг сбора данных об устройстве, геолокации
/registation/social - шаг социальных сетей
/registration/user - шаг создания клиента
/registration/decision -шаг принятия решения о клиенте
/registration/analytics - шаг отправки аналитики во внешние системы
/registration/finish - шаг завершения регистрации

Когда пользователь закончит регистрацию, то будет направлен на одну из следующих страниц:

/customers/approve - страница с одобрением
/customers/reject - страница с отказом

Создадим главный файл с путями регистрации:

В реализации определяем, что только неавторизованные пользователи могут попасть на страницу регистрации (AuthGuard):

{
path: '',
component: RegistrationLayoutComponent,
canActivate: [AuthGuard, RegistrationProcessLoadGuard],
children: []
}

RegistrationProcessLoadGuard ожидает загрузки процесса регистрации и блокирует отображение, пока процесс не загружен:

Далее, каждый шаг регистрации имеет RegistrationProcessGuard, который проверяет является ли данный шаг текущим и можно ли пускать пользователя на данный шаг:

Рассмотрим на шаге принятия решения:

{
path: NAVIGATION_PATHS.registrationDecision,
canActivate: [RegistrationProcessGuard],
data: {
step: RegistrationStepType.Decision,
},
loadChildren: () => import('@banx/registration/decision/page').then((modules) => modules.RegistrationDecisionPageModule),
},

Route имеет data, в который передается шаг Decision, который должен быть выбран.

Отметим, что NAVIGATION_PATHS это всего лишь константа, которая содержит все пути:

export interface NavigationPaths {
...
// Registration
registration: string;
registrationForm: string;
registrationFormPersonal: string;
registrationFormSms: string;
registrationFormFamily: string;
registrationFormEmployment: string;
registrationFormAdditional: string;
registrationData: stringRegistrationStepType.;
registrationSocial: string;
registrationDecision: string;
registrationUser: string;
registrationConversion: string;
registrationFinish: string;
registrationRestart: string;
}
export const NAVIGATION_PATHS: NavigationPaths = {
registration: 'registration',
registrationForm: 'registration/form',
registrationFormFamily: 'registration/form/family',
registrationFormPersonal: 'registration/form/personal',
registrationFormSms: 'registration/form/sms',
registrationFormEmployment: 'registration/form/employment',
registrationFormAdditional: 'registration/form/additional',
registrationData: 'registration/data',
registrationSocial: 'registration/social',
registrationDecision: 'registration/decision',
registrationUser: 'registration/user',
registrationConversion: 'registration/conversion',
registrationFinish: 'registration/finish',
registrationRestart: 'registration/restart',
}

Так как шаг опросника имеет под шаги, создадим навигацию для подшагов:

Из примера видно, что каждый под шаг также проверяется соответствующим гуардом — RegistrationFormGuard:

В данном случае шаг является завершенным, если заполнены требуемые поля.

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

const check: ((form: Record<string, any>) => boolean) | null = typeof route.data['check'] === 'function' ? route.data['check'] : null;

Благодаря этому можно проверить доступность не глобальных полей опросника:

{
path: RegistrationFormSubSteps.Sms,
canActivate: [RegistrationFormGuard],
data: {
subStep: RegistrationFormSubSteps.Sms,
check: (form: Record<string, any>) =>
[
RussianRegistrationFormField.PassportSeriesNumber,
RussianRegistrationFormField.PassportIssueCode,
RussianRegistrationFormField.PassportIssueName,
RussianRegistrationFormField.PassportIssueDate,
RussianRegistrationFormField.PassportBirthplace,
].every((field) => form[field] != null),
},
loadChildren: () => import('@banx/registration/form/sms/page').then((modules) => modules.RegistrationFormSmsPageModule),
},

Разработка опросника

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

Под шаг опросника будет включать:

  • макет;
  • форму;
  • кнопки вперед, назад.

Добавим макет, который будет вызывать загрузку сохраненных значений опросника.

Добавим pipe, который будет отображать валидационные ошибки:

Добавим компонент, который возьмет на себя роль навигации между шагами:

В примере видно, что компонент принимает параметры для отображения кнопок вперед/назад.

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

Создание полей опросника

На примере ФИО покажем создание полей формы.

Создадим компонент и добавим логику:

Как видно, для отображения используется angular material. Также для отслеживания вводимых значений используются директивы трекера:

<input
...
matInput
banxInputTrack
[trackId]="id"
...
/>

Подробнее о трекере можно почитать в статье — Banx. Создание трекера событий пользователя в Angular.

Аналогично создадим все остальные поля в registration/form/ui/fields. Поля специфичные только для РФ(паспортные данные) сохраним в russain/registration/form/ui/fields.

Тогда форма регистрации будет следующей:

Форма создается при создании компонента:

readonly form = new FormGroup({
[RegistrationFormField.LastName]: new FormControl(null, [Validators.required, russianWordValidator]),
[RegistrationFormField.FirstName]: new FormControl(null, [Validators.required, russianWordValidator]),
...
});

Вся логика с формой вынесена в компонент — RegistrationFormCardComponent , который был разобран ранее.

Аналогично создадим другие под шаги опросника.

Создание других шагов регистрации

Все остальные шаги регистрации будут достаточно просты, где нужно будет либо один раз нажать продолжить, либо вообще ничего не делать, а просто ждать завершения регистрации.

Добавим шаг со сбором данных об устройстве.

Сначала добавим бекенд:

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

Добавим API для сохранения завершения шага:

Добавим state:

Создадим страницу:

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

Как данные будут собраны, пользователь будет перенаправлен на следующий шаг регистрации.

Аналогично доделаем оставшиеся шаги регистрации.

Отмечу, что вся реализация приведена в репозитории.

Запуск регистрации

После того как были настроены все шаги регистрации, то запустим проект и пройдем регистрацию.

Заполним опросник:

Пройдем остальные шаги регистрации:

После завершения, мы увидели страницу одобрения.

Резюме

В данной статье разобрали пример реализации сложной регистрации на Angular и NestJS.

Ключевые моменты, которые были рассмотрены и реализованы в статье:

  • Реализация базового функционала с помощью Nestjs, такого как сохранение данных, валидация форм, предоставление и управление процессом регистрации.
  • Обзор процесса регистрации, который представляет собой вектор шагов регистрации.
  • Реализация разбиения опросника на несколько раздельных форм.
  • Настройка доступа к шагам регистрации.
  • Обзор структуры регистрации и ее масштабирование в рамках монорепозитория NX.

Итоговая структура регистрации:

Часть, которая отвечает только за конкретную страну (в частности россия):

Как видно из структуры, специфика страны локализирована и затрагивает очень маленькую часть системы.

Ссылки

Предыдущая статья — Реализация отпечатков пальцев браузера в Angular.

Все исходники находятся на github, в репозитории:

Для того, чтобы посмотреть состояние проекта на момент написания статьи, нужно выбрать соответствующий тег — registration.

Подписывайтесь на блог, чтобы не пропустить новые статьи про Angular, и веб-разработку. Medium | Telegram| VK | FB | Tw | Inst | Ln

--

--

Aleksandr Serenko
F.A.F.N.U.R

Senior Front-end Developer, Angular evangelist, Nx apologist, NodeJS warlock