Spec-Zone.ru › Angular

Асинхронные операции

Для некоторых проверок требуются данные из внешних источников, например из API серверной части или сторонних сервисов. Signal Forms предоставляет две функции для асинхронной проверки: validateHttp() для проверки на основе HTTP и validateAsync() для пользовательской проверки на основе ресурсов.

Когда использовать асинхронную проверку

Используйте асинхронную проверку, если для вашей логики проверки требуются внешние данные. Вот несколько распространённых примеров:

  • Проверка уникальности — убедитесь, что имя пользователя или адрес электронной почты ещё не используются
  • Поиск в базе данных — проверьте значения по данным на сервере
  • Проверка через внешний API — проверьте адреса, налоговые идентификаторы и другие данные с помощью сторонних сервисов
  • Бизнес-правила на стороне сервера — применяйте правила проверки, которые может проверить только сервер

Не используйте асинхронную проверку для проверок, которые можно выполнить синхронно на клиенте. Для проверки формата и статических правил используйте синхронные правила проверки, такие как pattern(), email() или validate().

Как работает асинхронная проверка

Асинхронная проверка запускается только после успешного прохождения всех синхронных проверок. Пока выполняется проверка, сигнал pending() поля возвращает true. Проверка может назначать ошибки определённым полям, а ожидающие запросы автоматически отменяются при изменении значений полей.

Рассмотрим пример проверки доступности имени пользователя:

import {Component, signal} from '@angular/core';
import {form, validateHttp, FormField} from '@angular/forms/signals';

@Component({
  selector: 'app-registration',
  imports: [FormField],
  template: `
    <form>
      <label>
        Username:
        <input [formField]="registrationForm.username" />
      </label>

      @if (registrationForm.username().pending()) {
        <span class="checking">Checking availability...</span>
      }
      @if (registrationForm.username().invalid()) {
        @for (error of registrationForm.username().errors(); track $index) {
          <span class="error">{{ error.message }}</span>
        }
      }
    </form>
  `,
})
export class Registration {
  registrationModel = signal({username: ''});

  registrationForm = form(this.registrationModel, (schemaPath) => {
    validateHttp(schemaPath.username, {
      request: ({value}) => {
        const username = value();
        return username ? `/api/users/check?username=${username}` : undefined;
      },
      onSuccess: (response: {available: boolean}) => {
        return response.available
          ? null
          : {
              kind: 'usernameTaken',
              message: 'Username is already taken',
            };
      },
      onError: (error) => {
        console.error('Validation request failed:', error);
        return {
          kind: 'serverError',
          message: 'Could not verify username availability',
        };
      },
    });
  });
}

Процесс проверки выглядит так:

  1. Пользователь вводит значение
  2. Сначала выполняются синхронные правила проверки
  3. Если синхронная проверка не пройдена, асинхронная проверка не запускается
  4. Если синхронная проверка пройдена, запускается асинхронная проверка и pending() становится true
  5. Запрос завершается, и pending() становится false
  6. Ошибки обновляются в соответствии с ответом

Проверка по HTTP с помощью validateHttp()

Функция validateHttp() предоставляет наиболее распространённый способ асинхронной проверки. Используйте её, если нужно выполнить проверку с помощью REST API или любой конечной точки HTTP.

Функция запроса

Функция request возвращает либо строку URL, либо объект HttpResourceRequest. Чтобы пропустить проверку, верните undefined:

import {Component, signal} from '@angular/core';
import {form, validateHttp, FormField} from '@angular/forms/signals';

@Component({
  selector: 'app-registration',
  imports: [FormField],
  template: `...`,
})
export class Registration {
  registrationModel = signal({username: ''});

  // Cache usernames that passed validation
  private validatedUsernames = new Set<string>();

  registrationForm = form(this.registrationModel, (schemaPath) => {
    validateHttp(schemaPath.username, {
      request: ({value}) => {
        const username = value();
        // Skip HTTP request if already validated
        if (this.validatedUsernames.has(username)) return undefined;

        return `/api/users/check?username=${username}`;
      },
      onSuccess: (response: {available: boolean}, {value}) => {
        if (response.available) {
          // Cache successful validations
          this.validatedUsernames.add(value());
          return null;
        }
        return {
          kind: 'usernameTaken',
          message: 'Username is already taken',
        };
      },
      onError: () => ({
        kind: 'serverError',
        message: 'Could not verify username',
      }),
    });
  });
}

