Angular 2 变化检测与 observables
Posted
技术标签:
【中文标题】Angular 2 变化检测与 observables【英文标题】:Angular 2 change detection with observables 【发布时间】:2017-05-04 15:03:50 【问题描述】:我通常只浏览现有问题就能找到我做错了什么,但在这里,没有任何帮助。
我正在使用一个简单的 Ng2 模块来尝试列出和更新 NeDB 存储的内容。
请注意,我对 NeDB 商店没有任何问题,我已确认它已正确更新,并且最初已正确加载,所以我的问题在别处。
我遇到的问题如下:
“异步管道不起作用”。
我有这个模块。
@NgModule(
imports: [CommonModule],
exports: [],
declarations: [WikiComponent],
providers: [WikiDbService],
)
export class WikiModule
我有这个组件。
@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")
);
我有这个模板。
<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>
我有这项服务。
@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);
当使用非空持久存储路由到我的组件时,我得到以下控制台日志(对应于组件的 OnInit 方法中的日志记录):
got value > [] (wiki.component.ts:20)
got value > [Object, Object, Object, Object] (wiki.component.ts:20)
但是我的 DOM 保持不变:
<wiki>
<p>[]</p>
<ul>
<!--template bindings=
"ng-reflect-ng-for-of": ""
-->
</ul>
<input>
<button>ADD</button>
</wiki>
因此,手动订阅我的 observable 确实有效并让我获得了值。但是异步管道没有得到它们。
我在这里做错了什么,还是这是一个错误?
编辑
2016 年 12 月 19 日下午 3:45
ngFor
指令之前是“let item of items | async”,我认为异步管道的范围可能是项目而不是我的 observable,所以我添加了括号,但结果没有改变。这与问题无关。
2016 年 12 月 20 日下午 3 点 06 分
按照@olsn 的建议,使用自动日志初始化组件的items
属性,以检查模板是否订阅了Observable。
确实如此。所以归结为检测变化,我猜。修改标题。
添加以下信息: 我的组件现在就是这样(注释更改)
@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.
在模板中添加此内容:
<button (click)="reload()">REFRESH</button>
解决方案
@osln 给出了正确答案。
问题根本不是关于订阅或检测更改,而是因为我的 sub.next
调用是在给外部库的回调中,这具体意味着我是在 Angular 领域之外进行的。
使用 NgZone 调用迫使他们回到 Angular 土壤是解决此问题的方法。
感谢@osln。
【问题讨论】:
我是新手,可能什么都不是,但我从来没有将中继器中的项目包裹在 () 中... item.name --> item.name 当我没有括号时,@Thibs 结果是一样的。这不是问题。我将编辑帖子以记录这一点。 如果我理解正确,即使您获得数据,项目也不会显示?您是否尝试过例如:<ul *ngIf="items.length > 0"><li *ngFor="let item of items">item.name</li></ul>
并摆脱异步管道。可能不是一个很好的解决方案,从长远来看对您不起作用,但它至少应该起作用?
那我该如何更新列表呢?
【参考方案1】:
尝试在之前 ngInit 初始化您的项目对象并将临时日志直接添加到流中,这样您就知道模板是否真的订阅了流,因为您当前的日志是在一个完全独立的流。
@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
此外,您可能会尝试将数据检索包装在 NgZone.run
中:
首先将其注入您的 DbService:private ngZone: NgZone
(来自 @angular/core
),然后使用:
this.sub.next(docs);
this.ngZone.run(() => this.sub.next(docs));
(也用于添加调用)
【讨论】:
我会尝试在构造函数中初始化它,但直接在类中我将无法访问 Db 服务 它应该像这样工作 - 你的constructor(private _db:WikiDbService)
是创建类时执行的第一件事 - 试一试 - 随意将代码粘贴到 typescriptlang.org/play 并查看 javascript从它生成的,你会看到所有的类成员都在构造函数参数之后初始化
我得到了完全相同的行为。我确实得到了带有值的订阅日志。我想这是一个变化检测的问题。
您使用的是哪个版本的 Angular 以及如何检索项目并将其插入商店?
我正在使用 Angular 2.3.1,您可以在 DbService 中看到我正在使用 NeDB 作为存储,insert()
和 find()
接受回调,在这些回调中我推送到我的 BehaviorSubject,这是我的公共 observable 的源对象以上是关于Angular 2 变化检测与 observables的主要内容,如果未能解决你的问题,请参考以下文章
Angular 2,Eventemitter 会触发变化检测吗?
如何检测 Angular 2 反应/动态表单的一个元素发生的变化?
Angular 2:如何检测数组中的变化? (@input 属性)