惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

aimingoo的专栏
aimingoo的专栏
量子位
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
S
Schneier on Security
Cisco Talos Blog
Cisco Talos Blog
T
ThreatConnect
J
Java Code Geeks
博客园 - 司徒正美
A
Arctic Wolf
T
True Tiger Recordings
C
Cybersecurity and Infrastructure Security Agency CISA
Cyberwarzone
Cyberwarzone
Know Your Adversary
Know Your Adversary
T
Threat Research - Cisco Blogs
V
Vulnerabilities – Threatpost
Recorded Future
Recorded Future
P
Palo Alto Networks Blog
The Hacker News
The Hacker News
The Register - Security
The Register - Security
S
Securelist
www.infosecurity-magazine.com
www.infosecurity-magazine.com
C
CXSECURITY Database RSS Feed - CXSecurity.com
Application and Cybersecurity Blog
Application and Cybersecurity Blog
I
Intezer
P
Privacy & Cybersecurity Law Blog
Scott Helme
Scott Helme
K
Kaspersky official blog
博客园 - 聂微东
Last Week in AI
Last Week in AI
V
V2EX
小众软件
小众软件
F
Fox-IT International blog
Martin Fowler
Martin Fowler
Apple Machine Learning Research
Apple Machine Learning Research
T
Tenable Blog
F
Future of Privacy Forum
Microsoft Security Blog
Microsoft Security Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
腾讯CDC
Stack Overflow Blog
Stack Overflow Blog
C
Check Point Blog
阮一峰的网络日志
阮一峰的网络日志
GbyAI
GbyAI
T
Threatpost
I
InfoQ
P
Proofpoint News Feed
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
T
Tor Project blog
G
GRAHAM CLULEY
D
DataBreaches.Net

Все публикации подряд на Хабре

