Extending Angular 2 ngModel directive to use observables

ngmodel not working in angular 8
ngmodeloptions=(standalone: true)
ngmodelchange
two-way data binding in angular 7
angular two-way binding
angular observable two-way binding
ngmodel set value
angular 7 forms

Angular 2 ngModel directive works with variables and functions like

<input [ngModel]="myVar" (ngModelChange)="myFunc($event)" />

Instead of variables and functions, I would like to use BehaviorSubjects instead

<input [ngModel]="mySubject | async" (ngModelChange)="mySubject.next($event)" />

Is there a safe way to extend ngModel or use some kind of macro to reduce repetition in my templates?

<input [myNewNgModel]="mySubject" />

I don't know why you wouldn't just use reactive forms, but this was a fun puzzle. I created a directive that will alter the model value to the BehaviorSubject's value. And any changes will call .next on the BehaviorSubject for you.

Usage will look like this

<input type="text" [ngModel]="ngModelValue" appRxModel> 

Here is the stackblitz, enjoy

Extending Angular 2 ngModel directive to use observables, Angular 2 ngModel directive works with variables and functions like <input [ ngModel]="myVar" (ngModelChange)="myFunc($event)" /> Instead of variables and� When version 2 of Angular came out, it introduced us to observables. The Observable isn’t an Angular specific feature, but a new standard for managing async data that will be included in the ES7

Do you really want to create an observable for each input field in your form? The pattern I use is to have one observable for the model of the whole form, clone it for a view variable that you can then bind to and then have the submit handler of the form push the new model back to the service.

user$ = this.userService.user$;

save(user: User) {
  this.userService.save(user);
}

and in the view

<form *ngIf="user$ | async | clone as user" #userForm="ngForm" (submit)="userForm.form.valid && save(user)">
  <label>
    Firstname
    <input name="firstname" [(ngModel)]="user.firstname" required>
  </label>
  <label>
    Lastname
    <input name="lastname" [(ngModel)]="user.lastname" required>
  </label>
  <button>Save</button>
</form>

The clone pipe looks like this

export const clone = (obj: any) =>
  Array.isArray(obj)
    ? obj.map(item => clone(item))
    : obj instanceof Date
    ? new Date(obj.getTime())
    : obj && typeof obj === 'object'
    ? Object.getOwnPropertyNames(obj).reduce((o, prop) => {
        o[prop] = clone(obj[prop]);
        return o;
      }, {})
    : obj;

import { Pipe, PipeTransform } from '@angular/core';

import { clone } from './clone';

@Pipe({
  name: 'clone'
})
export class ClonePipe implements PipeTransform {

  transform(value: any): any {
    return clone(value);
  }
}

I have done a write up on this pattern with my state management library here. https://medium.com/@adrianbrand/angular-state-management-with-rxcache-468a865fc3fb

NgModel, This directive is used by itself or as part of a larger form. Use the ngModel selector to activate it. It accepts a domain model as an optional Input . If you have a one-� [(ngModel)] = "source" is a two-way binding using NgModel directive. We will use [(ngModel)] in HTML element where we set a specific element property and listen for an element change event . We will use two-way binding with NgModel in text box and select box in our example. [(ngModel)] can set only data-bound property.

I came up with a similar approached to @Adbel. Not sure about the inner implications of this, but it will be awesome to have some feedback. Stackbliz code

Your.component.ts

export class AppComponent  {
  email = new BehaviorSubject("UnwrappedMe 😱");

  emailHandler(input) {
    this.email.next(input);
  }
}

Your.component.html

 <form class="mx-3">
     <input [ngModel]="email | async" 
            (ngModelChange)="emailHandler($event)" 
            name="email" type="email" 
            id="email" placeholder="Enter email">
 </form>

 <p class="mx-3"> {{ email | async }} </p>

A little variation in case you need to get a ref to your input value and you do not want to make a second subscription (use template vars).

Your.component.html

 <form class="mx-3">
     <input [ngModel]="email | async" #emailref
            (ngModelChange)="emailHandler($event)" 
            name="email" type="email" 
            id="email" placeholder="Enter email">
 </form>

 <p class="mx-3"> {{ emailref.value }} </p>

Building a template-driven form, Bind form controls to data properties using the ngModel directive and two-way data-binding syntax. Examine how ngModel reports control states using CSS� Angular makes use of observables as an interface to handle a variety of common asynchronous operations. For example: You can define custom events that send observable output data from a child to a parent component. The HTTP module uses observables to handle AJAX requests and responses.

AngularJS to Angular concepts: Quick reference, In AngularJS, the ng-model directive binds a form control to a property in the controller associated with the template. This provides two-way binding, whereby any� To inspect the properties of the associated FormControl (like validity state), export the directive into a local template variable using ngModel as the key (ex: #myVar="ngModel"). You then access the control using the directive's control property, but most properties used (like valid and dirty ) fall through to the control anyway for direct access.

NgModelGroup, Use this directive to validate a sub-group of your form separately from the rest of <input name="first" [ngModel]="name.first" minlength="2"> <input name="last"� In fact, I've just abstracted all of this stuff to an abstract class which I now extend with every component I need to use ngModel. For me this is a ton of overhead and boilerplate code which I can do without. Edit: Here it is:

Cheat Sheet, List of components, directives, and pipes that belong to this module. imports: [ BrowserModule Use it to extend change detection by performing a custom check. Note: as mentioned by @Clouse24, "Using Reactive Froms with ngModel is deprecated in angular 6 and will be removed in a future version of Angular" (which means that the answer below will no longer be supported in the future). Please read the link to see the reasoning for deprecation and to see what alternatives you will have.

Comments
  • Sounds like you are looking for something like github.com/angular/angular/issues/4062. I'm sure this will come to Angular2 but only after release.
  • @GünterZöchbauer Good to know there are others trying to Rx everything. The main difference is that I'm trying to extend/reuse ngModel with observables while the proposal is focusing on binding events to observables.
  • Is this something that was eventually solved? I am happy to write up a response but currently I struggle to understand whether you actually want to use these inputs as part of a form? If so, have you thought of listening to the form observable? If not, can you please give some background as to how you are using your BehaviorSubject? There is certainly use cases for it, but when new to RxJS, we tend to overuse Subjects a little.
  • There are 2 goals here: 1. Reduce boilerplate code used when dealing with observables such as myObservable.subscribe((myVar) => this.myVar = myVar) and myObserable | async 2. Learn how to extend Angular objects. I don't understand how decorators work and how to extend decorated objects. I'm picking ngModel to illustrate the problem I'm trying to solve but it's not really about ngModel at all.
  • Would a pipe be the ideal solution here? You'd keep the concerns separated in regards to updating a property and emitting a value. Otherwise, it would cause you to have to subscribe to the event to set the value back on the original property... Work experiment: try creating a simple component that will give you the best of both worlds without having to wire up more than you'd wire up using a ngModel.
  • Do not link to external resources; instead, explain how to solve the problem here
  • The op's question already has an explanation. The "external" solution is code converted into a directive.
  • This will work great! Only problem is that it is not as re-usable. Imagine you had 5 more inputs, that would mean 5 more behavior subjects you need to add to AppComponent and 5 other methods to handle their changes. You could potentially improve this method to become a bit more efficient, but my approach encapsulates all of that way in a directive for you.