23

Before implementing a solution on my own I would like to know if there is a simple manner to change the style of an element (a brief highlight) when the data bound property value has just change.

There are lot of elements in my DOM so I do not not want to store and maintain a dedicated property in component.

My elements to highlight are traditional input form's elements:

    <tr field label="Lieu dépôt">
        <select class="cellinput" #lieuDepotBon [(ngModel)]="rapport.LieuDepotBon" (ngModelChange)="changeRapport({LieuDepotBon:$event})">
            <option [ngValue]="null"></option>
            <option [ngValue]="i" *ngFor="let depotBonChoice of DepotBonInterventionValues; let i = index">{{DepotBonIntervention[i]}}</option>
        </select>
    </tr>
    <tr field *ngIf="rapport.LieuDepotBon==DepotBonIntervention.Autre" label="Autre lieu">
        <input class="cellinput" #autreLieuDepotBon [(ngModel)]="rapport.AutreLieuDepotBon" (ngModelChange)="changeRapport({AutreLieuDepotBon:autreLieuDepotBon.value})" />
    </tr>

I heard about special class styles set by Angular2 on element with ngModel directive that could help do what I need but I could not find more about it.

Anthony Brenelière
  • 60,646
  • 14
  • 46
  • 58
  • 2
    is this question only specific to `input` elements? and what do you mean by `changed`? _I heard about special class styles set by Angular2_ - do you mean `ng-dirty` class? if so, try simply adding a style for `input.ng-dirty {background-color: green}` – Max Koretskyi May 18 '17 at 08:15
  • It is specific to elements with a ngModel directive. It seems ng-dirty/ng-touched does not provide a solution because they depend on a user's action on the control. In my case changes are not performed by the user. It's juste a change in the data model. – Anthony Brenelière May 18 '17 at 08:34
  • 1
    _In my case changes are not performed by the user._ - can you show an example? – Max Koretskyi May 18 '17 at 08:43
  • Changes are perfomed by a merge of data structures, I use lodash for that. Here is an exemples Lodash.merge( updatedInter, newData ). My form controls are bound to some data of those merged structures. – Anthony Brenelière May 18 '17 at 08:54
  • well, maybe you can put up a simple plunker? – Max Koretskyi May 18 '17 at 08:58
  • Maybe Painf flashing of Chrome helps already? https://developers.google.com/web/fundamentals/performance/rendering/simplify-paint-complexity-and-reduce-paint-areas – Flowlicious May 18 '17 at 09:45
  • Do you use ng-repeat to show this merged data ? – Gaurav May 31 '17 at 05:55
  • You need to give a plnkr – Vamshi Jun 11 '17 at 10:57
  • Maybe you can use `valueChanges: Observable`. _(Emits an event every time the value of the control changes, in the UI or programmatically.)_ Check [Angular Documentation](https://angular.io/api/forms/AbstractControl#valueChanges) for more info. – lucascurti Jun 14 '17 at 20:34
  • There's a good article with a complete solution to this here: https://ngmilk.rocks/2015/12/18/animate-elements-when-a-model-changes-in-angularjs/ – I used this in the past to do what you're describing here. – Matt Andrews Dec 05 '17 at 10:54

3 Answers3

5

The easiest and cleaner way I can think of is to implement 2 css classes like so:

.highlight{
    background-color: #FF0;
}
.kill-highlight{
    background-color: #AD310B;
    -webkit-transition: background-color 1000ms linear;
    -ms-transition: background-color 1000ms linear;
    transition: background-color 1000ms linear;
}

and then affect both of them successively to the element. hope that helps

Taha Zgued
  • 1,088
  • 12
  • 18
2

Here is my solution.

I wanted to highlight the datas in the Form that are changed by other users in real-time.

In my HTML form, I replaced native html elements by Angular components. For each type of native element I created a new Angular Component with Highlight support. Each component implements the ControlValueAccessor Angular interface.

In the parent form I replaced the native element:

<input [(ngModel)]="itinerary.DetailWeather" />

by my custom element:

<reactive-input [(ngModel)]="itinerary.DetailWeather"></reactive-input>

When Angular calls detectChanges() for the parent form, it does check all the datas that are used as inputs by the components of the form.

If a component is a ControlValueAccessor, and a change occurred in the application model, it does call the method ControlValueAccessor.writeValue( value ). It is the method that is called when the data changed in memory. I use it as a hook to update temporarily the style to add the highlight.

Here is the custom element. I used Angular Animations for updating the border color and fade back to the original color.

import { Component, Input, forwardRef, ChangeDetectorRef } from '@angular/core';
import { ControlValueAccessor,  NG_VALUE_ACCESSOR  } from '@angular/forms';
import { trigger, state, style, animate, transition, keyframes } from '@angular/animations';

@Component(
{
  selector: 'reactive-input',
  template: `<input class="cellinput" [(ngModel)]="value" [@updatingTrigger]="updatingState" />`,
  styles: [`.cellinput {  padding: 4px }`],
  animations: [
    trigger( 
      'updatingTrigger', [
        transition('* => otherWriting', animate(1000, keyframes([
          style ({ 'border-color' : 'var( --change-detect-color )', offset: 0 }),
          style ({ 'border-color' : 'var( --main-color )', offset: 1 })
        ])))
    ])
  ],
  providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ReactiveInputComponent), multi: true } ]
})
export class ReactiveInputComponent implements ControlValueAccessor {