Security Week 2622: эффективность Claude Mythos по версии Cloudflare TorFlash — приложение для Linux: поиск торрентов, скачивание и копирование на флешку в одно нажатие Как я решил проблему русской диктовки для ИИ Оверинжиниринг, потопивший немецкую подлодку или некоторые «баги» не чинятся десятилетиями Как ставить цели и не забывать о них: пошаговая система с примерами в таск-менеджере Как настроить observability в Spring Boot 3 HackTheBox. Прохождение Mini Pro Lab Puppet Обзор серверного ускорителя NVIDIA Tesla V100 16 Gb в корпусе от RTX 4090: Часть 3 — Запуск локальных моделей ИИ Редактирование текста нейросетью: как сделать диплом и курсовую более человечными Самодельный ARM ноутбук, реально ли? Как 100+ авторов пишут 100+ процессов в 3 версиях и не путаются. Или как мы переехали с Wiki на Git Прошла AnalystDays – хорошие выступления и нетворкинг VSCode как IDE для embedded разработки Моделирование широкополосной антенны с двойной круговой поляризацией и высокой изоляцией Ваше прошлое физически существует прямо сейчас. И вы заморожены там навсегда От списка инструментов к technical output: как security engineer’у описывать hands-on опыт в CV и на интервью I just want an agent. Часть 1. Как я научил ИИ собирать ИИ-агентов за пользователей и выиграл конкурс I just want an agent. Часть 1. Как я научил ИИ собирать ИИ-агентов за пользователей и выиграл конкурс Вайбкодинг спас меня от подрядчиков. А потом я поняла, что сама стала подрядчиком для своих агентов Святой Августин и GAN: почему борьба добра и зла — это генеративная состязательная сеть В каждом QR-коде зашита половина лишней информации. Намеренно Я открываю автомат ключом, меняю рулон бумаги и зарабатываю 180 тысяч в месяц с точки Мастер восстановления. Культура достиженства и выгорание Недельный геймдев: #279 — 24 мая, 2026 Защита от дублирования кода агентами: семантические концепции Frontend Status: свежий дайджест фронтенда и AI — 25.05.2026 Где искать IT-работу кроме HH: подборка платформ 2026 Почему простые числа собираются в спирали? OCR для Data Lakehouse: от Apache Tika к собственному решению на базе Docling Jira — Тьюринг-полная Kubernetes-аудит после Wiz и Prisma: как живут без CNAPP в 2026 «Тестируем MVP в 4 раза быстрее»: как нейросети изменили жизнь предпринимателей На каком стеке и железе работает умное наблюдение в вашем городе: обзор технологий от разработчиков видеоаналитики Как мы ускорили согласования на двух заводах в 24 раза Heartbeat-мониторинг cron-job'ов: dead-man-switch на FastAPI [Перевод] Сегодня нет джуниоров, а в 2031 году не станет и синьоров Профайлер для PostgreSQL: от идеи до работающего MVP за сутки [Перевод] Ограничения размера cookie в ASP.NET Core в продакшене: причины и способы решения Проблема «божественного» Obsidian: почему я отказался от централизованного подхода в работе Лицензии GNU GPL: как пройти проверку Минцифры и заказчика для госзакупок и КИИ Хакатон Samsung IT Academy Hack 2026: как студенты оптимизировали поиск в корпоративном мессенджере Хакатон Samsung IT Academy Hack 2026: как студенты оптимизировали поиск в корпоративном мессенджере MTProxy jumper — делаем автоматическое переключение прокси-серверов Telegram Ты уже используешь агента. Просто не заметил Книжный салон. Послевкусие и благодарности Как отлаживать мини‑приложения в MAX и почему без DevTools это боль Cбор биометрических данных. Как защищается наша биометрия на практике Как запустить учет активов без цифровой свалки: первые 90 дней CGE: визуализация кравлера и скрытых связей между поддоменами Зачем банки тратят миллиарды на науку (спойлер: не благотворительности ради) Книга: «Современный Java Concurrency. Глубокое погружение в Virtual Threads, Structured Concurrency и Scoped Values» Как использовать подписку ChatGPT и Claude в Cursor без оплаты за API токены Специализированная ИСУП или модуль в универсальной платформе: вот в чем вопрос Обход белых списков через WebRTC на стероидах (с поддержкой iOS и десктопа) Регата INFOSTART CIO CAMP: когда команда проверяется не в переговорной, а на воде Пет-проект, который не умер: система бронирования устройств как полигон для AI-разработки Не надо встраивать ИИ в каждую корпоративную систему, это архитектурная ошибка Нейросети для дизайна интерьера: Выбираем лучший ИИ для генерации концептов и планировок квартиры Что там с Ил-114-300 Что такое DAS: как и зачем продукт-менеджеры саботируют запуск новых продуктов 8% компаний измеряют критическое мышление руководителей. Что делают остальные 92% CVE, Shell и побег из контейнера: испытываем возможности PT Cloud Application Firewall Как я научил Алису петь: генерация музыки по голосовой команде Восстановление данных с помощью бесплатной утилиты Easy Disk Checker Как мы построили сквозную аналитику в Power BI Год разработки iOS-игры, 266 тысяч показов и $33: как я делал Vault и почти ничего не заработал Ты прокрастинируешь потому, что избегаешь напрасных усилий, а не чрезмерных нагрузок Я построила диагностику «стоит ли это автоматизировать» — и она трижды говорила глупости. Разбор ошибок Как устроены world models, что показал Google на прошлой неделе и где это меняет gamedev и робототехнику Двухдневная рабочая неделя — будущий стандарт CPU не умер, он просто ждал. Китай строит двухэксафлопсный суперкомпьютер без единого GPU — прорыв, необходимость, фейк? 3Sound: поиск бесплатных звуков для игр больше не боль? 3 Тбит/с по-русски: почему DDoS в 2026 году стал угрозой для любого бизнеса 10 Гбит/с — зачем вам такая скорость передачи данных в облаке Ремонтируем аналоговый XY-самописец Endim 622 [Перевод] IPO компании SpaceX: хорошая попытка, но нет «Ща будет шрифт»: история одного русского embedded‑шрифта Как аквариум на подоконнике превратился в full-stack платформу с AI GiftsHub — из чат-бота в полноценный backend-продукт Пиратство, копирайт и DMCA: как Napster, The Pirate Bay и YouTube изменили закон. Часть II Как найти внутренние резервы для развития предприятия Как один французский чиновник от безысходности начал платил зарплаты картами и практически изобрёл банкноты RAG в энтерпрайзе: почему демо работает, а прод нет AI-агент для финансовых процессов: как мы научили ИИ считать числа из базе данных без галлюцинаций Автопостинг на 8 платформах: архитектура waterfall, custom publisher'ы и API-ловушки Кинетика против бронзы: Почему Голиаф был обречен в дуэли с Давидом [Перевод] Масштабирование LLM: от одного чипа до ЦОДа. Глава 2. Шардинг LLM не работает за вас. Она работает с вами Чем лучше защищает минеральный SPF, тем страшнее он выглядит Стимпанк как часть жизни. История паровых двигателей и место, которое они занимали в мире в XIX-XX веках. Часть 1 Гастарбайтеры ворвались в IT и зарабатывают на рекламе: тут вам не снег лопатой кидать Новые методы и инструменты: как мы обновили курсы по тестированию в Яндекс Практикуме Java 21 в стиле «клятый энтерпрайз» на одноплатном компьютере возрастом 13 лет Ваши секреты внутри LLM. Куда уходят промпты и чего стоит опасаться? 10× труда. 10% к бонусу. Главный риск AI-эпохи — это сениор AI-инженер, который умеет считать Сапожник с сапогами Минимум, который удержит тебя на плаву в период дедлайнов Как без проблем переносить курсы между платформами? Обзор формата SCORM Когда Claude Code ошибается не по своей вине: документационный долг в соло-проектах 70% кода с AI — и ни на день быстрее
Reactive Forms vs Signal Forms: Эволюция сложных форм в Angular
Buccanier · 2026-05-26 · via Все публикации подряд на Хабре

