Attribute directive with ngModel to change field value

32,126

Solution 1

update

This approach doesn't work properly. See @RyanHow's answer for a better solution.

original

@Directive({ 
  selector: '[ngModel][uppercase]',
  providers: [NgModel],
  host: {
    '(ngModelChange)' : 'onInputChange($event)'
  }
})
export class UppercaseDirective{
  constructor(private model:NgModel){}

  onInputChange(event){
    this.model.valueAccessor.writeValue(event.toUpperCase());
  }
}

Plunker

Solution 2

Although Günter's answer looks promising, there is a bug in that the final value in the model has the last entered letter in lowercase.

See here:

https://plnkr.co/edit/SzxO2Ykg2pKq1qfgKVMH

Please use the answer provided in the question. It works correctly.

@Directive({ 
selector: '[ngModel][uppercase]',
host: {
"(input)": 'onInputChange($event)'
    }
})
export class UppercaseDirective{
@Output() ngModelChange:EventEmitter<any> = new EventEmitter()
value: any

onInputChange($event){
    this.value = $event.target.value.toUpperCase()
    this.ngModelChange.emit(this.value)
    }
}

https://plnkr.co/edit/oE3KNMCG7bvEj8FV07RV

Solution 3

I have faced the same issue, where I need to create the custom select in Angular with select2. I have created following directive thing to achieve this with attribute directive and ngModel.

import {ElementRef, Directive, EventEmitter, Output, Input} from '@angular/core';
import {NgModel} from "@angular/forms";
declare let $;
@Directive({
  selector: '[custom-select]',
  providers: [NgModel]
})
export class CustomSelectComponent{
  $eventSelect:any;
  @Output() ngModelChange:EventEmitter<any> = new EventEmitter();
  @Input() set ngModel(value:any){
    //listen to the input value change of ngModel and change in the plugin accordingly.
    if(this.$eventSelect){
      this.$eventSelect.val(value).trigger('change',{fromComponent:true});
    }
  }
  constructor(private elementRef: ElementRef) {}
  ngOnInit(){
    this.$eventSelect = $(this.elementRef.nativeElement);
    this.$eventSelect.select2({minimumResultsForSearch:-1});
    this.$eventSelect.on("change.select2", (event,data)=> {
      //listen to the select change event and chanage the model value 
      if(!data || !data.fromComponent){ //dont change model when its chagned from the input change event
        this.ngModelChange.emit(this.$eventSelect.val());
      }
    });
  }
}

with following usage

<select custom-select [(ngModel)]="protocol.type">
  <option value="1">option1</option>
  <option value="1">option2</option>
</select>
Share:
32,126

Related videos on Youtube

majodi
Author by

majodi

Updated on July 09, 2022

Comments

  • majodi
    majodi almost 2 years

    I want to change (force) input field values while typing using a attribute Directive. With it I would like to create directives like uppercase, lowercase, maxlength, filterchar, etc. to be used on input fields on forms. I found this example: Angular 2 Attribute Directive Typescript Example but this doesn't seem to work. Maybe it did for an earlier build of Angular2. It is however exactly what I would like to do.

    When I create a directive like this:

    import {Directive} from 'angular2/core';
    import {NgModel} from 'angular2/common';
    
    @Directive({ 
    selector: '[ngModel][uppercase]', 
    host: {
        '(input)' : 'onInputChange()'
          }
    })
    export class UppercaseDirective{
    
    constructor(public model:NgModel){}
    
    onInputChange(){
        var newValue = this.model.value.toUpperCase();
        this.model.valueAccessor.writeValue(newValue);
        this.model.viewToModelUpdate(newValue);
        }
    }
    

    And use it on a form like this:

    <input type="text" class="form-control" [(ngModel)]="field.name" ngControl="name" #name="ngForm" required uppercase>
    

    (and register NgModel as a provider). I get an

    undefined this.model.value.

    I can use $event.target.value = $event.target.value.toUpperCase() (when passing $event with the onInputChange()) and that works for the view (it does show the input as uppercase. But it doesn't update the bind field "field.name".

    So how to create an Angular2 attribute directive that does this?

    -- EDIT --

    After some further investigation I managed to get what I want. The answer Günter provided is closer to my original intention and perhaps better. But here is another way:

    import {Directive, Input, Output, EventEmitter} from 'angular2/core';
    
    @Directive({ 
    selector: '[ngModel][uppercase]',
    host: {
    "(input)": 'onInputChange($event)'
        }
    })
    export class UppercaseDirective{
    @Output() ngModelChange:EventEmitter<any> = new EventEmitter()
    value: any
    
    onInputChange($event){
        this.value = $event.target.value.toUpperCase()
        this.ngModelChange.emit(this.value)
        }
    }
    

    As I said I'm not sure if this is also a good way to do this so comments are welcome.

  • majodi
    majodi about 8 years
    This is exactly what I was trying to do Günter, thnx! This really makes sense. In the mean time I found another way of doing this. I will edit my question with this variation. I'm not sure if this is also a good way. Maybe you could have a look at it.
  • Günter Zöchbauer
    Günter Zöchbauer about 8 years
    Sure, just write a comment after you added your answer, so I get notified.
  • Günter Zöchbauer
    Günter Zöchbauer about 8 years
    I tried the @Output() ngModelChange:EventEmitter to set the value but this didn't work for me :D. I think using ngModelChange for the @Input() has the advantage that it works for all kinds of input elements that are covered by ngModel and also with browsers where different events are used (there are currently issues with select and radio inputs because of this - at least when the ngModel issues are fixed. I guess I'd like a combination of my @Input() and your @Output() best if it is actually working.
  • Günter Zöchbauer
    Günter Zöchbauer about 8 years
    I tried the combination but that causes endless loops :-/
  • majodi
    majodi about 8 years
    I will experiment a bit more using both options. For now I'm happy to have learned enough to get it right somehow. thnx.
  • Günter Zöchbauer
    Günter Zöchbauer almost 8 years
    My Plunker seems to work fine. How can I reproduce the issue you mention?
  • Ryan How
    Ryan How almost 8 years
    In your plunker, if you put a binding eg. <h2>Hello {{field.name}}</h2>. It has a lowercase last letter.
  • Ryan How
    Ryan How almost 8 years
    Also, in angular rc1 updating the model triggers the ngModelChange event to trigger again and you get an infinite loop.
  • Ryan How
    Ryan How almost 8 years
    And in respect to the original question (which I can't comment on :/), in rc1 the initial code would work if using $event.target.value.toUpperCase() because updating the model emits the model change event. Seems a few things have changed!
  • westor
    westor almost 8 years
    @RyanHow: The plnkrs are both not running for me. I experimented with that approach, and it's working. But what can I do, if I want to have original input values in input field, but modified values in model? With this approach I still have "feedback" from the model back to the input.
  • Ryan How
    Ryan How almost 8 years
    @westor: Not sure why they aren't working. They don't even load for me now :(. You can use getters and setters on your model to give the effect of formatters and parsers which angular2 supports out of the box. Not sure how to do it from an attribute directive though. I have the same problem but haven't looked into it since I got this part working. Need some experts here hey!
  • Kody
    Kody about 7 years
  • MeVimalkumar
    MeVimalkumar about 6 years
    @RyanHow it works for me partially. it updated my textbox value but it also gives me an error 'ExpressionChangedAfterItHasBeenCheckedError' could pls tell me why this would probably be occurred
  • Ryan How
    Ryan How about 6 years
    @Vicky Sorry, Angular has changed so much since here and I suspect this doesn't work anymore.
  • Abhijeet
    Abhijeet about 6 years
    Thanks for your help .. nice solution :)