web-dev-qa-db-fra.com

Comment lier dans les deux sens mon propre sujet RxJS à un [(ngModel)]?

Existe-t-il un moyen simple et rapide de passer un RxJS Subject ou BehaviorSubject à une directive Angular 2 pour une liaison bidirectionnelle? Le long chemin à faire serait le suivant:

@Component({
    template: `
        <input type="text" [ngModel]="subject | async" (ngModelChange)="subject.next($event)" />
    `
})

J'aimerais pouvoir faire quelque chose comme ça:

@Component({
    template: `
        <input type="text" [(ngModel)]="subject" />
    `
})

Je crois que le tube async est à sens unique, donc cela ne suffit pas. Est-ce que Angular 2 fournit un moyen simple et rapide de procéder? Angular 2 utilise également RxJS, je m'attendais donc à une compatibilité inhérente.

Pourrais-je peut-être créer une nouvelle directive semblable à ngModel- afin de rendre cela possible?

23
mhelvens

J'ai commencé à chercher quelque chose comme ceci pour intégrer les contrôles de formulaire à ma bibliothèque ng-app-state . Si vous êtes du genre à aimer les codes très génériques, de type bibliothèque, continuez votre lecture. Mais attention, c'est long! En fin de compte, vous devriez pouvoir utiliser ceci dans vos modèles:

<input [subjectModel]="subject">

J'ai fait une validation de concept pour la première moitié de cette réponse, et la seconde moitié, je crois, est correcte, mais sachez qu'aucun des codes réellement écrits dans cette réponse n'a été testé. Je suis désolé, mais c'est le meilleur que j'ai à offrir pour le moment. :)

Vous pouvez écrire votre propre directive appelée subjectModel pour connecter un objet à un composant de formulaire. Vous trouverez ci-dessous les éléments essentiels, à l’exception des opérations de nettoyage. Il s'appuie sur l'interface ControlValueAccessor , de sorte que Angular inclut les adaptateurs nécessaires pour le connecter à tous les éléments de formulaire HTML standard, et cela fonctionnera avec tous les contrôles de formulaire personnalisés vous trouvez à l'état sauvage, tant qu'ils utilisent ControlValueAccessor (ce qui est la pratique recommandée).

@Directive({ selector: '[subjectModel]' })
export class SubjectModelDirective {
    private valueAccesor: ControlValueAccessor;

    constructor(
        @Self() @Inject(NG_VALUE_ACCESSOR)
        valueAccessors: ControlValueAccessor[],
    ) {
        this.valueAccessor = valueAccessors[0]; // <- this can be fancier
    }

    @Input() set subjectModel(subject: Subject) {
        // <-- cleanup here if this was already set before
        subject.subscribe((newValue) => {
            // <-- skip if this is already the value
            this.valueAccessor.writeValue(newValue);
        });
        this.valueAccessor.registerOnChange((newValue) => {
            subject.next(newValue);
        });
    }
}

Nous pourrions nous arrêter ici et vous pourrez l'écrire dans vos modèles:

<input [subjectModel]="subject" [ngDefaultControl]>

Ce supplément [ngDefaultControl] existe pour que manuellement angular fournisse la variable ControlValueAccessor nécessaire à notre directive. D'autres types d'entrées (comme les boutons radio et les sélections) nécessiteraient une directive supplémentaire différente. En effet, Angular n'attache pas automatiquement les accesseurs de valeur à chaque composant de formulaire, mais uniquement ceux qui ont également une variable ngModel, formControl ou formControlName.

Si vous voulez faire un effort supplémentaire pour éliminer le besoin de ces directives supplémentaires, vous devrez essentiellement les copier dans votre code, mais modifier leurs sélecteurs pour les activer pour votre nouvelle subjectModel. Ceci est la partie totalement non testée, mais je crois que vous pourriez faire ceci:

// This is copy-paste-tweaked from
// https://angular.io/api/forms/DefaultValueAccessor
@Directive({
    selector: 'input:not([type=checkbox])[subjectModel],textarea[subjectModel]',
    Host: {
        '(input)': '_handleInput($event.target.value)',
        '(blur)': 'onTouched()',
        '(compositionstart)': '_compositionStart()',
        '(compositionend)': '_compositionEnd($event.target.value)'
    },
    providers: [DEFAULT_VALUE_ACCESSOR]
})
export class DefaultSubjectModelValueAccessor extends DefaultValueAccessor {}

Le mérite de ma compréhension de ceci va à ngrx-forms , qui utilise cette technique.

2
Eric Simonton

Le plus proche auquel je puisse penser est d'utiliser un FormControl:

import { FormControl } from '@angular/forms';

@Component({
    template: '<input [formControl]="control">'
})
class MyComponent {
    control = new FormControl('');
    constructor(){
        this.control.valueChanges.subscribe(()=> console.log('tada'))
    }
}
2
Quentin F