Введение или «Как я перестал бояться и полюбил сигналы».

Признаюсь честно, что моя первая реакция на анонс Signal Forms была: «О, нет! Только не ещё один способ делать формы». Потому что у нас уже были для быстрых и простых вариантов Template Driven Forms и Reactive для всего серьёзного. А еще была возможность расширять базовый функционал и уже там можно было найти нечто вообще невообразимое. Я в начале карьеры работал с такой гигантской конструкцией содержащей вложенные расширенные подформы и более 1500 Form Control и поэтому представляю всю сложность подобного. Но команда разработки Angular решила что два способа это недостатчно и давайте добавим еще и третий.

Однако, после ковыряния в новом API в течении нескольких вечеров и после трех литров кофе моя реакция все таки смягчилась. Разработчики из команды Angular стараются не просто так, а Signal Forms не так уж страшны. Особенно когда форма с которой ты работаешь уже давно разрослась и усложнилась и на текущий момент увешана гирляндами из FormArray и FormGroupи различными кастомными самоделками аки ёлка новогодняя.

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

Спойлер: новый способ не плох, но чайную ложечку дегтя я все же припас для своих любознательных читателей.

Reactive Forms: «Тяжелое наследие» или проверенная классика.

Как это работает (если вы вдруг забыли)

Reactive Forms построены на трёх китах: FormControlFormGroup и FormArray. Плюс RxJS который находится под капотом и который собственно и обеспечивает всю реактивную магию. Вы вызываете нужный вам класс формы, который живёт в компоненте и привязываете его к шаблону. Все достаточно просто и обыденно. Нюансы есть, но к ним надо привыкнуть (или сначала смириться а потом привыкнуть).

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

interface Order {
  client: {
    name: string;
    email: string;
  };
  items: Array<{
    product: string;
    quantity: number;
    price: number;
  }>;
  total: number;
}

// Код компонента
orderForm = new FormGroup({
  client: new FormGroup({
    name: new FormControl('', [Validators.required]),
    email: new FormControl('', [Validators.required, Validators.email])
  }),
  items: new FormArray([]),
  total: new FormControl({ value: 0, disabled: true })
});

