使用 observables 检测 Angular 2 变化 [英] Angular 2 change detection with observables

查看:25
本文介绍了使用 observables 检测 Angular 2 变化的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我通常通过浏览现有问题来设法找出我做错了什么,但在这里,没有任何帮助.

I usually manage to find what I'm doing wrong just browsing existing questions, but here, nothing has helped.

我正在使用一个简单的 Ng2 模块来尝试列出和更新 NeDB 存储的内容.

I'm working with a simple Ng2 module that attempts to list and update the contents of a NeDB store.

请注意,我对 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.

我遇到的问题如下:

异步管道不起作用".

<小时>

我有这个模块.


I have this module.

@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 方法中的日志记录):


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):

got value > [] (wiki.component.ts:20)
got value > [Object, Object, Object, Object] (wiki.component.ts:20)

但是我的 DOM 保持如下:

However my DOM stays as this:

<wiki>
    <p>[]</p>
    <ul>
        <!--template bindings={
          "ng-reflect-ng-for-of": ""
        }-->
    </ul>
    <input>
    <button>ADD</button>
</wiki>

<小时>

所以手动订阅我的 observable 确实有效并为我获取值.但是异步管道没有得到它们.


So a manual subscription to my observable does work and gets me the values. But the async pipe doesn't get them.

我在这里做错了什么,还是这是一个错误?

Am I doing something wrong here, or is this a bug?

编辑

12/19/16 下午 3:45

ngFor 指令之前是let item of items | async",我想也许异步管道的范围是项目而不是我的可观察对象,所以我添加了括号,但结果没有改变.这与问题无关.

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.

12/20/16 3.06pm

根据@olsn 的建议,使用自动日志初始化组件的 items 属性,以检查模板是否订阅了 Observable.

As per @olsn's advice, Initialized the component's items property with an auto-log, to check if the template subscribed to the 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.
    }
}

在模板中添加此内容:

<button (click)="reload()">REFRESH</button>

<小时>

解决方案

@osln 给出了正确答案.


SOLUTION

@osln gave a correct answer.

问题根本不是关于订阅或检测更改,而是因为我的 sub.next 调用是在给外部库的回调中,这具体意味着我在 Angular 之外执行它们领土.

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.

通过 NgZone 调用迫使他们回到 Angular 土壤是解决这个问题的方法.

Forcing them back onto Angular soil with NgZone calls was the way to fix this issue.

谢谢@osln.

推荐答案

尝试在 ngInit 之前初始化您的 item-object 并将临时日志直接添加到流中,这样您就知道模板是否确实订阅了流,因为您当前的日志是在一个完全独立的流上完成的.

Try to initialize your item-object before ngInit 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.

@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);,使用:

First inject this in your DbService: private ngZone: NgZone (from @angular/core) and then instead of just using this.sub.next(docs);, use:

this.ngZone.run(() => this.sub.next(docs));

(也用于 add-call)

(also for the add-call)

这篇关于使用 observables 检测 Angular 2 变化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