更新ngrx / store中的对象

时间:2016-11-30 23:01:47

标签: javascript angular typescript rxjs ngrx

我正在使用@ ngrx / store作为Angular 2应用程序。

我的商店拥有一个说明Book个对象的列表。我想更新其中一个对象中的字段。我也碰巧有一个我想要更新的Book实例的Observable(比如,selectedBook)。

要进行更新,我打算使用UpdateBookAction调用reducer,以及新Book的有效负载。所以我通过订阅selectedBook然后调用Object.assign()来制作现有Book对象的深层副本。

但是当我尝试写入副本的某个字段时,我收到以下错误。 (如果我试图直接写入商店中的Book对象,那恰好是我得到的错误。)

错误

Cannot assign to read only property 'name' of object '#<Object>' at ViewWrappedError.BaseError [as constructor]

代码

ngOnInit() {
    this.book$ = this.store.let(fromRoot.getSelectedBook);
    //...
}

someFunction() {
    //...
    this.book$.subscribe(book => {

        let updatedBook = Object.assign({}, book);
        updatedBook.name = 'something else';          // <--- THIS IS WHAT THROWS

        let action = new BookUpdateAction(updatedBook);
        this.store.dispatch(action);

    }
}

评论后澄清

我假设我可以使用不是商店整个状态的有效负载进行操作。 (事实上​​这似乎是必要的,不是吗?)我相信这是文件的情况。

我想采取的行动是这样的:

Action = UPDATE, payload = {'id': 1234, 'name': 'something new'}

如前所述,我打算像这样打电话:

this.store.dispatch(action);

据推测,ngrx正在将我的行动传递给减速器以及(不可变的)当前状态。

所以从那里开始,一切都应该可行。我在reducer中的逻辑不会改变现有状态,它只是从现有状态和我传入的有效负载中创建一个新的状态。

这里真正的问题是如何合理地构建新的“objectToUpdate”,以便我可以将其作为有效负载传递。

我可以这样做:

this.book$.subscribe(book => {

    let updatedBook = new Book();
    updatedBook.id = book.id;
    //set all other fields manually...
    updatedBook.name = 'something else';

    let action = new BookUpdateAction(updatedBook);
    this.store.dispatch(action);

}

但我们不只是在这里讨论两个领域......如果我的书有几个字段怎么办?每次只更新一个字段时,是否必须从头开始手动构建新书?

我的解决方案是使用Object.assign({}, book)进行深层复制(而不是改变旧版本!),然后仅对我想要触摸的字段进行更新。

4 个答案:

答案 0 :(得分:7)

ngrx商店的想法是拥有一个且只有一个单一的真实位置,这意味着所有对象都是不可变的,而改变任何东西的唯一方法就是重新创建整个事物。此外,您可能正在使用ngrx冻结(https://github.com/codewareio/ngrx-store-freeze),这意味着所有对象都将以只读方式创建,因此您无法更改任何对象(如果您想要完全遵循redux模式)。如果您删除商店冻结对象的部分,您将能够更改它,但这不是最佳实践。

我建议您使用以下内容:使用带有异步管道的ngrx observable将数据(在您的案例中)放入一个只能输入和输出某个事件的哑组件中。在愚蠢的组件内,你可以&#34;编辑&#34;该对象通过制作它的副本,并在完成后,您可以将更改发回到订阅商店的智能组件,并允许它通过存储(提交)更改状态。这种方式是最好的,因为对于一个非常小的更改来改变整个状态并不常见(比如当用户输入时双向绑定...)。

如果您遵循redux模式,那么您将能够添加历史记录,这意味着商店将保留最后X个状态重新创建的副本,因此您可以获得UNDO功能,更容易调试,时间线等

您的问题是您直接编辑属性而不是重新创建整个状态。

答案 1 :(得分:6)

我必须对OP正在经历的实际情况做出假设。

问题

无法修改冻结对象的成员。它的错误被抛出。

原因

Object.assign()用作元缩减器来冻结进入商店的任何对象。在另一个地方,当需要更改对象时,正在进行浅拷贝。 cloneDeep()不进行深层复制。正在修改从原始对象到达的另一个对象的成员。此辅助对象也被冻结,不会重复

<强>解决方案

使用lodash中的$command1 = exec("mail -s 'title' info@example.com <<< 'message'"); $text1 = file_get_contents('http://example.com/page'); $intext1 = strpos($text1, 'tvshenja1') !== false; // note !==, not != echo $intext1 ? 'do nothing' : $command1; 这样的深层副本。或者通过适当的行动发送一袋待更改的房产。在reducer上处理更改。

答案 2 :(得分:1)

正如已经提到的 - 你得到的原因

  

无法指定只读属性&#39; name&#39;对象

是因为&#39; ngrx-store-freeze&#39;冻结国家,防止变异。

Object.assign将提供您期望的新对象,它将复制州的属性以及每个属性自己的定义 - 例如&#39 ;可写&#39;定义(&#39; ngrx-store-freeze&#39;可能设置为false)。

描述了一种不同的方法in this answer并解释了如何使用JSON.parse(JSON.stringify(yourObject))克隆对象的速度最快,但如果你保留日期或方法等,这种方法就有缺陷。在你的州。

使用lodash&#39; cloneDeep&#39;对于深度克隆国家来说,这可能是你最好的选择。

答案 3 :(得分:1)

实现此目的的一种方法是使用实​​用程序/帮助程序方法来制作新书。 您可以给它一本现有的书,以及要添加到新书中的属性的子集(如果要输入安全性,请在Partial中使用typeScript

createNewBook(oldBook: Book, newProps: Partial<Book>): Book {
    const newBook = new Book(); 
    for(const prop in oldBook) {
        if(newProps[prop]) {
            newBook[prop]=newProps[prop];
        } else {
            newBook[prop]=oldBook[prop];
        }
    }
    return newBook 
}

您可以通过newBook = createNewBook(new Book(), {title: 'first foo, then bar'});进行调用 并使用此newBook更新您的商店。