Для запросов POST или пользовательских заголовков верните объект HttpResourceRequest:

request: ({value}) => ({
  url: '/api/validate',
  method: 'POST',
  body: {username: value()},
}) // prettier-ignore

Обработчики успешного выполнения и ошибок

Функция onSuccess получает HTTP-ответ и возвращает ошибки проверки или undefined для допустимых значений:

onSuccess: (response: { valid: boolean; message?: string }) => {
  if (response.valid) return undefined;

  return {
    kind: 'invalid',
    message: response.message || 'Validation failed',
  };
} // prettier-ignore

При необходимости верните несколько ошибок:

onSuccess: (response: { usernameTaken: boolean; profanity: boolean }) => {
  const errors = [];
  if (response.usernameTaken) {
    errors.push({
      kind: 'usernameTaken',
      message: 'Username taken',
    });
  }
  if (response.profanity) {
    errors.push({
      kind: 'profanity',
      message: 'Username contains inappropriate content',
    });
  }
  return errors.length > 0 ? errors : undefined;
} // prettier-ignore

Функция onError обрабатывает сбои запросов, например сетевые ошибки или ошибки HTTP:

onError: (error) => {
  console.error('Validation request failed:', error);
  return {
    kind: 'serverError',
    message: 'Could not verify. Please try again later.',
  };
} // prettier-ignore

Параметры HTTP

Настройте HTTP-запрос с помощью параметра options:

import {HttpHeaders} from '@angular/common/http';

validateHttp(schemaPath.field, {
  request: ({value}) => `/api/validate?value=${value()}`,
  options: {
    headers: new HttpHeaders({
      Authorization: 'Bearer token',
    }),
    timeout: 5000,
  },
  onSuccess: (response: {valid: boolean}) =>
    response.valid
      ? null
      : {
          kind: 'invalid',
          message: 'Invalid value',
        },
  onError: () => ({
    kind: 'requestFailed',
    message: 'Unable to reach server to validate.',
  }),
});

СОВЕТ: Полный список доступных параметров см. в документации API httpResource.

Пользовательская асинхронная проверка с помощью validateAsync()

В большинстве приложений для асинхронной проверки следует использовать validateHttp(). Она обрабатывает HTTP-запросы с минимальной настройкой и подходит для большинства случаев.

validateAsync() — это API более низкого уровня, который напрямую предоставляет примитив resource из Angular. Он обеспечивает полный контроль, но требует больше кода и знакомства с API resource в Angular.

Используйте validateAsync(), только если validateHttp() не подходит для ваших задач. Например:

  • Проверка не по HTTP — подключения WebSocket, поиск в IndexedDB или вычисления в Web Worker
  • Пользовательские стратегии кэширования — кэширование с учётом особенностей приложения, выходящее за рамки простой мемоизации
  • Сложная логика повторных попыток — пользовательские стратегии задержки или условные повторы
  • Прямой доступ к ресурсу — если нужен полный жизненный цикл ресурса

Создание пользовательского правила проверки

Для функции validateAsync() требуются четыре свойства: params, factory, onSuccess и onError. Функция params возвращает параметры для ресурса, а factory создаёт ресурс:

import {Component, inject, signal, resource, Signal} from '@angular/core';
import {form, validateAsync, FormField} from '@angular/forms/signals';
import {UsernameValidator} from './username-validator';

@Component({
  selector: 'app-registration',
  imports: [FormField],
  template: `...`,
})
export class Registration {
  registrationModel = signal({username: ''});

  private usernameValidator = inject(UsernameValidator);
  private cache = new Map<string, {available: boolean}>();

  // Custom resource factory with caching
  createUsernameResource = (usernameSignal: Signal<string | undefined>) => {
    return resource({
      params: () => usernameSignal(),
      loader: async ({params: username}) => {
        if (!username) return undefined;

        // Check cache first
        const cached = this.cache.get(username);
        if (cached !== undefined) return cached;

        // Use injected service for validation
        const result = await this.usernameValidator.checkAvailability(username);

        // Cache result
        this.cache.set(username, result);
        return result;
      },
    });
  };

