# grcm-components

> ⚡ Crea formularios dinámicos en Angular de forma rápida, limpia y reutilizable.

<br>

---

## ✨ ¿Por qué usar este paquete?

- ¿Cansado de escribir formularios con `FormBuilder` y `FormGroup` 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`.

<br>

---

## 📦 Instalación

```bash
npm i grcm-components
```

<br>

---

## ⚙️ Componentes disponibles

- **`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 propios `form-field` y componentes de control personalizados.


<br>

---

## 🚀 Ejemplo básico

```ts
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 })
  }
}

```
<br>

---

## 🔁 Enviar el formulario con múltiples acciones (submitters)

Si tu formulario tiene más de un botón para distintas acciones:

### Usando `lib-grcm-button`:

```html
<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>
```

### Usando botones normales:

```html
<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>
```

### En el componente:

```ts
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
  }
}
```

<br>

---

## 🧩 Templates personalizados

Puedes insertar templates personalizados antes o después del formulario completo o de campos individuales:

```html
<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>
```

<br>

---

## 🧱 Crear tu propio `form-field`

```ts
// 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();
  }
}

```

> ⚠️ **Nota**
> 
> Si 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 tu `form-field` personalizado.


<br>

---

## ✏️ Crear tu propio componente de input

```ts
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;
  }
}
```

<br>

---

## 🧪 Ejemplo final usando `CustomFormFieldComponent` y `CustomInputComponent`

```ts
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 })
  }
}
```

<br>

---

## 🧠 Conclusión

`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.