  public updatingState : string = null;
  _value = '';

  // stores the action in the attribute (onModelChange) in the html template:
  propagateChange:any = ( change ) => {};

  constructor( private ref: ChangeDetectorRef ) { }

  // change from the model
  writeValue(value: any): void
  {
    this._value = value; 
    this.updatingState = 'otherWriting';

    window.setTimeout( () => {
      this.updatingState = null;
    }, 100 );

    // model value has change so changes must be detected (case ChangeDetectorStrategy is OnPush)
    this.ref.detectChanges();
  }

  // change from the UI
  set value(event: any)
  {
    this._value = event;
    this.propagateChange(event);
    this.updatingState = null;
  }

  get value()
  {
    return this._value;
  }

  registerOnChange(fn: any): void { this.propagateChange = fn; }
  registerOnTouched(fn: () => void): void {}
  setDisabledState?(isDisabled: boolean): void {};
}
Anthony Brenelière
  • 60,646
  • 14
  • 46
  • 58
1

To highlight DOM element for a moment, use setTimeout() to add or remove CSS class

check demo

HTML

<mat-form-field [ngClass]="{'changed': isChanged}">
  <mat-select [(ngModel)]="yourModel" (ngModelChange)="modelChanged($event)">
     <mat-option value="1">1</mat-option>
     <mat-option value="2">2</mat-option>
     <mat-option value="3">3</mat-option>
  </mat-select>
</mat-form-field>

Typescript

isChanged: boolean = false
modelChanged(value) {
   console.log('model changed')

   this.isChanged = true
   setTimeout(() => {
       this.isChanged = false
   }, 1000);
}

CSS

.changed {
    transition: color 0.4s ease-in, text-shadow 0.4s ease-in, background-color 0.5s linear 0s;
    text-shadow: #bbb 2px 2px 2px;
    background-color: #ffffcc;
    color: #BF1722;
}

Note: if your application changes in milliseconds then you should reduce the time for setTimeout() to 0.5s or 0.3s as per your application requirement.

Thanks to Ingo Bürk for pointing-out this issue

Community
  • 1
  • 1
WasiF
  • 26,101
  • 16
  • 120
  • 128
  • 2
    This will break down horribly when multiple updates are coming in. – Ingo Bürk Aug 13 '18 at 14:52
  • can you please explain what type of horrible break downs can occur? – WasiF Aug 13 '18 at 17:33
  • if you got 3updates in the same time, it will update the first one, wait 1s, trigger the second one, wait 1s, trigger the third – sheplu Aug 13 '18 at 17:37
  • When an element is updated, it highlights that element for 1s for the user to show update success. – WasiF Aug 13 '18 at 17:53
  • I think you misunderstood or I am not understanding your point. if you can modify, please do it, make it better, I want to learn from you. How you would make it better for the user to indicate change success – WasiF Aug 13 '18 at 17:55
  • 1
    Imagine modelChange being called at t=0s and again and t=990ms. After the second call, the timeout from the first call will set `isChanged` to false after just 10ms instead of one second since the consecutive timeouts are independent and racing against each other. If you want to learn to improve it you should ask a question yourself. – Ingo Bürk Aug 13 '18 at 18:08
  • Your objection is valid and its solution is if your model changes quickly then you should change **1.0s** to **0.5s** or **0.3s**. It depends on application requirement. – WasiF Aug 13 '18 at 19:00