  registrationForm = form(this.registrationModel, (schemaPath) => {
    validateAsync(schemaPath.username, {
      params: ({value}) => {
        const username = value();
        return username.length >= 3 ? username : undefined!;
      },
      factory: this.createUsernameResource,
      onSuccess: (result) => {
        return result?.available
          ? null
          : {
              kind: 'usernameTaken',
              message: 'Username taken',
            };
      },
      onError: (error) => {
        console.error('Validation failed:', error);
        return {
          kind: 'serverError',
          message: 'Could not verify username',
        };
      },
    });
  });
}

Функция params запускается при каждом изменении значения. Чтобы пропустить проверку, верните undefined. Функция factory запускается один раз при настройке и получает параметры в виде сигнала. Ресурс автоматически обновляется при изменении параметров.

Использование сервисов на основе Observable

Если в приложении уже есть сервисы, возвращающие Observable, используйте rxResource из @angular/core/rxjs-interop:

import {Component, inject, signal, Signal} from '@angular/core';
import {rxResource} from '@angular/core/rxjs-interop';
import {form, validateAsync, FormField} from '@angular/forms/signals';
import {UsernameService} from './username-service';

@Component({
  selector: 'app-registration',
  imports: [FormField],
  template: `...`,
})
export class Registration {
  registrationModel = signal({username: ''});

  private usernameService = inject(UsernameService);

  private createUsernameResource = (usernameSignal: Signal<string | undefined>) => {
    return rxResource({
      params: () => usernameSignal(),
      stream: ({params: username}) => this.usernameService.checkUsername(username),
    });
  };

  registrationForm = form(this.registrationModel, (schemaPath) => {
    validateAsync(schemaPath.username, {
      params: ({value}) => value(),
      factory: this.createUsernameResource,
      onSuccess: (result) =>
        result?.available ? null : {kind: 'usernameTaken', message: 'Username taken'},
      onError: () => ({
        kind: 'serverError',
        message: 'Could not verify username',
      }),
    });
  });
}

Функция rxResource напрямую работает с Observable и автоматически очищает подписку при изменении значения поля.

Устранение дребезга

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

Добавьте правило debounce в схему, чтобы отложить передачу изменений интерфейса поля формы в модель. В простейшем случае debounce(path, ms) удерживает каждое изменение интерфейса в течение заданного количества миллисекунд, прежде чем записать его в модель. Новое изменение в течение этого интервала сбрасывает таймер.

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

import {Component, signal} from '@angular/core';
import {form, debounce, validateHttp, FormField} from '@angular/forms/signals';

@Component({
  selector: 'app-registration',
  imports: [FormField],
  template: `
    <label>
      Username:
      <input [formField]="registrationForm.username" />
    </label>

    @if (registrationForm.username().pending()) {
      <span class="checking">Checking availability...</span>
    }
  `,
})
export class Registration {
  registrationModel = signal({username: ''});

  registrationForm = form(this.registrationModel, (schemaPath) => {
    // Hold UI updates for 300 ms before writing to the model
    debounce(schemaPath.username, 300);

    // Runs against the debounced model value, not every keystroke
    validateHttp(schemaPath.username, {
      request: ({value}) => {
        const username = value();
        // Skip the request for blank values
        return username ? `/api/users/check?username=${username}` : undefined;
      },
      onSuccess: (response: {available: boolean}) =>
        response.available ? null : {kind: 'usernameTaken', message: 'Username is already taken'},
      onError: () => ({
        kind: 'serverError',
        message: 'Could not verify username availability',
      }),
    });
  });
}

При задержке устранения дребезга в 300 мс модель обновляется и проверяется только после того, как пользователь делает паузу в наборе текста дольше заданного интервала. Например, быстрый ввод «signal forms» запускает один запрос проверки вместо двенадцати.

Состояние touched немедленно обновляет модель

