次の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オブジェクトの変更を監視し、それに対応したいだけです。
私はもう一度試してみましたが、コンポーネントの内部primitiveプロパティ here をデバウンスできました。
しかし、独自のプロパティを持つ複雑なオブジェクトでこれを機能させることができませんでした( here );提供されたplnkrのセッターは、ngModel
オブジェクトのプロパティを更新するときにfoo
によって呼び出されないためです。
正解は、これが複雑なオブジェクトで機能することを示します。
私は複雑なオブジェクトを処理していると思います ;ただし、オブジェクトが不変であること、および各プロパティにセッターがあることを確認する必要があります。これは一種の失望です。また、カスタムセッターロジックを指定できるように、[(ngModel)]
を_[ngModel]
_と_(ngModelChange)
_に分割する必要があるようです。
理論的には機能を状態サービスに抽象化することもできますが、定型文の量をさらに削減できるかどうかを確認します。状態の変更が必要になるたびに新しいオブジェクトを作成するのは、ちょっと面倒です。
単純なオブジェクトと複雑なオブジェクトでこれを部分的に解決したようです。変更検出器全体が必要ない場合は、次の方法で簡単に実装できます。
以下は、これを実現する方法の最小限の例です。
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
もちろん、現実的には、コンポーネントはサブジェクトや監視可能要素の知識を持っていてはならないので、必要に応じてサービスに抽出してください。
この例はプリミティブでのみ機能します。オブジェクトを操作する場合は、プロパティごとにカスタムセッターロジックが必要であり、そのオブジェクトのプロパティを変更するたびに新しいオブジェクトを作成することを忘れないでください。
@vjawalaは非常に近いです:)
入力を追跡する場合(@Input
)プロパティはOnChanges
を使用します:
ngOnChanges(changes: SimpleChanges) {
if(changes['foo']){
triggerAutoSave();
}
}
キー/値オブジェクトを追跡する場合は、DoCheck
リスナーを実装してKeyValueDiffer
を使用します。例はangular docsにあります。
変更/入力(おそらく両方)イベントに直接サブスクライブする方がはるかに効率的/簡単かもしれません:
<form (input)="triggerAutoSave()" (change)="triggerAutoSave()">
<input>
<input>
<input>
</form>
RxJSを使用すると、デバウンスは比較的簡単です。
triggerAutoSave(){
subject.next(this.foo);
}
subject.debounceTime(500).subscribe(...);
Http経由で保存する場合は、exhaustMap
が役立ちます。
これは私が思いついたものです。おそらく少し磨く必要がありますが、関係なくうまく機能します。
動作 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)
);
}
}
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);
}
}
Angular2でストリームを利用できます。それと同じ要件があり、サービスでストリームを使用することを次のように実装しました。
ser.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);
});