webentwicklung-frage-antwort-db.com.de

Änderungen an der Eigenschaft der Komponente in abonnieren Angular 2 für entprellte automatische Speicherung?

Angenommen, ich habe die folgende Angular 2-Komponente, wobei foo ein Objekt ist, das nicht an ein Formular gebunden ist und nicht von einer Input() stammt:

@Component({
    selector: 'foo',
    templateUrl: '/angular/views/foo.template.html',
    directives: [ContentEditableDirective, ROUTER_DIRECTIVES],
    providers: [FooService]
})
export class FooComponent implements OnInit {
    public foo: Foo = new Foo();

    constructor(
        private fooService: FooService,
        private route : ActivatedRoute,
        private router : Router) {}

    ngOnInit() {
        let id = +this.route.snapshot.params['id'];
        this.fooService.getFoo(id).subscribe(
            foo => {
                this.foo = foo;

                // I have this.foo, we should watch for changes here.

                Observable. // ???
                    .debounceTime(1000)
                    .subscribe(input => fooService.autoSave(this.foo));
            },
            error => console.log(error)
        );
    }
}

Wie kann ich Änderungen am Foo-Objekt abonnieren und an meinen Server senden?

Jedes Beispiel, das ich bisher gesehen habe, beinhaltet entweder foo von einer Input() oder an ein Formular gebunden zu sein. Ich möchte nur ein einfaches altes Javascript-Objekt auf Änderungen beobachten und darauf reagieren.

Aktualisieren

Ich habe es noch einmal versucht und konnte eine interne primitive -Eigenschaft auf einer Komponente hier entprellen.

Aber ich konnte diese Arbeit nicht mit einem komplexen Objekt durchführen, das eigene Eigenschaften hat ( here ); da der Setter im bereitgestellten plnkr nicht von ngModel aufgerufen wird, wenn er die Eigenschaft des foo-Objekts aktualisiert.

Eine korrekte Antwort zeigt das Arbeiten mit einem komplexen Objekt.

Update 2

Ich glaube, ich habe es mit einem komplexen Objekt zu tun ; Man muss jedoch sicherstellen, dass das Objekt unveränderlich ist und dass Sie für jede Eigenschaft Setter haben, was eine Art Enttäuschung darstellt. Es scheint auch die Aufteilung von [(ngModel)] in [ngModel] und (ngModelChange) zu erfordern, damit Sie eine benutzerdefinierte Setzerlogik angeben können.

Sie könnten die Funktionalität theoretisch auf einen Status-Service übertragen, aber ich würde gerne sehen, ob die Boilerplate-Menge weiter reduziert werden kann. Das Erstellen neuer Objekte jedes Mal, wenn Sie eine Statusänderung wünschen, ist ziemlich frustrierend.

Ich scheine das für einfache und komplexe Objekte teilweise gelöst zu haben. Wenn Sie keinen kompletten Änderungsdetektor benötigen, können Sie dies einfach mit nur:

  • Ein Thema und ein Beobachtbares für Ihre Komponente.
  • Ein Getter und ein Setter für die verfolgte Eigenschaft Ihrer Komponente.

Unten ist ein sehr minimales Beispiel, wie man dies erreichen kann. 

Komponente

import {Component} from '@angular/core'
import {Subject} from 'rxjs/Rx';

@Component({
  selector: 'my-app',
  providers: [],
  template: `
    <div>
      <input type="text" [(ngModel)]="foo"  />
      <div>current Foo: {{ foo }}</div>
      <div>debounced Foo: {{ debouncedFoo }}</div>
    </div>
  `,
  directives: []
})
export class App {
  private _foo: any;
  debouncedFoo: any;
  fooSubject : Subject<any> = new Subject<any>(this._foo);
  fooStream : Observable<any> = this.fooSubject.asObservable();

  get foo() {
      return this._foo;
  });

  set foo(value:any) {
    this._foo = value;
    this.fooSubject.next(value);
  });

  constructor() {
    // Manipulate your subscription stream here
    this.subscription = this.fooStream
      .debounceTime(2000)
      .subscribe(
        value => {
          this.debouncedFoo = value;
      });
  }
}

Beispiel

http://plnkr.co/edit/HMJz6UWeZIBovMkXlR01?p=preview

Natürlich sollte die Komponente keine Kenntnis des Themas haben oder beobachtbar sein, also ziehen Sie sie zu einem Dienst hinzu, wie Sie möchten.

Nachteile

Dieses Beispiel funktioniert nur mit Grundelementen. Wenn Sie mit Objekten arbeiten möchten, benötigen Sie für jede Eigenschaft eine benutzerdefinierte Einstellungslogik. Denken Sie daran, jedes Mal neue Objekte zu erstellen, wenn Sie eine Eigenschaft dieses Objekts ändern.