Независимо от длительности задержки устранения дребезга, фреймворк немедленно записывает controlValue() поля в модель, когда поле получает состояние touched. Нативные поля ввода получают это состояние при потере фокуса, поэтому пользователю, который закончил ввод и перешёл к следующему полю, не нужно ждать истечения таймера. Пользовательские элементы управления могут отмечать поле как touched при любом выбранном ими событии.

Обычно это важно при отправке формы. Когда пользователь нажимает кнопку отправки, поле, находящееся в фокусе, теряет фокус, что переводит его в состояние touched и записывает в модель отложенные изменения до запуска обработчика отправки.

Фиксация только при потере фокуса

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

В таких случаях передайте 'blur' вместо длительности, чтобы отложить все обновления до перехода поля в состояние touched:

form(this.registrationModel, (schemaPath) => {
  debounce(schemaPath.username, 'blur');
});

При использовании 'blur' модель сохраняет предыдущее значение, пока пользователь вводит текст. Синхронная и асинхронная проверка, производные сигналы и все реактивные правила, считывающие поле, видят предыдущее значение, пока поле не станет touched. Обычно это происходит, когда пользователь переводит фокус с нативного поля ввода, либо когда пользовательский элемент управления самостоятельно сообщает о переходе в состояние touched.

Пользовательская логика задержки

Если нужную логику задержки нельзя выразить с помощью длительности или 'blur', передайте функцию Debouncer. Функция получает контекст поля и AbortSignal, а возвращает Promise<void>, который разрешается, когда модель должна обновиться:

import {debounce, type Debouncer} from '@angular/forms/signals';

const shorterWhenLonger: Debouncer<string> = ({value}, abortSignal) => {
  // Shorter queries get a longer delay since the user is likely still typing.
  const ms = value().length < 3 ? 500 : 200;
  return new Promise((resolve) => {
    const timeoutId = setTimeout(resolve, ms);
    // Abort fires when this field is touched or its value changes, so the pending timer is cleared
    abortSignal.addEventListener(
      'abort',
      () => {
        clearTimeout(timeoutId);
        resolve();
      },
      {once: true},
    );
  });
};

const registrationForm = form(registrationModel, (schemaPath) => {
  debounce(schemaPath.username, shorterWhenLonger);
});

Событие abortSignal срабатывает, когда поле получает состояние touched или когда его значение меняется до завершения задержки. При отмене разрешите промис, чтобы функция устранения дребезга очистила все ожидающие таймеры. При переходе в состояние touched фреймворк записывает отложенное значение в модель, а при появлении нового значения отбрасывает его. Полную сигнатуру Debouncer см. в справочнике API debounce.

Устранение дребезга для одного асинхронного валидатора

Правило debounce откладывает все реакции на изменение поля: от синхронной проверки и производных сигналов до асинхронной проверки. Однако иногда нужно обратное: недорогие синхронные валидаторы, например required или email, должны запускаться немедленно и сразу предоставлять обратную связь, а только затратный асинхронный вызов должен ожидать, пока пользователь закончит ввод. У параметров validateHttp() и validateAsync() есть собственный параметр debounce, который ограничивает частоту вызова только этого валидатора:

form(this.registrationModel, (schemaPath) => {
  validateHttp(schemaPath.username, {
    // Throttles only this HTTP call
    debounce: 300,
    request: ({value}) => {
      const username = value();
      // Skip the request for blank values
      return username ? `/api/users/check?username=${username}` : undefined;
    },
    onSuccess: (response: {available: boolean}) =>
      response.available ? null : {kind: 'usernameTaken', message: 'Username is already taken'},
    onError: () => ({
      kind: 'serverError',
      message: 'Could not verify username availability',
    }),
  });
});

Модель по-прежнему обновляется при каждом нажатии клавиши, а все остальные правила, связанные с полем, по-прежнему срабатывают немедленно. Задерживается только HTTP-запрос: после каждого изменения выжидается 300 мс бездействия, поэтому запрос отправляется только после паузы в наборе текста.

Выбирайте подходящий уровень с учётом области действия:

Параметр Когда использовать
правило debounce() Синхронная проверка, производное состояние и отправка формы должны ожидать фиксации значения поля. Поле целиком не должно реагировать во время ввода.
validateHttp({ debounce }) или validateAsync({ debounce }) Недорогие синхронные валидаторы должны сразу предоставлять обратную связь, а затратные асинхронные вызовы должны ждать паузы в наборе текста.

