export interface Kamoulox {
  canard: string;
  beaujolais: number;
  mitterrand: string;
  pressing: string;
}	

import { Component, OnInit } from "@angular/core";
import { FormGroup, FormControl } from "@angular/forms";

@Component({
  selector: "kmx-form",
  template: `
    <form [formGroup]="form">
      <div class="form-group">
        <label>canard</label>
        <input formControlName="canard" />
      </div>
      <div class="form-group">
        <label>beaujolais</label>
        <input formControlName="beaujolais" type="number" />
      </div>
      <div class="form-group">
        <label>mitterrand</label>
        <select formControlName="mitterrand">
          <option>parapluie</option>
          <option>homard</option>
        </select>
      </div>
      <div class="form-group">
        <label>pressing</label>
        <input formControlName="pressing" />
      </div>
      <button>Kamoulox !</button>
    </form>
  `
})
export class FormComponent {
  form = new FormGroup({
    canard: new FormControl(),
    beaujolais: new FormControl(),
    mitterrand: new FormControl(),
    pressing: new FormControl()
  });
}	

export type FieldConfig = {
  label?: string;
  visible?: boolean;
};	

export function mitterrandRule(beaujolais) {
    const visible = beaujolais > 100;
    return { config: { visible } };
}
export function pressingRule(beaujolais, mitterrand) {
    const label =
      beaujolais === 101 && mitterrand === "homard"
      ? "youpi"
      : "pressing";
    return { config: { label } };
}
export function beaujolaisRule(canard) {
    if (canard === "lucca") return { beaujolais: 101, config: { disabled: true } };
    return {};
}	

 // Kamoulox défini plus tôt
import { Kamoulox } from "./kamoulox.service";
import { BehaviorSubject, Observable } from "rxjs";
import { map } from "rxjs/operators";

export type FieldConfig = {
  label?: string;
  visible?: boolean;
};
export type FieldConfig$ = {
  [key in keyof FieldConfig]: Observable<FieldConfig[key]>;
};
type Config<T> = {
  [key in keyof T]: FieldConfig;
};
export type KamouloxConfig = Config<Kamoulox>;
export class KamouloxConfigSubject extends BehaviorSubject<KamouloxConfig> {
  // Helpers pour accéder aux configs spécifiques d'un champ
  canard: FieldConfig$ = {
    label: this.pipe(map((c) => c.canard.label)),
    visible: this.pipe(map((c) => c.canard.visible)),
  };
  // ... (idem pour chaque champ)
  // Note : pourrait être géré dans un proxy
  constructor(config: KamouloxConfig) {
    super(config);
  }
  // Mise à jour d'une partie de la configuration
  update<K extends keyof Kamoulox>(key: K, value: Partial<FieldConfig>) {
    this.next({ ...this.value, [key]: { ...this.value[key], ...value } });
  }
}	

import { BehaviorSubject } from "rxjs";
import { Kamoulox } from "./kamoulox.service";

export class KamouloxSubject extends BehaviorSubject<Kamoulox> {
  // Mise à jour d'une propriété de l’objet
  change<K extends keyof Kamoulox>(key: K, value: Kamoulox[K]) {
    this.next({ ...this.value, [key]: value });
  }
}	

// les données sont écoutées pour remplir le formulaire 
this.kamoulox$.subscribe(kamoulox =>
  this.form.patchValue(kamoulox, { emitEvent: false })
);
// le formulaire est écouté pour mettre à jour les données
this.form.valueChanges.subscribe(kamoulox => this.kamoulox$.next(kamoulox));	

    <form [formGroup]="form">
      <div class="form-group" *ngIf="config$.mitterrand.visible | async">
        <label>mitterrand</label>
        <select formControlName="mitterrand">
          <option>parapluie</option>
          <option>homard</option>
        </select>
      </div>
      <!-- Autres champs ... -->
      <button class="button">Kamoulox !</button>
    </form>	

@Injectable({ providedIn: "root" })
export class KamouloxService {
  // initialisation de la config avec des valeurs par défaut
  config$ = new KamouloxConfigSubject({
    canard: { visible: true },
    beaujolais: { visible: true },
    mitterrand: { visible: true },
    pressing: { visible: true, label: "pressing" },
  });
  // initialisation des données (pourrait être issu d’une api)
  kamoulox$ = new KamouloxSubject({
    canard: "",
    beaujolais: 0,
    mitterrand: "parapluie",
    pressing: "",
  });

  constructor() {}
// ...
}	

  private mergeRules(): Observable<never> {
    const mitterrand = this.feedRule({
      keys: ["beaujolais", "canard"],
      rule: mitterrandRule,
    }).pipe(
      map(({ config }) => {
        this.config$.update("mitterrand", config);
      })
    );

    const pressing = this.feedRule({
      keys: ["beaujolais", "mitterrand"],
      rule: pressingRule,
    }).pipe(
      map(({ config, pressing }) => {
        config && this.config$.update("pressing", config);
        pressing && this.kamoulox$.change("pressing", pressing);
      })
    );

    const beaujolais = this.feedRule({
      keys: ["canard"],
      rule: beaujolaisRule,
    }).pipe(
      map(({ beaujolais }) => {
        beaujolais && this.kamoulox$.change("beaujolais", beaujolais);
      })
    );

    return merge(mitterrand, pressing, beaujolais).pipe(ignoreElements());
  }	

  private feedRule<K extends keyof Kamoulox>({ keys, rule }: {
    keys: Array<K>;
    rule: RuleFunction;
  }): Observable<
    { config?: Partial<FieldConfig>; } & Partial<Kamoulox>
  > {
    return this.kamoulox$.pipe(
      map((kamoulox) => keys.map((key) => kamoulox[key])),
      distinctUntilChanged((x, y) => JSON.stringify(x) === JSON.stringify(y)),
      map(kamouloxValues => rule(...kamouloxValues)),
    );
  }