web-dev-qa-db-ger.com

Wie wird auf Wertänderungen aus der Klasseneigenschaft TypeScript - Angular gewartet?

In AngularJS können Sie Variablenänderungen mit $watch, $digest... abhören, was mit den neuen Versionen von Angular (5, 6) nicht mehr möglich ist.

In Angular ist dieses Verhalten nun Teil des Komponentenlebenszyklus.

Ich habe die offizielle Dokumentation, Artikel und insbesondere die Angular 5-Änderungserkennung für veränderliche Objekte geprüft, um herauszufinden, wie man eine Variable (Klasseneigenschaft) in einer TypeScript-Klasse abhört / Angular

Was heute vorgeschlagen wird, ist: 

import { OnChanges, SimpleChanges, DoCheck } from '@angular/core';


@Component({
  selector: 'my-comp',
  templateUrl: 'my-comp.html',
  styleUrls: ['my-comp.css'],
  inputs:['input1', 'input2']
})
export class MyClass implements OnChanges, DoCheck, OnInit{

  //I can track changes for this properties
  @Input() input1:string;
  @Input() input2:string;
  
  //Properties what I want to track !
  myProperty_1: boolean
  myProperty_2: ['A', 'B', 'C'];
  myProperty_3: MysObject;

  constructor() { }

  ngOnInit() { }

  //Solution 1 - fired when Angular detects changes to the @Input properties
  ngOnChanges(changes: SimpleChanges) {
    //Action for change
  }

  //Solution 2 - Where Angular fails to detect the changes to the input property
  //the DoCheck allows us to implement our custom change detection
  ngDoCheck() {
    //Action for change
  }
}

Dies gilt nur für @Input()-Eigenschaft! 

Wenn ich Änderungen der eigenen Eigenschaften meiner Komponente (myProperty_1, myProperty_2 oder myProperty_3) verfolgen möchte, funktioniert dies nicht.

Kann mir jemand helfen, dieses Problem zu lösen? Vorzugsweise eine Lösung, die mit Angular 5 kompatibel ist

6
Lyes CHIOUKH

Sie können die Wertänderung der Feldelemente der Komponenten weiterhin über KeyValueDiffers über DoCheck lifehook überprüfen.

import { DoCheck, KeyValueDiffers, KeyValueDiffer } from '@angular/core';

differ: KeyValueDiffer<string, any>;;
constructor(private differs: KeyValueDiffers) {
  this.differ = this.differs.find({}).create();
}

ngDoCheck() {
  const change = this.differ.diff(this);
  if (change) {
    change.forEachChangedItem(item => {
      console.log('item changed', item);
    });
  }
}

siehe demo.

4
Pengyy

Ich denke, die schönste Lösung für Ihr Problem ist die Verwendung eines Dekorators, der das ursprüngliche Feld automatisch durch eine Eigenschaft ersetzt. Anschließend können Sie auf dem Setter ein SimpleChanges-Objekt erstellen, das dem von angle erstellten ähnlich ist, um denselben Benachrichtigungsrückruf wie für zu verwenden eckig (alternativ können Sie eine andere Schnittstelle für diese Benachrichtigungen erstellen, es gilt jedoch dasselbe Prinzip)

import { OnChanges, SimpleChanges, DoCheck, SimpleChange } from '@angular/core';

function Watch() : PropertyDecorator & MethodDecorator{
    function isOnChanges(val: OnChanges): val is OnChanges{
        return !!(val as OnChanges).ngOnChanges
    }
    return (target : any, key: string | symbol, propDesc?: PropertyDescriptor) => {
        let privateKey = "_" + key.toString();
        let isNotFirstChangePrivateKey = "_" + key.toString() + 'IsNotFirstChange';
        propDesc = propDesc || {
            configurable: true,
            enumerable: true,
        };
        propDesc.get = propDesc.get || (function (this: any) { return this[privateKey] });

        const originalSetter = propDesc.set || (function (this: any, val: any) { this[privateKey] = val });

        propDesc.set = function (this: any, val: any) {
            let oldValue = this[key];
            if(val != oldValue) {
                originalSetter.call(this, val);
                let isNotFirstChange = this[isNotFirstChangePrivateKey];
                this[isNotFirstChangePrivateKey] = true;
                if(isOnChanges(this)) {
                    var changes: SimpleChanges = {
                        [key]: new SimpleChange(oldValue, val, !isNotFirstChange)
                    }
                    this.ngOnChanges(changes);
                }
            }
        }
        return propDesc;
    }
}

// Usage
export class MyClass implements OnChanges {