get itemsArray(): FormArray {
  return this.orderForm.get('items') as FormArray;
}

addItem() {
  const itemGroup = new FormGroup({
    product: new FormControl('', Validators.required),
    quantity: new FormControl(1, [Validators.required, Validators.min(1)]),
    price: new FormControl(0, [Validators.required, Validators.min(0.01)])
  });
  
  this.itemsArray.push(itemGroup);
  this.updateTotal(); // не забываем пересчитать сумму
}

updateTotal() {
  let total = 0;
  this.itemsArray.controls.forEach(group => {
    const quantity = group.get('quantity')?.value || 0;
    const price = group.get('price')?.value || 0;
    total += quantity * price;
  });
  this.orderForm.get('total')?.setValue(total);
}

Все логично и читаемо. Если мы список расширим полей так до 50 и с 1-2 уровнями вложенности, то структура все еще читаема, но уже хуже. А если список начинает обретать глубину и сильную вложенность и помимо обычных FormGroup и FormArray появляются кастомные структуры и каждый в свою очередь имеет свои несколько уровней вложенности, то вот такой код становится читать очень и очень непросто.

Где Reactive Forms… ну, такие себе

Проблема первая, она же «боль в пояснице»: типизация. Все страдают, но все уже привыкли. Посмотрите на строку: this.orderForm.get('items') as FormArray. Без as TypeScript ругается, потому что get() возвращает AbstractControl | null. Теоретически правильно, но на практике вы точно знаете что null там нет, что там FormArray. И это необходимо делать постоянно.

Проблема вторая, она же «головная боль»: подписки. Если вам нужно реагировать на изменение конкретного поля, вы пишете:

this.orderForm.get('client.email')?.valueChanges.subscribe(email => {
  // делаем что то умное
});

Вы же не забудете потом про unsubscribe? Все же знают про про элементарные правила работы с потоками? Да, ведь? Иначе это чревато утечкой памяти. И так для каждого поля.

Проблема третья, она же «почему оно тормозит?»: производительность. Когда у вас больше сотни динамических полей и каждое изменение вызывает Change Detection на всей форме, браузер начинает чихать и задумчиво жевать память. Особенно весело, когда форма сложная и вложенная. Некогда объяснять, расчехляйте оптимизатор, предстоит много работать.

Динамическое построение на Reactive Forms: «Куда ты нажал?»

Не самый редкий способ делать формы это генерация формы из JSON-конфига при помощи генератора. Например, конструктор опросов, который хранится на бэке и где автор добавляет вопросы разных типов.

// Конфиг от бэкенда
const formConfig = {
  fields: [
    { type: 'text', label: 'Ваше имя', required: true },
    { type: 'email', label: 'Email', validators: ['email'] },
    { type: 'select', label: 'Город', options: ['Москва', 'СПб', 'Казань'] },
    // ... ещё 20 полей
  ]
};

// Фабрика для создания формы
function createDynamicForm(config: any): FormGroup {
  const group: any = {};
  
  config.fields.forEach((field, index) => {
    const validators = [];
    if (field.required) validators.push(Validators.required);
    if (field.validators?.includes('email')) validators.push(Validators.email);
    
    group[`field_${index}`] = new FormControl('', validators);
  });
  
  return new FormGroup(group);
}

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

Signal Forms: «Встречайте новую надежду»

Философия: «Никакой магии, только сигналы»

Signal Forms это не столько новый API, сколько новая парадигма. Вы не создаете объекты из сложных структур нужной степени вложенности, а работаете с сигналами которые уже используются в приложении. Данные живут в модели, а форма это обертка с валидацией в которую вы оборачиваете модель.

Звучит как «возьмите кусок данных и добавьте ему валидацию». Так оно и есть.

// Модель — обычный сигнал
private orderModel = signal<Order>({
  client: { name: '', email: '' },
  items: [{ product: '', quantity: 1, price: 0 }],
  total: 0
});

