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 结果是一样的。这不是问题。我将编辑帖子以记录这一点。 如果我理解正确,即使您获得数据,项目也不会显示?您是否尝试过例如:&lt;ul *ngIf="items.length &gt; 0"&gt;&lt;li *ngFor="let item of items"&gt;item.name&lt;/li&gt;&lt;/ul&gt; 并摆脱异步管道。可能不是一个很好的解决方案,从长远来看对您不起作用,但它至少应该起作用? 那我该如何更新列表呢? 【参考方案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:检测浏览器宽度的变化并更新模型

Angular 2:如何检测数组中的变化? (@input 属性)

Angular ngOnChanges 和变化检测策略似乎是矛盾的?

详解ANGULAR2组件中的变化检测机制(对比ANGULAR1的脏检测)