Поле подписи Angular | ng-hub-ui-signature

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

Последнее обновление 16 сент. 2026 г.

Обзор

Почему команды ищут эту библиотеку

Используйте ng-hub-ui-signature, когда форме нужна повторно используемая, доступная и переносимая поверхность подписи.

Установка

npm install ng-hub-ui-signature

Перейти к

Идеально для

  • принятие договора
  • подтверждение доставки
  • формы согласия
  • процессы утверждения

О библиотеке signature

ng-hub-ui-signature — standalone-поле Angular на Pointer Events. Оно сохраняет штрихи мыши, касания и пера как SVG-значение ControlValueAccessor, восстанавливает сохранённые подписи и предлагает очистку, отмену, повтор и экспорт PNG.

Руководства по возможностям

Capture and edit signatures

Pointer input works with a mouse, touch screen or pen, and arrow keys with Space carry the same stroke for anyone signing without a pointer. Either way the field keeps a scalable SVG history for undo, redo and form persistence.

Примеры:
Подпись

Draw a signature and use the built-in history actions.

Sign with the keyboard: arrow keys move the pen, holding Shift moves it further, Space or Enter lowers and lifts it, and Escape discards the stroke in progress.

Рисуйте мышью, на сенсорном экране или пером.
Код
Import:
Template:
Component:

Signing with the keyboard

Arrow keys carry the pen, Space or Enter lower and lift it, Escape discards the stroke in progress — and what comes out is an ordinary stroke, which is why the draw outputs carry PointerEvent | KeyboardEvent.

Примеры:
Подпись с клавиатуры

Поле работает без указателя: стрелки ведут перо, Space или Enter опускают и поднимают его, Escape отменяет незавершённый штрих. Полученный штрих — обычный, поэтому (drawStart) передаёт PointerEvent | KeyboardEvent: разница сохраняется только в полезной нагрузке события.

Sign with the keyboard: arrow keys move the pen, holding Shift moves it further, Space or Enter lowers and lifts it, and Escape discards the stroke in progress.

Перейдите в рамку клавишей Tab и используйте клавиши ниже.
  • Ведут перо
  • Shift + Ведут его дальше
  • Space / EnterОпускают перо, а при повторном нажатии поднимают, фиксируя штрих
  • EscОтменяет незавершённый штрих
Ещё не подписано
Код
Import:
Template:
Component:

Signature as a form control

The field is a ControlValueAccessor whose value is the SVG it serializes itself into: the required marker and the error message come from the control's validators, and the success message needs both [showValid] and [validFeedback].

Примеры:
Подпись как элемент формы

Поле — это ControlValueAccessor, значением которого служит SVG, в который оно само себя сериализует. Отметка обязательности и текст ошибки берутся из валидаторов контрола; сообщение об успехе требует одновременно [showValid] и [validFeedback].

Sign with the keyboard: arrow keys move the pen, holding Shift moves it further, Space or Enter lowers and lifts it, and Escape discards the stroke in progress.

Подпишите внутри рамки указателем или с клавиатуры.
Код
Import:
Template:
Component:

Draw events and stroke data

The drawing lifecycle is observable through (drawStart) and (drawEnd), the field answers isEmpty() without parsing its SVG, and toStrokes() / fromStrokes() move the geometry itself — which the SVG form value cannot carry.

Примеры:
События рисования и данные штрихов

Реагируйте на цикл рисования через (drawStart) и (drawEnd), проверяйте поле с помощью isEmpty() и переносите саму геометрию между полями через toStrokes() и fromStrokes().

Sign with the keyboard: arrow keys move the pen, holding Shift moves it further, Space or Enter lowers and lifts it, and Escape discards the stroke in progress.

Sign with the keyboard: arrow keys move the pen, holding Shift moves it further, Space or Enter lowers and lifts it, and Escape discards the stroke in progress.

Пусто
Код
Import:
Template:
Component:

Sizing the surface and the pen

[height] re-measures and repaints, so the viewBox toSvg() emits keeps describing the box on screen; [strokeWidth] and [strokeColor] are read when a stroke opens and stored on it, so strokes already drawn keep the pen they were drawn with.

Примеры:
Размеры поверхности и пера

Двигайте ползунки, пока поле пустое: [height] заново измеряет и перерисовывает, поэтому viewBox, который выдаёт toSvg(), продолжает описывать видимую рамку. [strokeWidth] и [strokeColor] читаются в момент начала штриха и сохраняются в нём, так что уже нарисованные штрихи сохраняют своё перо, а (valueChange) сообщает SVG после каждого изменения, сделанного пользователем.

