Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/javascript/460.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript 更改QueryList.changes上的ContentChildren模型_Javascript_Angular - Fatal编程技术网

Javascript 更改QueryList.changes上的ContentChildren模型

Javascript 更改QueryList.changes上的ContentChildren模型,javascript,angular,Javascript,Angular,假设我有一个具有@ContentChildren(Child)子级的父组件。假设每个子类在其组件类中都有一个索引字段。当父项的子项发生更改时,我希望使这些索引字段保持最新,具体操作如下: this.children.changes.subscribe(() => { this.children.forEach((child, index) => { child.index = index; }) }); 然而,当我尝试这样做时,我得到了一个“ExpressionCh

假设我有一个具有
@ContentChildren(Child)子级
的父组件。假设每个
子类
在其组件类中都有一个
索引
字段。当父项的子项发生更改时,我希望使这些
索引
字段保持最新,具体操作如下:

this.children.changes.subscribe(() => {
  this.children.forEach((child, index) => {
    child.index = index;
  })
});
然而,当我尝试这样做时,我得到了一个“ExpressionChangedAfter…”错误,我猜这是因为
索引
更新发生在更改周期之外。下面是一个stackblitz演示此错误:


我怎样才能解决这个问题?一个明显的方法是简单地将
索引绑定到模板中。第二个明显的方法是在更新其索引时为每个子级调用
detectChanges()
。假设我不能使用这两种方法中的任何一种,还有其他方法吗?

一种方法是使用宏任务更新索引值。这本质上是一个
设置超时
,但请耐心听我说

这使您的StackBlitz订阅看起来像这样:

ngAfterContentInit(){
this.foos.changes.subscribe(()=>{
//宏任务
设置超时(()=>{
this.foos.forEach((foo,index)=>{
foo.index=索引;
});
}, 0);
});
}
这是一本书

因此javascript事件循环开始发挥作用。“ExpressionChangedAfter…”错误的原因是强调了正在对其他组件进行更改的事实,这本质上意味着应该运行另一个更改检测周期,否则您可能会在UI中得到不一致的结果。这是需要避免的

这归结起来就是,如果我们想要更新某些内容,但我们知道它不应该导致其他副作用,那么我们可以在宏任务队列中安排一些内容。更改检测过程完成后,才执行队列中的下一个任务


资源

整个事件循环都在javascript中,因为只有一个线程可以使用,所以了解发生了什么是很有用的

这更好地解释了Javascript事件循环,并深入到了微/宏队列的细节


为了更深入地了解和运行代码示例,我发现Jake Archibald的帖子非常好:

使用下面的代码,在下一个周期中进行更改

this.foos.changes.subscribe(() => {

  setTimeout(() => {
    this.foos.forEach((foo, index) => {
      foo.index = index;
    });
  });

});

这里的问题是,在视图生成过程进一步修改它试图首先显示的数据之后,您正在更改某些内容。理想的更改位置是在显示视图之前的生命周期钩子中,但是这里出现了另一个问题,即
this.foos
未定义的
,当这些钩子被称为QueryList时,仅在
ngAfterContentInit
之前填充

不幸的是,目前没有太多的选择。详细解释微观/宏观任务对于理解hacky
setTimeout
的工作原理非常有用

但是一个可观测的解决方案是使用更多的可观测/操作符(双关语),因此在我看来,管道延迟操作符是一个更干净的版本,因为
setTimeout
被封装在其中

ngAfterContentInit(){
this.foos.changes.pipe(延迟(0)).subscribe(()=>{
this.foos.forEach((foo,index)=>{
foo.index=索引;
});
});
}

这里是

如前所述,错误来自于更改周期评估后的值更改
{{index}}

更具体地说,视图使用本地组件变量
索引
来分配
0
。。。当一个新项目被推送到数组中时,它会被更改。。。只有在创建了上一个项目并将其添加到DOM中,且索引值为
0
之后,您的订阅才会为该项目设置真正的索引


setTimout
.pipe(delay(0))
(本质上是一样的)工作,因为它将更改链接到
this.model.push({})
在。。。如果没有它,则更改周期已经完成,单击按钮时,上一个周期中的0将在新/下一个周期中更改

将setTimeout方法的持续时间设置为
500
ms,您将看到它真正在做什么

 ngAfterContentInit() {
    this.foos.changes.pipe(delay(0)).subscribe(() => {
      this.foos.forEach((foo, index) => {
        setTimeout(() => {
          foo.index = index;
        }, 500)
      });
    });
  }
  • 它确实允许在渲染元素后设置值 DOM在避免错误的同时,您将不具有该值 在构造过程中在组件中可用,如果 你需要它
FooComponent
中的以下内容将始终导致
0
setTimeout
解决方案

ngOnInit(){
    console.log(this.index)
  }
将索引作为如下所示的输入传递,将使 在构造函数或
FooComponent的
ngOnInit
期间可用

export class FooComponent  {
  // index: number = 0;
  @Input('index') _index:number;

您提到不希望绑定到模板中的索引,但不幸的是,在您的示例中,这是在DOM上呈现元素之前传递索引值的唯一方法,默认值为
0

您可以接受
FooComponent

export class FooComponent  {
  // index: number = 0;
  @Input('index') _index:number;
然后将索引从循环传递到输入

<foo *ngFor="let foo of model; let i = index" [index]="i"></foo>

然后在视图中使用输入

selector: 'foo',
  template: `<div>{{_index}}</div>`,
选择器:“foo”,
模板:`{{u index}}`,
这将允许您通过
*ngFor
app.component
级别管理索引,并在呈现时将其传递到DOM上的新元素中。。。基本上避免了将索引分配给组件变量的需要,并且还确保在渲染/类初始化时,在更改周期需要时提供
true
索引

Stackblitz


我真的不知道应用程序的类型,但为了避免使用有序索引,它很有用
@Component({
  selector: 'foo',
  template: `<div>{{index}}</div>`,
})
export class FooComponent  {
  @Input() index: number = 0;

  constructor(@Host() @Inject(forwardRef(()=>HelloComponent)) private hello) {}

  getIndex() {
    if (this.hello.foos) {
      return this.hello.foos.toArray().indexOf(this);
    }

    return -1;
  }
}

@Component({
  selector: 'hello',
  template: `<ng-content></ng-content>
    <button (click)="addModel()">add model</button>`,
})
export class HelloComponent  {
  @Input() model = [];
  @ContentChildren(FooComponent) foos: QueryList<FooComponent>;

  constructor(private cdr: ChangeDetectorRef) {}



  addModel() {
    this.model.push({});
  }
}