@vjawala ist sehr nah :)

Wenn Sie Eingabeeigenschaften (mit @Input kommentiert) nachverfolgen möchten, verwenden Sie OnChanges:

  ngOnChanges(changes: SimpleChanges) {
    if(changes['foo']){
        triggerAutoSave();
    }
  }

Wenn Sie das Schlüssel-/Wertobjekt nachverfolgen möchten, implementieren Sie DoCheck listener und verwenden Sie KeyValueDiffer. Ein Beispiel hierfür finden Sie in Angular Docs.

Das direkte Abonnieren von Änderungs-/Eingabeereignissen (höchstwahrscheinlich beide) ist möglicherweise wesentlich effizienter/einfacher: 

<form (input)="triggerAutoSave()" (change)="triggerAutoSave()">
   <input>
   <input>
   <input>
</form>

Debounce ist mit RxJS relativ einfach:

triggerAutoSave(){
     subject.next(this.foo);
}

subject.debounceTime(500).subscribe(...);

Wenn Sie über http speichern, kann exhaustMap hilfreich sein.

3
kemsky

Das ist etwas, worüber ich gerade gekommen bin. Es muss wahrscheinlich ein wenig poliert werden, funktioniert aber trotzdem gut.

Arbeiten Plunker zum Beispiel Verwendung


export class ChangeTracker {
  private _object: any;
  private _objectRetriever: () => any;
  private _objectSubject: Subject<any> = new Subject<any>();
  private _onChange: (obj: any) => void;
  private _fnCompare: (a: any, b: any) => boolean;

  constructor(objRetriever: () => any, 
              onChange: (obj: any) => void, 
              fnCompare?: (a: any, b: any) => boolean) {

    this._object = objRetriever();
    this._objectRetriever = objRetriever;
    this._onChange = onChange;
    this._fnCompare = fnCompare ? fnCompare : this.defaultComparer;

    this._objectSubject
      .debounceTime(1000)
      .subscribe((data: any) => {
          this._onChange(data);
      });

    setInterval(() => this.detectChanges(), 500);
  }

  private defaultComparer(a: any, b: any) {
    return JSON.stringify(a) == JSON.stringify(b);
  }

  private detectChanges() {
    let currentObject = this._objectRetriever();

    if (!this._fnCompare(this._object, currentObject)) {
      this._object = currentObject;

      this._objectSubject.next(currentObject);
    }
  }
}

Verwendung in Ihrem Codebeispiel:

@Component({
    selector: 'foo',
    templateUrl: '/angular/views/foo.template.html',
    directives: [ContentEditableDirective, ROUTER_DIRECTIVES],
    providers: [FooService]
})
export class FooComponent implements OnInit {
    public foo: Foo = new Foo();

    constructor(
        private fooService: FooService,
        private route : ActivatedRoute,
        private router : Router) {}

    ngOnInit() {
        let id = +this.route.snapshot.params['id'];
        this.fooService.getFoo(id).subscribe(
            foo => {
                this.foo = foo;

                /* just create a new ChangeTracker with a reference
                 * to the local foo variable and define the action to be
                 * taken on a detected change
                 */
                new ChangeTracker(
                    () => this.foo, 
                    (value) => {
                        this.fooService.autoSave(value);
                    }
                );
            },
            error => console.log(error)
        );
    }
}
2
rinukkusu

Was ist, wenn Sie die Funktion ngDoCheck() verwenden? So etwas wie:

@Component({
    selector: 'foo',
    templateUrl: '/angular/views/foo.template.html',
    directives: [ContentEditableDirective, ROUTER_DIRECTIVES],
    providers: [FooService]
})
export class FooComponent implements OnInit, DoCheck {
    public foo: Foo = new Foo();

    constructor(
        private fooService: FooService,
        private route : ActivatedRoute,
        private router : Router) {}

    ngOnInit() {
        let id = +this.route.snapshot.params['id'];
        this.foo = this.fooService.getFoo(id);
    }
    ngDoCheck() {
        fooService.autoSave(this.foo);
    }
}
1
vjawala

Sie können Streams in angle2 ..__ verwenden. Ich hatte die gleiche Anforderung wie ich, die Streams in einem Service wie folgt implementiert:

user.service.ts

selectedUserInstance:user = new User();

// Observable selectUser source
private selectUserSource = new Subject<any>();
// Observable selectUser stream
selectUser$ = this.selectUserSource.asObservable();

// service command
selectUser(user:User) {
    //console.log(user);
    this.selectedUserInstance=user;    
    this.selectUserSource.next(this.selectedUserInstance);
}

innere Komponente

this.subscription = this.userService.selectUser$.subscribe(
  selectedUser => {
    this.selectedUser = selectedUser;
    console.log("In component : " + this.selectedUser.name);
  });
0
Bhushan Gadekar