Sign with the keyboard: arrow keys move the pen, holding Shift moves it further, Space or Enter lowers and lifts it, and Escape discards the stroke in progress.

Пока не подписано
Код
Import:
Template:
Component:

Placing the label

[labelType] takes the shared vocabulary of ng-hub-ui-forms so a signature lines up with the fields around it. 'horizontal' puts the label in a first column and stacks the actions, helper text and feedback in the second; 'floating' is accepted and falls back to stacked, because a label parked inside the box would sit on top of the ink.

Примеры:
Расположение метки

[labelType] использует общий словарь меток ng-hub-ui-forms, поэтому поле подписи выравнивается с соседними полями. 'horizontal' помещает метку в первую колонку, а действия, подсказку и сообщения проверки складывает во вторую; 'floating' принимается и откатывается к обычному расположению сверху, потому что метка внутри рамки оказалась бы поверх чернил, как только кто-нибудь распишется.

Sign with the keyboard: arrow keys move the pen, holding Shift moves it further, Space or Enter lowers and lifts it, and Escape discards the stroke in progress.

Рисуйте мышью, на сенсорном экране или пером.

Sign with the keyboard: arrow keys move the pen, holding Shift moves it further, Space or Enter lowers and lifts it, and Escape discards the stroke in progress.

Рисуйте мышью, на сенсорном экране или пером.
Код
Import:
Template:
Component:

Naming one field on its own

[ariaLabel] names a surface with no visible label — on a field that has one it is not consulted at all — and [labels] overrides the built-in action wording for a single field without touching the application dictionary.

Примеры:
Назвать отдельное поле

[ariaLabel] даёт имя поверхности без видимой метки; в поле с [label] он не используется вовсе, потому что доступным именем становится сама метка. [labels] переписывает встроенные действия одного поля, не трогая словарь приложения: храните его в сигнале или computed, но не в виде объектного литерала в шаблоне.

Уполномоченный подписант

Sign with the keyboard: arrow keys move the pen, holding Shift moves it further, Space or Enter lowers and lifts it, and Escape discards the stroke in progress.

Sign with the keyboard: arrow keys move the pen, holding Shift moves it further, Space or Enter lowers and lifts it, and Escape discards the stroke in progress.

Код
Import:
Template:
Component:

Helper text and error messages as markup

hubFormText and hubValidationError are exports of ng-hub-ui-forms, and the field renders both: a keyboard hint with real key caps, and the required message written in the product’s own words. Each error key resolves its own template before the message builder is consulted, so overriding one leaves the rest alone.

Примеры:
Подсказка и ошибки как разметка

hubFormText и hubValidationError экспортируются из ng-hub-ui-forms, и поле отрисовывает оба: подсказку с настоящими клавишами и сообщение об обязательности словами самого продукта. Каждый ключ ошибки сначала ищет свой шаблон и только потом обращается к стандартному генератору сообщений, поэтому переопределение одного не трогает остальные.

Sign with the keyboard: arrow keys move the pen, holding Shift moves it further, Space or Enter lowers and lifts it, and Escape discards the stroke in progress.

Распишитесь указателем или ведите перо стрелками, опуская его пробелом.
Код
Import:
Template:
Component:

Reactive external translations

The action-label contract accepts strings or reactive sources, so Transloco and ngx-translate language changes update the field without a direct package dependency.

Примеры:
Transloco и ngx-translate

Подключите реактивные потоки переводов, чтобы обновлять действия при смене языка.

Sign with the keyboard: arrow keys move the pen, holding Shift moves it further, Space or Enter lowers and lifts it, and Escape discards the stroke in progress.

Рисуйте мышью, на сенсорном экране или пером.
Код
Import:
Template:
Component:

Ключевые возможности

Последние изменения

Version 22.8.1 - 9/16/26, 12:00 AM

changed: Repository, issue and README links follow the move to the hub-env organization. Issues for every Hub UI package are now gathered in hub-env/hub-ui, and the repository and bugs fields of the manifest point at the new addresses. No code, types or styles change.

Version 22.8.0 - 9/8/26, 12:00 AM

changed: The field keeps its stylesheet to itself. <hub-signature> shipped with ViewEncapsulation.None, which publishes every rule it emits into the application's global cascade, where it competes with rules the library never sees and cannot be removed by anyone who did not know it was there. None of the four reasons CODING_RULES.md allows for it applied: every selector in the sheet names .hub-signature, and that is the component's own root element, drawn by its own template, so emulated encapsulation reaches all of it. Not one selector had to move, and neither does what you write to theme the field: .hub-signature is still on an element your global stylesheet matches, so a hand-written rule and hub-signature-theme() land exactly where they did. See BREAKING_CHANGES.md.