Оба параметра принимают длительность в миллисекундах. Их функции обратного вызова для пользовательской настройки задержки различаются: правило уровня формы принимает Debouncer, а параметр уровня валидатора принимает DebounceTimer из @angular/core. Эти две сигнатуры несовместимы.

Компоновка ресурсов в асинхронной проверке с помощью фабрики

Встроенный параметр debounce позволяет ограничивать частоту вызовов, но validateAsync() предоставляет более широкие возможности для композиции — функцию factory. Фабрика получает параметры в виде сигнала и возвращает ресурс. Между этими этапами вы можете компоновать всё необходимое.

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

export class Registration {
  registrationModel = signal({username: ''});
  private usernameValidator = inject(UsernameValidator);

  // Factory function
  checkUsernameAvailable = (username: Signal<string | undefined>) =>
    resource({
      params: () => username(),
      loader: async ({params: name}) => this.usernameValidator.checkAvailability(name),
    });

  registrationForm = form(this.registrationModel, (schemaPath) => {
    validateAsync(schemaPath.username, {
      params: ({value}) => {
        const username = value();
        // Skip validation for short usernames
        return username.length >= 3 ? username : undefined!;
      },
      debounce: 300,
      // Reference to the factory defined above
      factory: this.checkUsernameAvailable,
      onSuccess: (result) =>
        result?.available ? null : {kind: 'usernameTaken', message: 'Username taken'},
      onError: () => ({kind: 'serverError', message: 'Could not verify'}),
    });
  });
}

Функция обратного вызова params возвращает undefined для коротких имён пользователей, указывая, что проверку следует пропустить. При использовании debounce: 300 ресурс ждёт, пока пользователь не сделает паузу в наборе текста на 300 мс, прежде чем обработать каждое изменение. Затем он запускает загрузчик для допустимых имён пользователей и остаётся бездействующим, когда значение после задержки становится undefined.

Сочетание устранения дребезга с дополнительной логикой

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

export class Registration {
  registrationModel = signal({username: ''});
  private usernameValidator = inject(UsernameValidator);

  registrationForm = form(this.registrationModel, (schemaPath) => {
    validateAsync(schemaPath.username, {
      params: ({value}) => {
        const username = value();
        return username.length >= 3 ? username : undefined;
      },
      factory: (username) => {
        // Core primitive: settles 300 ms after the source stops changing
        const debouncedUsername = debounced(username, 300);
        // Cache lives in the factory's closure and persists for the field's lifetime
        const cache = new Map<string, {available: boolean}>();
        return resource({
          // Read from the debounced signal, not the raw one
          params: () => debouncedUsername.value(),
          loader: async ({params: name}) => {
            const cached = cache.get(name);
            if (cached) return cached;

            const result = await this.usernameValidator.checkAvailability(name);
            cache.set(name, result);
            return result;
          },
        });
      },
      onSuccess: (result) =>
        result?.available ? null : {kind: 'usernameTaken', message: 'Username taken'},
      onError: () => ({
        kind: 'serverError',
        message: 'Could not verify username',
      }),
    });
  });
}

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

Состояние ожидания

Во время асинхронной проверки сигнал pending() поля возвращает true. В это время:

  • valid() возвращает false
  • invalid() возвращает false
  • errors() возвращает пустой массив
  • submit() ожидает завершения проверки

Показывайте состояние ожидания в шаблоне, чтобы информировать пользователя:

<input [formField]="loginForm.username" />

@if (loginForm.username().pending()) {
  <span class="loading">Checking availability...</span>
}

@if (loginForm.username().touched() && loginForm.username().invalid()) {
  @for (error of loginForm.username().errors(); track $index) {
    <span class="error">{{ error.message }}</span>
  }
}

Отключайте отправку формы, пока выполняется проверка:

<button type="submit" [disabled]="loginForm().pending()">
  @if (loginForm().pending()) {
    Validating...
  } @else {
    Submit
  }
</button>

СОВЕТ: Другие примеры использования сигналов pending(), valid() и invalid() см. в руководстве Управление состоянием поля.

Порядок выполнения проверок

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