// Форма — обёртка над моделью
protected orderForm = form(this.orderModel, (path) => {
  required(path.client.name);
  required(path.client.email);
  email(path.client.email);
  
  applyEach(path.items, (item) => {
    required(item.product);
    min(item.quantity, 1);
    min(item.price, 0.01);
  });
  
  // Кастомная валидация для общей суммы
  validate(path, (ctx) => {
    const total = ctx.value().items.reduce(
      (sum, item) => sum + (item.quantity * item.price), 0
    );
    if (total === 0) {
      return { kind: 'emptyOrder', message: 'Заказ не может быть пустым' };
    }
    return undefined;
  });
});

// Добавление товара — просто мутация массива
addItem() {
  this.orderModel.update(order => ({
    ...order,
    items: [...order.items, { product: '', quantity: 1, price: 0 }]
  }));
}

Нет FormArray, нет push() и removeAt(). Всё, что вы умеете делать с массивами в JavaScript, работает и здесь. Это примерно как снять обувь после долгой ходьбы и сразу легче дышать становиться.

Где Signal Forms хороши (спойлер: почти везде)

Наконец-то типизация!

В Signal Forms нет AbstractControl | null. Есть тип FieldTree<T>, который и контрактует вашу модель. Когда вы пишете orderForm.items[0].product, TypeScript понимает, что это поле строки, и валидатор для него тоже строковый. Никаких as unknown as. Все, можно расслабиться.

Забудьте о подписках

valueChanges.subscribe() теперь ушли и вместо них computed и effect. Сигналы сами знают, когда обновляться.

// Раньше: ручная подписка и очистка
subscription = orderForm.get('client.email')?.valueChanges.subscribe(...);
ngOnDestroy() { this.subscription.unsubscribe(); }

// Теперь: всё автоматически
readonly emailValidationMessage = computed(() => {
  const emailField = this.orderForm.client.email;
  if (emailField.touched() && emailField.invalid()) {
    return 'Email looks suspicious...';
  }
  return '';
});

Производительность

Сигналы обновляются точечно. В новой сигнальной форме на изменение одного поля отреагирует только валидация для этого поля и, возможно, несколько computed которые на него подписаны. Остальные 99 полей даже не шелохнутся. Я проводил тест на динамических на 100 и потом на 200 полей и в моём тесте Signal Forms оказались на ~35% быстрее. Браузер выдохнул и сказал «спасибо».

Динамическое построение в действии

Возможно, вам уже стало интересно а что же по динамике? Вот динамическое построение на сигналах:

@Component({...})
export class SurveyBuilderComponent {
  // Модель: массив вопросов
  private surveyModel = signal<Survey>({
    title: '',
    questions: [
      { id: crypto.randomUUID(), type: 'text', text: '', required: false }
    ]
  });
  
  protected surveyForm = form(this.surveyModel, (path) => {
    required(path.title);
    
    // Применяем валидацию к каждому вопросу в массиве
    applyEach(path.questions, (question) => {
      required(question.text);
      
      // Условная валидация: для select нужны варианты ответов
      applyWhen(question.options, () => question.type() === 'select', (opts) => {
        required(opts);
        minLength(opts, 1);
      });
    });
  });
  
  addQuestion() {
    this.surveyModel.update(survey => ({
      ...survey,
      questions: [...survey.questions, {
        id: crypto.randomUUID(),
        type: 'text',
        text: '',
        required: false
      }]
    }));
  }
  
  removeQuestion(index: number) {
    this.surveyModel.update(survey => ({
      ...survey,
      questions: survey.questions.filter((_, i) => i !== index)
    }));
  }
}

Шаблон, кстати, тоже стал стал проще:

<form>
  <input [field]="surveyForm.title" placeholder="Название опроса" />
  
  @for (question of surveyModel().questions; track question.id; let i = $index) {
    <div class="question-card">
      <input [field]="surveyForm.questions[i].text" placeholder="Текст вопроса" />
      
      <select [field]="surveyForm.questions[i].type">
        <option value="text">Текстовый</option>
        <option value="select">Выбор из списка</option>
      </select>
      
      @if (surveyForm.questions[i].type() === 'select') {
        <input [field]="surveyForm.questions[i].options" placeholder="Варианты (через запятую)" />
      }
      
      <button type="button" (click)="removeQuestion(i)">Удалить вопрос</button>
    </div>
  }
  
  <button type="button" (click)="addQuestion()">+ Добавить вопрос</button>