added: ng-hub-ui-ds is declared as an optional peer dependency (>=22.0.0). The token defaults climb --hub-input-* and --hub-label-* first and the --hub-sys-* / --hub-ref-* ladder after that, and nothing in the manifest said so, so a consumer reading the package on npm could not tell that installing the token package is what gives the field the family palette and its dark mode. It stays optional: every token ends in a literal fallback.

Version 22.7.0 - 9/6/26, 12:00 AM

fixed: The declared peer ranges now name versions the component actually builds against. They read ng-hub-ui-forms >=22.0.0 and ng-hub-ui-utils >=22.8.0, and neither floor was ever true: showsFormTextTooltip(), formText() and the .hub-field__label-row styling all arrived with forms 22.31.0, while HubTooltipDirective arrived with utils 22.9.0, so against 22.8.x the import resolves to nothing and the directive sits undefined in the imports array. Ranges that wide were not permissive, they were silent: npm saw them satisfied, upgraded nothing, and the application failed to compile on a base class member that was simply not there. The floors are now >=22.31.0 and >=22.9.0, which is what the rest of the family already does.

fixed: The helper-text mark survives a field with no visible label. formTextType="tooltip" hangs the helper text behind a question mark at the end of the label row, and the whole row was nested inside the @if (label() || required()) guard, so a bare surface named only by [ariaLabel] drew no mark while the block below had already stood down because tooltip mode was on. The helper text was accepted, resolved and rendered nowhere at all, and the only way out was to give the field a visible label it was deliberately built without. The row is now rendered because the hint is due rather than because the label is, and the label moves into it only when there is one, which is the rule hub-input and hub-segmented already state.

fixed: [height] resizes the surface it already reported. canvas.height, canvas.style.height and the repaint were written in resizeCanvas() alone, called once from afterNextRender(), while toSvg() read height() live, so changing the input moved the viewBox of every value saved from that moment on and moved nothing on screen: ink drawn on a 160-tall surface was filed as a 240-tall document and no longer filled the frame it declared. An effect now watches height() and re-runs resizeCanvas(). Strokes are still never rescaled, since they keep the coordinates they were captured with, so change the height while the field is empty whenever the ink has to keep its place inside the box.

fixed: The README states the stylesheet setup step. Install said npm install ng-hub-ui-signature ng-hub-ui-forms and stopped there, while the label row, the helper text, the validation feedback and the question mark that opens it are all drawn by ng-hub-ui-forms, whose sheet nothing told you to load: a reader following the README alone got a correct canvas surrounded by unstyled body text and an empty button where the mark should be, with no error anywhere to explain it. The warning existed only in MIGRATION.md, which a new consumer has no reason to open. Both READMEs now carry the @use lines, including the utils tooltip sheet that formTextType="tooltip" needs because its bubble is appended to body, and ng-hub-ui-utils joins the install line it was already a peer of.

fixed: BREAKING_CHANGES.md covers 22.5.0 and 22.6.0. The newest section was 22.4.0 while the library was on 22.6.1, and both releases in between asked the consumer to do something: 22.5.0 to delete the invalid-state override the migration guide used to recommend, which now collides with the component rule at equal specificity and can stop applying in a production build while still working in the dev server, and 22.6.0 to raise the ng-hub-ui-forms floor to 22.31.0, without which the application no longer compiles. In a family whose major tracks Angular, that file is the only warning a breaking change can give, and it said nothing.

added: Projected hubFormText and hubValidationError templates are rendered. Both queries live on HubFieldControl, so a template placed inside hub-signature compiled, was matched and was collected, and was then read by nothing: the field printed the plain formText() string and the default message for every error key. The helper block now renders the projected template and falls back to the string otherwise, and each validation error resolves its own template before reaching the default message, through the same two ngTemplateOutlet pairs the rest of the family uses, so a consumer who wrote the markup for hub-input can move it across unchanged.

Version 22.6.1 - 9/3/26, 12:00 AM

fixed: Server-side rendering no longer throws. writeValue() repaints the canvas and runs whenever a reactive form binds a value — including during prerendering, where the server DOM shim throws NotYetImplemented from canvas.getContext('2d') instead of returning null, so the existing null check never ran. redraw() now returns early outside the browser; the afterNextRender hook already repaints once there are pixels.