    //Properties what I want to track !
    @Watch()
    myProperty_1: boolean  =  true
    @Watch()
    myProperty_2 =  ['A', 'B', 'C'];
    @Watch()
    myProperty_3 = {};

    constructor() { }
    ngOnChanges(changes: SimpleChanges) {
        console.log(changes);
    }
}

var myInatsnce = new MyClass(); // outputs original field setting with firstChange == true
myInatsnce.myProperty_2 = ["F"]; // will be notified on subsequent changes with firstChange == false

wie gesagt kannst du verwenden 

public set myProperty_2(value: type): void {
 if(value) {
  //doMyCheck
 }

 this._myProperty_2 = value;
}

und dann, wenn Sie es abrufen müssen 

public get myProperty_2(): type {
  return this._myProperty_2;
}

auf diese Weise können Sie alle gewünschten Überprüfungen durchführen, während Sie Ihre Variablen einstellen/abrufen. Diese Methoden werden jedes Mal ausgelöst, wenn Sie die Eigenschaft myProperty_2 festlegen bzw. abrufen.

kleine Demo: https://stackblitz.com/edit/angular-n72qlu

2
Vale Steve

Ich glaube, ich bin auf den Weg gekommen, DOM-Änderungen anzuhören, damit Sie Änderungen an Ihrem Element vornehmen können. Ich hoffe wirklich, dass diese Hinweise und Tipps Ihnen dabei helfen werden, Ihr Problem zu lösen, indem Sie den folgenden einfachen Schritt ausführen:

Zuerst müssen Sie Ihr Element folgendermaßen referenzieren:

in HTML:

<section id="homepage-elements" #someElement>
....
</section>

Und in Ihrer TS-Datei dieser Komponente:

@ViewChild('someElement')
public someElement: ElementRef;

Second, Sie müssen einen Beobachter erstellen, um die Elementänderungen zu überwachen. Sie müssen die ts-Datei der Komponente in implements AfterViewInit, OnDestroy ändern und dann die ngAfterViewInit() dort implementieren (OnDestroy hat später einen Job):

private changes: MutationObserver;
ngAfterViewInit(): void {
  console.debug(this.someElement.nativeElement);

  // This is just to demo 
  setInterval(() => {
    // Note: Renderer2 service you to inject with constructor, but this is just for demo so it is not really part of the answer
    this.renderer.setAttribute(this.someElement.nativeElement, 'my_custom', 'secondNow_' + (new Date().getSeconds()));
  }, 5000);

  // Here is the Mutation Observer for that element works
  this.changes = new MutationObserver((mutations: MutationRecord[]) => {
      mutations.forEach((mutation: MutationRecord) => {
        console.debug('Mutation record fired', mutation);
        console.debug(`Attribute '${mutation.attributeName}' changed to value `, mutation.target.attributes[mutation.attributeName].value);
      });
    }
  );

  // Here we start observer to work with that element
  this.changes.observe(this.someElement.nativeElement, {
    attributes: true,
    childList: true,
    characterData: true
  });
}

Sie werden sehen, dass die Konsole mit allen Änderungen an diesem Element funktioniert:

 enter image description here

Dies ist ein weiteres Beispiel, in dem Sie zwei Mutationsdatensätze und die geänderte Klasse sehen:

// This is just to demo
setTimeout(() => {
   // Note: Renderer2 service you to inject with constructor, but this is just for demo so it is not really part of the answer
  this.renderer.addClass(this.someElement.nativeElement, 'newClass' + (new Date().getSeconds()));
  this.renderer.addClass(this.someElement.nativeElement, 'newClass' + (new Date().getSeconds() + 1));
}, 5000);

// Here is the Mutation Observer for that element works
this.changes = new MutationObserver((mutations: MutationRecord[]) => {
    mutations.forEach((mutation: MutationRecord) => {
      console.debug('Mutation record fired', mutation);
      if (mutation.attributeName == 'class') {
        console.debug(`Class changed, current class list`, mutation.target.classList);
      }
    });
  }
);

Konsolenprotokoll:

 enter image description here

Und nur Housekeeping-Sachen, OnDestroy:

ngOnDestroy(): void {
  this.changes.disconnect();
}

Zum Schluss können Sie diese Referenz betrachten: DOM-Änderungen mit MutationObserver in Angular anhören

1
Al-Mothafar

Sie können ChangeDetectorRef importieren

 constructor(private cd: ChangeDetectorRef) {
          // detect changes on the current component
            // this.cd is an injected ChangeDetector instance
            this.cd.detectChanges();

            // or run change detection for the all app
            // this.appRef is an ApplicationRef instance
            this.appRef.tick();
}
0
Sultan Khan