【问题标题】:Subscribe to changes on property of component in Angular 2 for debounced auto saving?订阅Angular 2中组件属性的更改以进行去抖动自动保存?
【发布时间】:2016-11-11 18:36:24
【问题描述】:

假设我有以下 Angular 2 组件,其中 foo 是一个未绑定到表单的对象,并且不是来自 Input()

@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)
        );
    }
}

如何订阅Foo 对象的更改并将其发送到我的服务器?

到目前为止,我看到的每个示例都涉及使 foo 脱离 Input() 或绑定到表单。我只是想观察一个普通的旧 Javascript 对象的变化并对此做出反应。

更新

我再次尝试,并且已经能够对组件 here 的内部 primitive 属性进行去抖动。

但是,我无法使用具有自身属性的复杂对象 (here);因为ngModel 在更新foo 对象的属性时不会调用提供的plnkr 中的setter。

正确的答案将证明这一点适用于复杂的对象。

更新 2

I believe I have it working with a complex object;但是必须确保对象是不可变的,并且每个属性都有设置器,这有点令人失望。它似乎还需要将[(ngModel)] 拆分为[ngModel](ngModelChange),以便您可以指定自定义设置器逻辑。

理论上,您可以将功能抽象为状态服务,但我想看看是否可以进一步减少样板的数量。每次想要更改状态时都创建新对象有点令人沮丧。

【问题讨论】:

  • 在这里使用 angular2 区域可能会有所帮助,您可以挂接到该区域并在每次执行新的异步操作时调用您的代码,实际上是异步操作,如事件、https、超时等。这正是 angular2 在这些异步操作可以更改属性的假设下运行更改检测的时候。很好的切入点是:blog.thoughtram.io/angular/2016/02/01/zones-in-angular-2.html,告诉我这个帮助或者如果您需要更多详细信息

标签: typescript angular observable


【解决方案1】:

如果您使用ngDoCheck() 函数会怎样?比如:

@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);
    }
}

【讨论】:

  • 你能描述一下自动保存功能是如何工作的,并让我可以访问一个我可以操作的可观察流吗?
  • 在 fooService 中,您可以对保存的值做任何您想做的事情(在保存之前)。除非你的意思是别的?
【解决方案2】:

您可以在 angular2 中使用流。 我有同样的要求,我已经实现了在服务中使用流如下:

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);
}

内部组件

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

【讨论】:

    【解决方案3】:

    这是我刚刚想到的。它可能需要稍加打磨,但无论如何它都能很好地工作。

    以工作Plunker为例使用


    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);
        }
      }
    }
    

    在您的代码示例中的用法:

    @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)
            );
        }
    }
    

    【讨论】:

    • 这很有希望,谢谢。明天有空的时候再研究一下。谢谢!
    • 嗯。 setInterval(() =&gt; this.detectChanges(), 500); 所以你真的只是每半秒手动做很多工作吗?看起来有点脏。
    • 是的,但是 Angular 的变更检测也是如此,甚至更常见的是——检测变更并不是黑魔法。如果您有更多要跟踪的变量,可以考虑将更改检测放入定期通过它们的数组运行的服务中。
    【解决方案4】:

    对于简单和复杂的对象,我似乎已经部分解决了这个问题。如果不需要整个变更检测器,您可以通过以下方式轻松实现:

    • 组件上的主题和可观察对象。
    • 组件上被跟踪属性的 getter 和 setter。

    下面是一个非常简单的示例,说明如何实现这一点。

    组件

    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;
          });
      }
    }
    

    示例

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

    当然,实际上组件应该不了解主题或可观察的,因此请随意将它们提取到服务中。

    缺点

    此示例仅适用于原语。如果您希望它与对象一起工作,则需要为每个属性自定义设置器逻辑,并记住每次更改该对象的属性时都要创建新对象。

    【讨论】:

    • 我真的很喜欢这个答案! +1 使用 get/set - 没有想到这一点,因此我单独上课。
    【解决方案5】:

    @vjawala 非常接近 :)

    如果您想跟踪输入(使用@Input 注释)属性,请使用OnChanges

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

    如果要跟踪键/值对象,则实现DoCheck 侦听器并使用KeyValueDiffer,示例在角度文档中提供。

    直接订阅更改/输入(很可能是两者)事件可能更有效/更简单:

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

    使用 RxJS 去抖动相对容易:

    triggerAutoSave(){
         subject.next(this.foo);
    }
    
    subject.debounceTime(500).subscribe(...);
    

    如果您通过 http 保存,那么 exhaustMap 可能会有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-05-09
      • 2015-11-10
      • 1970-01-01
      • 1970-01-01
      • 2019-09-21
      • 1970-01-01
      • 2017-10-13
      • 1970-01-01
      相关资源
      最近更新 更多