Version 22.6.0 - 9/2/26, 12:00 AM

added: formTextType="tooltip" works here too. ng-hub-ui-forms 22.31.0 moved formText and formTextType onto HubFieldControl, the base class this component extends, so the input arrived for free — and did nothing, because the template rendered the helper block unconditionally and drew no question mark. The mark now sits in a .hub-field__label-row beside the label rather than inside it, which is not decoration: clicking this label focuses the drawing surface, so a button nested in it would open the tooltip and put the pen in the reader's hand at once.

fixed: The component compiles against ng-hub-ui-forms 22.31.0. It declared its own formText, which the base class now declares too, and TypeScript refuses the redeclaration without an override modifier (TS4114). The local declaration is deleted rather than annotated — the base one is identical, and two declarations of one input is how they drift.

Version 22.5.0 - 9/1/26, 12:00 AM

fixed: [labelType] is read. It was declared, compiled, type-checked and never looked at, so a team migrating a horizontal form bound the input, saw a stacked label, and went hunting through ng-hub-ui-forms for a bug that was not there. 'horizontal' now places the label beside the drawing surface, with the action row, helper text and feedback stacked in the second column. 'floating' still falls back to stacked, deliberately: it reuses the space an empty text control's value would occupy and is driven by :placeholder-shown, and a label parked inside the box would sit on top of the ink the moment anyone signed.

fixed: The validation state shows on the drawing surface. The --invalid and --valid classes were bound on the root and styled by nothing, so a required-but-empty signature printed an error message under a canvas that looked exactly like a valid one. The canvas now takes the danger border and ring when touched and invalid, and the success pair when [showValid] is on and the field is valid, from the shared --hub-form-* contract the rest of the family uses. If you wrote the workaround the migration guide recommended, delete it: it now collides with the component rule at equal specificity.

added: A live theming demo exercising all eleven --hub-signature-* slots through hub-signature-theme(). Building it surfaced a trap now documented: setting the tokens on a wrapper element does nothing, because the component declares every slot on the field element itself and a property declared on an element always beats one inherited from an ancestor. That is why the mixin emits "<your scope> :where(.hub-signature)" rather than relying on inheritance.

Version 22.4.0 - 9/1/26, 12:00 AM

fixed: The visible label now names the drawing surface. The template rendered <label for> pointing at the canvas, and for associates only with labelable elements — a <canvas> is none of them, so the attribute was inert: no association, clicking did nothing, and the name came entirely from [ariaLabel]. The surface is named with aria-labelledby, the only mechanism that works on a non-labelable element and the only one unaffected by role="application". aria-labelledby and aria-label are now mutually exclusive, because the first outranks the second outright and emitting both would leave one permanently unreachable. Clicking the label focuses the surface.

fixed: [ariaLabel] goes through the translation dictionary. It was a hardcoded English literal with no HUBUI.SIGNATURE.* key behind it, so an application that localized every button still had its drawing surface announce itself in English. It now resolves through the explicit input, then [labels] / provideHubSignature(), then HUBUI.SIGNATURE.ARIA_LABEL, then the English fallback; its default changed from Signature to the empty string so that unset is distinguishable.

changed: The accessible name comes from [label] when there is one, and [ariaLabel] is the fallback for a bare surface. This is what closes WCAG 2.5.3: translating [ariaLabel] alone would only have made agreement possible, still asking every consumer to pass the same string twice. Binding [ariaLabel] beside a [label] no longer does anything — remove it. HubSignatureLabels also gained a required ariaLabel member; breaking, see BREAKING_CHANGES.md.

Version 22.3.0 - 9/1/26, 12:00 AM

added: A keyboard path to sign. The canvas had carried tabindex="0" since 22.0.0 over pointer-only handlers, so the field was focusable and unusable — a required control no keyboard-only user could satisfy. Arrow keys now carry a visible pen, Space or Enter lower and lift it, Escape abandons the stroke. It goes through the same internal begin/commit pair the pointer does, so it yields the same stroke, the same toSvg() output and the same reported value. The surface carries role="application" because a canvas with tabindex is not a form control, and screen readers would otherwise consume the arrows for document navigation; the instructions are announced through aria-describedby under HUBUI.SIGNATURE.KEYBOARD_HINT.

added: [validFeedback] is rendered. It existed on HubFieldControl all along, so it compiled and type-checked on <hub-signature> and then did nothing. It now renders the same .hub-field__feedback--valid block as every other field of the family.