import {form, required, minLength, validateHttp} from '@angular/forms/signals';

form(model, (schemaPath) => {
  // 1. These synchronous validation rules run first
  required(schemaPath.username);
  minLength(schemaPath.username, 3);

  // 2. This async validation rule only runs if synchronous validation passes
  validateHttp(schemaPath.username, {
    request: ({value}) => `/api/check?username=${value()}`,
    onSuccess: (result: {valid: boolean}) =>
      result.valid
        ? null
        : {
            kind: 'usernameTaken',
            message: 'Username taken',
          },
    onError: () => ({
      kind: 'serverError',
      message: 'Validation failed',
    }),
  });
});

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

Отмена запросов

При изменении значения поля Signal Forms автоматически отменяет все ожидающие запросы асинхронной проверки для этого поля. Это предотвращает состояния гонки и гарантирует, что результат проверки всегда соответствует текущему значению. Вам не нужно самостоятельно реализовывать логику отмены.

Рекомендации

Сочетайте с синхронной проверкой

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

import {form, required, email, validateHttp} from '@angular/forms/signals';

form(model, (schemaPath) => {
  // Validate format first
  required(schemaPath.email);
  email(schemaPath.email);

  // Then check availability
  validateHttp(schemaPath.email, {
    request: ({value}) => `/api/emails/check?email=${value()}`,
    onSuccess: (result: {available: boolean}) =>
      result.available
        ? null
        : {
            kind: 'emailInUse',
            message: 'Email already in use',
          },
    onError: () => ({
      kind: 'serverError',
      message: 'Could not verify email',
    }),
  });
});

Пропускайте проверку, когда это уместно

Чтобы пропустить проверку, верните undefined из функции request. Так можно избежать проверки пустых полей или значений, не соответствующих минимальным требованиям:

import {validateHttp} from '@angular/forms/signals';

validateHttp(schemaPath.username, {
  request: ({value}) => {
    const username = value();
    // Skip validation for empty or short usernames
    if (!username || username.length < 3) return undefined;

    return `/api/users/check?username=${username}`;
  },
  onSuccess: (result: {valid: boolean}) =>
    result.valid
      ? null
      : {
          kind: 'usernameTaken',
          message: 'Username taken',
        },
  onError: () => ({
    kind: 'serverError',
    message: 'Validation failed',
  }),
});

Корректно обрабатывайте ошибки

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

import {validateHttp} from '@angular/forms/signals';

validateHttp(schemaPath.field, {
  request: ({value}) => `/api/validate?field=${value()}`,
  onSuccess: (result: {valid: boolean; message?: string}) => {
    if (result.valid) return null;
    // Use server message when available
    return {
      kind: 'serverError',
      message: result.message || 'Validation failed',
    };
  },
  onError: (error) => {
    // Log for debugging
    console.error('Validation request failed:', error);

    // Show user-friendly message
    return {
      kind: 'serverError',
      message: 'Unable to validate. Please try again later.',
    };
  },
});

Предоставляйте понятную обратную связь

Используйте сигнал pending(), чтобы показывать, когда выполняется проверка. Это помогает пользователям понять причину задержки и улучшает субъективное восприятие производительности:

@if (field().pending()) {
  <span class="checking">
    <span class="spinner"></span>
    Checking...
  </span>
}
@if (field().valid() && !field().pending()) {
  <span class="success">Available</span>
}
@if (field().invalid()) {
  <span class="error">{{ field().errors()[0]?.message }}</span>
}

Дальнейшие шаги

В этом руководстве рассмотрена асинхронная проверка с помощью validateHttp() и validateAsync(). В связанных руководствах рассматриваются другие аспекты Signal Forms:

Проверка Управление состоянием поля

Подробную документацию по API см. здесь:

  • validateHttp() — асинхронная проверка на основе HTTP
  • validateAsync() — пользовательская асинхронная проверка на основе ресурсов
  • httpResource() — API HTTP-ресурсов Angular
  • resource() — примитив resource в Angular

Super-powered by Google ©2010–2025.
Code licensed under an MIT-style License. Documentation licensed under CC BY 4.0.
https://angular.dev/guide/forms/signals/async-operations

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API