typescript 使用 observables 检测 Angular 2 变化
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/41224671/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Angular 2 change detection with observables
提问by FacelessPanda
I usually manage to find what I'm doing wrong just browsing existing questions, but here, nothing has helped.
我通常通过浏览现有问题来设法找到我做错了什么,但在这里,没有任何帮助。
I'm working with a simple Ng2 module that attempts to list and update the contents of a NeDB store.
我正在使用一个简单的 Ng2 模块,该模块尝试列出和更新 NeDB 存储的内容。
Mind you, I have no issues with the NeDB store, I have confirmed that it gets updated correctly, and correctly loaded initially, so the problems I have lie elsewhere.
请注意,我对 NeDB 商店没有任何问题,我已确认它已正确更新,并且最初已正确加载,因此我遇到的问题在别处。
The problems I have are the following:
我遇到的问题如下:
"the async pipe doesn't work".
“异步管道不起作用”。
I have this module.
我有这个模块。
@NgModule({
imports: [CommonModule],
exports: [],
declarations: [WikiComponent],
providers: [WikiDbService],
})
export class WikiModule { }
I have this component.
我有这个组件。
@Component({
selector: 'wiki',
templateUrl: './wiki.component.html'
})
export class WikiComponent implements OnInit {
items: Observable<WikiItem[]>;
constructor(private _db : WikiDbService) { }
ngOnInit() {
this.items = this._db.items;
this.items.subscribe({
next: x => console.log("got value", x),
error: e => console.error("observable error", e),
complete: () => console.log("done")
});
}
}
I have this template.
我有这个模板。
<p>{{items | async | json}}</p>
<ul>
<li *ngFor="let item of (items | async)">{{item.name}}</li>
</ul>
<input #newName (keyup)="0">
<button (click)="_db.addByName(newName.value)">ADD</button>
And I have this service.
我有这项服务。
@Injectable()
export class WikiDbService {
private sub: BehaviorSubject<WikiItem[]> = new BehaviorSubject<WikiItem[]>([]);
private db: DataStore;
public items: Observable<WikiItem[]> = this.sub.asObservable();
constructor() {
console.log("BehaviorSubject", this.sub);
console.log("Observable", this.items);
this.db = new DataStore(
{
filename: path.join(app.getAppPath(),"wiki.db"),
autoload: true,
onload:
(err)=>{
if(!err) {
this.db.find<WikiItem>({},
(e,docs) => {
if(!e) {
this.sub.next(docs);
}
})
}
}
});
}
public add(v: WikiItem) {
this.db.insert(
v,
(e, nDoc) =>
{
if(!e) {
this.sub.next([...this.sub.getValue(),nDoc]);
}
}
)
}
public addByName(str:string) {
this.add({name: str, _id: undefined});
}
}
When routing to my component with a non-empty persistent store I get the following console log (corresponding to the logging in the OnInit method of the component):
当使用非空持久存储路由到我的组件时,我得到以下控制台日志(对应于组件的 OnInit 方法中的日志记录):
got value > [] (wiki.component.ts:20)
got value > [Object, Object, Object, Object] (wiki.component.ts:20)
However my DOM stays as this:
但是我的 DOM 保持如下:
<wiki>
<p>[]</p>
<ul>
<!--template bindings={
"ng-reflect-ng-for-of": ""
}-->
</ul>
<input>
<button>ADD</button>
</wiki>
So a manual subscription to my observable does work and gets me the values. But the async pipe doesn't get them.
因此,手动订阅我的 observable 确实有效并为我提供了值。但是异步管道没有得到它们。
Am I doing something wrong here, or is this a bug?
我在这里做错了什么,还是这是一个错误?
EDITS
编辑
12/19/16 3:45pm
16 年 12 月 19 日下午 3:45
The
ngFor
directive was "let item of items | async" before, and I thought maybe the async pipe was scoped to the item and not my observable so I added brackets, but results were unchanged. This is not relevant for the issue.
该
ngFor
指令之前是“let item of items | async”,我想也许异步管道的范围是项目而不是我的可观察对象,所以我添加了括号,但结果没有改变。这与问题无关。
12/20/16 3.06pm
16 年 12 月 20 日下午 3.06
As per @olsn's advice, Initialized the component's items
property with an auto-log, to check if the template subscribed to the Observable.
根据@olsn 的建议,使用items
自动日志初始化组件的属性,以检查模板是否订阅了 Observable。
It does. So it comes down to detecting the changes, I guess. Amending the title.
确实如此。所以归结为检测变化,我猜。修改标题。
Adding this bit of information : My Component is now as such (commented changes)
添加这一点信息:我的组件现在是这样(评论更改)
@Component({
selector: 'wiki',
templateUrl: './wiki.component.html',
changeDetection: ChangeDetectionStrategy.OnPush // <=== I've read this might help. It doesn't.
})
export class WikiComponent implements OnInit {
items: Observable<WikiItem[]> = this._db.items //
.do(x => console.log("got value", x)) // <== new initialization, with a stream
.publishReplay().refCount(); //
constructor(private _db : WikiDbService, private _cd: ChangeDetectorRef) { }
ngOnInit() {
// <=== moved items initialization
}
reload() : void {
this._cd.markForCheck(); // <== added a button to force the change detector to react. Does not do anything.
}
}
with this addition in the template :
在模板中添加此内容:
<button (click)="reload()">REFRESH</button>
SOLUTION
解决方案
@osln gave a correct answer.
@osln 给出了正确答案。
The problem wasn't fundamentally about subscription or detecting changes, it was because my sub.next
call were in callbacks given to an external library, which concretely meant that I was doing them outside of Angular territory.
问题根本不在于订阅或检测更改,而是因为我的sub.next
调用是在给外部库的回调中进行的,这具体意味着我是在 Angular 领域之外执行这些操作。
Forcing them back onto Angular soil with NgZone calls was the way to fix this issue.
通过 NgZone 调用迫使它们回到 Angular 土壤是解决这个问题的方法。
Thanks @osln.
谢谢@osln。
回答by olsn
Try to initialize your item-object beforengInit and add a temporary log directly into the stream, that way you know if the template really subscribes to the stream, because your current log is done on a completely separate stream.
尝试在ngInit之前初始化您的 item-object并将临时日志直接添加到流中,这样您就知道模板是否真的订阅了流,因为您当前的日志是在完全独立的流上完成的。
@Component({
selector: 'wiki',
templateUrl: './wiki.component.html'
})
export class WikiComponent implements OnInit {
items: Observable<WikiItem[]> = this._db.items
.do(x => console.log("got value", x)
// if items is not a Behavior- or ReplaySubject or ReplayObservable, also add the following:
.publishReplay()
.refCount();
constructor(private _db : WikiDbService) { }
ngOnInit() {
// ..nothing to do here
}
}
Additionally you might try to wrap your data-retrieval in an NgZone.run
:
此外,您可能会尝试将数据检索包装在NgZone.run
:
First inject this in your DbService: private ngZone: NgZone
(from @angular/core
) and then instead of just using this.sub.next(docs);
, use:
首先将它注入到您的 DbService: private ngZone: NgZone
(from @angular/core
) 中,然后this.sub.next(docs);
使用:
this.ngZone.run(() => this.sub.next(docs));
(also for the add-call)
(也用于添加呼叫)