added: hub-signature-theme() reaches all eleven --hub-signature-* tokens instead of five. The six new parameters are appended after the original five so existing positional includes keep resolving to the same tokens, and an argument-less include now raises a Sass @warn rather than compiling silently to nothing.

fixed: pointercancel no longer commits the partial stroke. It was wired to the pen-up handler, so an OS gesture, a scroll takeover or palm rejection pushed a half-drawn stroke onto the history, reported it to the form and emitted (drawEnd). A cancelled interaction is now discarded, and losing focus mid-stroke takes the same path.

fixed: The default currentColor ink is resolved before capture. The canvas 2D context cannot parse CSS-context keywords, so the ink fell back to black whatever the surrounding colour, and the literal was written into the persisted SVG — leaving archived signatures with no fixed colour at all. It is now resolved with getComputedStyle() when the stroke opens, which is also what makes hub-signature-theme($color) reach the ink as documented.

changed: (drawStart) and (drawEnd) emit HubSignatureDrawEvent — PointerEvent | KeyboardEvent — because drawing is no longer pointer-exclusive, and HubSignatureLabels gained a required keyboardHint member. Both are breaking; see BREAKING_CHANGES.md.

Version 22.2.0 - 9/1/26, 12:00 AM

added: isEmpty(), so a form can validate the field without parsing the serialized SVG — previously the only way to ask whether anything had been drawn.

added: toStrokes() / fromStrokes(), exposing the committed strokes as structured HubSignatureStroke[]. Deliberately not named toData / fromData: angular2-signaturepad uses those names for an incompatible payload, and the same name with a different shape would let a migration compile and then fail silently.

added: (drawStart) and (drawEnd) outputs, emitted around a user stroke, so a host can react to drawing activity without polling the value.

changed: The hub-signature-theme() mixin documents itself where the tooling can read it. Its header used a block comment, which the generator skips, so the library page showed no theming section at all despite the mixin having shipped since 22.0.0. Comment-only — the emitted declarations are byte-identical.

Version 22.1.1 - 8/17/26, 12:00 AM

fixed: The published package declared no licence. A registry reports an absent `license` field as unlicensed, which legally reads as all rights reserved. The intent was always MIT; it is now stated in `package.json` and carried in a `LICENSE` file that ships with the package.

Version 22.1.0 - 8/14/26, 12:00 AM

fixed: The field now really inherits the ng-hub-ui-forms contract. The --hub-signature-* slots defaulted to a --hub-field-* family that no library declares, so they always fell through to their sys/ref fallbacks and a form themed with --hub-input-bg left its signature field untouched. They now read the canonical tokens of the .hub-field__* shell: --hub-input-* for the drawing surface, --hub-label-* for the label and --hub-form-disabled-opacity for the disabled state.

added: The eleven --hub-signature-* tokens are documented in the design-system token spec, so they now appear in the library reference table.

Version 22.0.0 - 8/14/26, 12:00 AM

added: Initial signature field release.

Связанные библиотеки

Часто задаваемые вопросы

Как получить рукописную подпись в форме Angular?

Установите ng-hub-ui-signature и поместите <hub-signature> в форму с formControlName либо привяжите через ngModel. Компонент реализует ControlValueAccessor, поэтому значение контрола — это сама подпись: строка SVG, которую можно хранить как текст. Валидатор required из Angular работает с ней, а valueChange отдаёт тот же SVG каждый раз, когда пользователь рисует или очищает поле.

Можно ли подписаться без мыши и без сенсорного экрана?

Да. Область рисования принимает клавиатуру: стрелки двигают перо на четыре логических пикселя, с Shift — на двадцать, Пробел или Enter опускает и поднимает его, Escape отменяет начатый штрих. Именно поэтому у canvas стоит role="application": стрелки нужны ему самому, а не странице.

Как получить PNG или SVG подписи, чтобы отправить на бэкенд?

Возьмите ссылку на компонент через шаблон и вызовите toDataUrl() — по умолчанию image/png — либо toSvg() для векторной разметки; toStrokes() вернёт точки в исходном виде. Храните SVG, если подпись когда-нибудь придётся открыть на редактирование: PNG обратно в поле загрузить нельзя.

Это юридически значимая электронная подпись?

Нет. ng-hub-ui-signature фиксирует только геометрию штриха: ни сертификата, ни метки времени, ни хеша подписываемого документа. Для квалифицированной подписи по eIDAS или ESIGN нужен сервис подписания, а это поле будет для него максимум способом нарисовать. К тому же одиночное касание оно дополняет до полноценного штриха, поэтому проверяйте не только required, если поле закрывает юридически значимое действие.