</form>

Нет ни FormArrayName, ни formArrayName в шаблоне, нет путаницы с индексами. Просто массив в модели и track по ID. И всё работает.

Сравнительная таблица и выводы

Цифры и факты

Я протестировал оба подхода на трёх реальных сценариях. Вот что получилось:

Сценарий

Reactive Forms

Signal Forms

Простая форма (5 полей)

15 строк кода

12 строк

Сложная форма с 3 уровнями вложенности

120 строк + 8 подписок

85 строк + 0 подписок

Динамический список (50 элементов)

65ms на добавление

42ms на добавление

Изменение одного поля в массиве из 100 элементов

48ms (CD цикл)

29ms

При этом количество ошибок в типизации при переносе реальной формы с Reactive на Signal снизилось на 70%. Просто потому что TypeScript перестал путаться в get('path.to.field').

Где подвох? Ложка дёгтя обязательна, не так ли?

Сигналы штука классная, но не без нюансов:

  • Signal Forms они экспериментальные. Это значит, что API может поменяться завтра. Или через месяц. Или через час после того, как вы закончите работу над новой сигнальной формой. В продакшен с этим идти, это как прыгать с парашютом на котором нет подписи укладчика. Вроде и азартно, но сильно боязно.

  • Миграция потребует переписывания. не получится просто взять и заменить FormGroup на form(). Придётся переписывать всё: от моделей до шаблонов. Если у вас проект на 500 форм то стоит ли переход месяцев работы?

  • Документация пока бедная. На момент написания этой статьи официальной англоговорящей документации по Signal Forms очень мало. Только RFC и пара статей от смельчаков, которые экспериментируют. Будьте готовы читать исходники.

  • Не все библиотеки поддерживают.  ngx-formlyng-zorromaterial  многие популярные наборы компонентов ещё не добавили поддержку Signal Forms. Придётся либо ждать, либо писать свои обёртки.

Что делать? Практические советы

Реактивные формы ваш выбор, если:

  • Проекту больше года и он уже на Angular 12-16

  • Команда поёт оды RxJS и не представляет жизнь без switchMap

  • У вас есть сложные асинхронные валидации, которые зависят от API

  • Вам нужна стабильность любой ценои (банки, гос. учреждения, медицинское ПО)

Signal Forms можете пробовать, если:

  • Вы начинаете новый проект на Angular 21+

  • Вам надоело писать as FormArray в каждом компоненте

  • Производительность важнее, чем «а вдруг API поменяется»

  • Вы уже используете сигналы в приложении и хотите единообразия

Инструменты и ресурсы

  • Официальный RFC по Signal Forms (англ.) обязателен к прочтению.

  • Блог Deborah Kurata, она первая начала писать туториалы.

Заключение: «Старый друг лучше новых двух?»

У меня для вас есть две новости.

Первая: Reactive Forms никуда не денутся. Это как jQuery, который используют до сих пор, хотя уже 2026 год. Стабильность и огромная экосистема перевешивают любые нововведения.

Вторая: Signal Forms это реально интересно. Они решают проблемы, которые мучили нас годами: типизация, производительность, сложность динамических форм. И если команда Angular доведёт API до ума (и сделает стабильным), то через несколько лет мы будем смотреть на старые FormArray так же как сейчас на var  с лёгкой улыбкой ностальгии.

Мой личный вердикт:

  • Если у вас Pet-проекты или MVP то берите Signal Forms без раздумий.

  • Если продакшен с высокой критичностью то ждите стабильного релиза или используйте классику.

А теперь ваша очередь. Пробовали Signal Forms? Словили баги или наоборот в восторге? Делитесь в комментариях.