⚡ Crea formularios dinámicos en Angular de forma rápida, limpia y reutilizable.
- ¿Cansado de escribir formularios con
FormBuilder
yFormGroup
una y otra vez? - ¿Cansado de escribir en el template todos los campos, clases y estructura cada vez que necesitas un formulario?
- ¿Leíste esto con voz de anuncio? 😉
Con grcm-components
, puedes definir tus formularios dinámicamente desde el archivo .ts
, usando un solo componente de formulario configurable. Además, soporta cualquier componente personalizado que implemente ControlValueAccessor
.
npm i grcm-components
-
lib-grcm-form
: Componente principal para construir formularios dinámicos. -
lib-grcm-input
: Input simple ya listo para usarse. -
lib-grcm-button
: Botón de ejemplo. -
lib-grcm-form-field
/lib-grcm-plain-form-field
: Ejemplos de 'Wrappers' para campos de formulario. -
lib-grcm-example-login
: Un formulario de un login simple de ejemplo.
💡 Nota
Los componentes de ejemplo incluidos usan estilos de Bootstrap.
Sin embargo, no necesitas Bootstrap si usas tus propiosform-field
y componentes de control personalizados.
import { Component } from '@angular/core';
import { Validators } from '@angular/forms';
import {
GrcmFormComponent,
GrcmInputComponent,
GrcmFormFieldComponent,
GrcmButtonComponent,
GrcmDataForm,
ResponseGrcmForm,
createGrcmControl
} from 'grcm-components';
@Component({
selector: 'app-login-page',
standalone: true,
imports: [GrcmFormComponent, GrcmButtonComponent],
template: `
<div class="vh-100 d-flex justify-content-center align-items-center">
<lib-grcm-form
[data]="dataForm"
[templates]="{ afterFormTemplate }"
(formSubmit)="submit($event)">
<ng-template #afterFormTemplate let-form="form">
<lib-grcm-button
[disabled]="form.invalid"
type="submit"
class="w-100 mt-2"
label="Entrar">
</lib-grcm-button>
</ng-template>
</lib-grcm-form>
</div>`,
})
export default class LoginPageComponent {
protected dataForm: GrcmDataForm = {
form: {
id: 'login'
},
controls: {
username: createGrcmControl({
component: GrcmInputComponent,
formFieldComponent: GrcmFormFieldComponent,
value: "",
validators: [Validators.required, Validators.minLength(3), Validators.maxLength(10)],
args: {
control: {
placeholder: "Escriba su usuario",
icon: "person-circle",
autocomplete: false
},
formField: {
label: "Usuario"
}
},
classes: {
control: "input-group"
}
}),
password: createGrcmControl({
component: GrcmInputComponent,
formFieldComponent: GrcmFormFieldComponent,
value: "",
validators: [Validators.required, Validators.minLength(6), Validators.maxLength(15)],
args: {
control: {
type: 'password',
placeholder: "Escriba su contraseña",
icon: "lock",
autocomplete: false,
},
formField: {
label: "Contraseña"
}
},
classes: {
control: "input-group"
}
}),
}
};
submit(data: ResponseGrcmForm) {
const {username, password} = data.form.value;
console.log({ username, password })
}
}
Si tu formulario tiene más de un botón para distintas acciones:
<lib-grcm-form [data]="dataForm" [templates]="{ afterFormTemplate }" (formSubmit)="submit($event)">
<ng-template #afterFormTemplate let-form="form">
<lib-grcm-button type="submit" submitter="create-room" label="Crear sala"></lib-grcm-button>
<lib-grcm-button type="submit" submitter="join-room" label="Unirse"></lib-grcm-button>
</ng-template>
</lib-grcm-form>
<lib-grcm-form [data]="dataForm" [templates]="{afterFormTemplate}" style="width: 100%;"
(formSubmit)="submit($event)" class="d-block">
<ng-template #afterFormTemplate let-form="form">
<div class="d-flex justify-content-between mt-2">
<button type="submit" data-submitter="create-room">Crear sala</button>
<button type="submit" data-submitter="join-room">Unirse a la sala</button>
</div>
</ng-template>
</lib-grcm-form>
submit(data: ResponseGrcmForm) {
const action = data.submitter;
if (action === 'create-room') {
// lógica para crear sala
} else if (action === 'join-room') {
// lógica para unirse
}
}
Puedes insertar templates personalizados antes o después del formulario completo o de campos individuales:
<lib-grcm-form [data]="dataForm" [templates]="{afterFormTemplate, beforeFormTemplate, before_username, after_username}" (formSubmit)="submit($event)">
<ng-template #beforeFormTemplate let-form="form">
Esto aparecerá al principio del formulario.
</ng-template>
<ng-template #before_username let-form="form" let-control="control">
Esto aparecerá antes del campo de username.
</ng-template>
<ng-template #after_username let-form="form" let-control="control">
Esto aparecerá después del campo de username.
</ng-template>
<ng-template #afterFormTemplate let-form="form">
Esto aparecerá al final del formulario.
</ng-template>
</lib-grcm-form>
// custom-form-field.component.ts
import { CommonModule } from '@angular/common';
import { Component, input, OnDestroy, viewChild, ViewContainerRef } from '@angular/core';
import { AbstractControl } from '@angular/forms';
import { GrcmFormField } from 'grcm-components';
@Component({
selector: 'custom-form-field',
imports: [CommonModule],
template: `
<label [for]="id()">{{ label() }}</label>
<ng-container #controlView></ng-container>
@let errors = control().errors;
<p style="color: red;">{{errors | json}}</p>
`
})
export class CustomFormFieldComponent implements GrcmFormField, OnDestroy {
public controlView = viewChild.required('controlView', { read: ViewContainerRef });
public control = input.required<AbstractControl>();
public id = input.required<string>();
// Aquí puedes añadir las propiedades que necesites:
public label = input<string>('');
ngOnDestroy(): void {
this.controlView().clear();
}
}
⚠️ NotaSi modificas un
form-field
personalizado, es posible que los cambios no se reflejen correctamente con recarga en caliente (HMR).Para evitar errores de renderizado, puedes optar por una de estas opciones:
- Reiniciar la aplicación.
- Ejecutar
ng serve --no-hmr
para desactivar HMR temporalmente mientras estés modificando tuform-field
personalizado.
import { Component, computed, signal, inject } from '@angular/core';
import { ControlValueAccessor } from '@angular/forms';
import { GrcmControlComponent } from 'grcm-components';
@Component({
selector: 'custom-input',
standalone: true,
template: `
<input
type="text"
[attr.id]="id()"
[attr.disabled]="disabled() ? true : null"
class="form-control"
[class.is-valid]="control()?.valid && control()?.touched"
[class.is-invalid]="control()?.invalid && control()?.touched"
[value]="value()"
(input)="change(input.value)"
(blur)="onBlur()"
#input/>`
})
export class CustomInputComponent implements ControlValueAccessor {
private readonly _controlComp = inject(GrcmControlComponent);
public control = computed(() => this._controlComp.control());
public id = computed(() => this._controlComp.id());
private _value = signal<string>('');
private _disabled = signal<boolean>(false);
protected value = this._value.asReadonly();
protected disabled = this._disabled.asReadonly();
change(value: string) {
this._onTouched();
this._onChange(value);
}
onBlur() {
this._onTouched();
}
_onChange: Function = () => { };
_onTouched: Function = () => { };
writeValue(value: unknown): void {
if (typeof value === 'string') this._value.set(value);
}
setDisabledState(isDisabled: boolean): void {
this._disabled.set(isDisabled);
}
registerOnChange(fn: Function): void {
this._onChange = fn;
}
registerOnTouched(fn: Function): void {
this._onTouched = fn;
}
}
import { Component } from '@angular/core';
import { Validators } from '@angular/forms';
import {
GrcmFormComponent,
GrcmButtonComponent,
GrcmDataForm,
ResponseGrcmForm,
createGrcmControl
} from 'grcm-components';
import { CustomFormFieldComponent } from './custom-form-field.component';
import { CustomInputComponent } from './custom-input.component';
@Component({
selector: 'custom-form-page',
standalone: true,
imports: [GrcmFormComponent, GrcmButtonComponent],
template: `
<div class="vh-100 d-flex justify-content-center align-items-center">
<lib-grcm-form [data]="dataForm" (formSubmit)="submit($event)" [templates]="{afterFormTemplate}">
<ng-template #afterFormTemplate let-form="form">
<lib-grcm-button type="submit" label="Enviar" class="w-100 mt-2"></lib-grcm-button>
</ng-template>
</lib-grcm-form>
</div>`,
})
export default class CustomFormPageComponent {
protected dataForm: GrcmDataForm = {
form: { id: 'custom-form' },
controls: {
myCustomField: createGrcmControl({
component: CustomInputComponent,
formFieldComponent: CustomFormFieldComponent,
value: '',
validators: [Validators.required],
args: {
control: {},
formField: {
label: 'Mi campo personalizado'
}
}
})
}
};
submit(data: ResponseGrcmForm) {
const { myCustomField } = data.form.value;
console.log({ myCustomField })
}
}
grcm-components
es una solución flexible y extensible para reducir la repetición en formularios de Angular. Define la estructura en tu .ts
, usa tus propios inputs o campos, y gana en mantenibilidad y claridad.