角阵列变化检测
Posted
技术标签:
【中文标题】角阵列变化检测【英文标题】:Angular array changes detection 【发布时间】:2019-04-17 09:16:12 【问题描述】:我是 Angular 的初学者,我找不到适合我的问题的解决方案。
我有一个包含项目列表的组件(列表中的每个项目都在另一个组件中),并且第三个组件包含过滤器。过滤器包含多个复选框和一个过滤器按钮。
我将复选框状态的布尔数组从 filterComponent 通过 itemsListComponent 发送到 singleItemComponent,它工作正常。我的问题是更改检测。当我使用 NgDoCheck 时,它总是在我单击复选框和过滤器按钮而不是仅过滤器按钮时起作用。
我尝试了 NgOnChanges,但它只工作了一次,然后它没有看到数组中的值有任何变化。
这是我的 SingleItemsComponent(我想你不需要别人帮我解决这个问题)。看看这个,请告诉我解决这个问题的任何例子。
import Component, OnInit, OnChanges,ViewChild, AfterViewInit, Inject, Input, DoCheck, KeyValueDiffer, KeyValueDiffers, SimpleChanges, SimpleChange, ChangeDetectionStrategy, ChangeDetectorRef, IterableDiffers from '@angular/core';
import ActivatedRoute from '@angular/router';
import animate, state, style, transition, trigger from '@angular/animations';
import MatTableDataSource, MatPaginator, MatSort, throwToolbarMixedModesError from '@angular/material';
import IPOLine from 'src/app/models/po-line';
import POLineService from 'src/app/services/po-line.service';
@Component(
selector: 'app-po-lines-list',
templateUrl: './po-lines-list.component.html',
styleUrls: ['./po-lines-list.component.css'],
animations: [
trigger('detailExpand', [
state('collapsed', style( height: '0px', minHeight: '0', display: 'none' )),
state('expanded', style( height: '*' )),
transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
]),
],
)
export class POLinesListComponent implements OnInit, DoCheck
isLogged = false;
login: string;
dataSource = new MatTableDataSource<IPOLine>();
expandedElement: IPOLine;
errorMessage: string;
response: any;
today = new Date(); // (1990, 0, 1);
isLoadingList = true;
differ: any;
@Input() sentData: boolean[];
_sentData = this.sentData;
ngDoCheck()
var changes = this.differ.diff(this._sentData);
if (changes)
console.log('changes detected changes detected changes detected changes detected changes detected ');
else
console.log('changes not detected changes not detected changes not detected changes not detected ');
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
constructor(
//private cd: ChangeDetectorRef,
private differs: KeyValueDiffers,
private _POLineService: POLineService,
private activatedRoute: ActivatedRoute)
this.differ = differs.find().create();
ngOnInit()
// Assign the data to the data source for the table to render
this.login = this.activatedRoute.snapshot.paramMap.get('login');
this._POLineService.getUserPOLines(this.login)
.subscribe(data =>
this.dataSource.data = data;
,
error =>
this.errorMessage = <any>error;
this.isLoadingList = false;
,
() =>
// console.log('eee' + this.dataSource.data);
this.isLoadingList = false;
this.dataSource.data.forEach(x => x.isExpired = new Date(x.PromisedDate) < new Date() ? true : false);
);
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
我很抱歉代码中的混乱,但我尝试了很多东西,我什至不记得是什么。感谢您的帮助。
FilterComponent.ts:
import Component, OnInit, Output, EventEmitter, Input from '@angular/core';
@Component(
selector: 'app-filters',
templateUrl: './filters.component.html',
styleUrls: ['./filters.component.css']
)
export class FiltersComponent implements OnInit
@Output() filterByClass = new EventEmitter();
filterByTTR = false;
filterByTBR = false;
filters:boolean[] = [];
constructor()
ngOnInit()
log()
if(!this.filterByTTR)
console.log('TTR not checked');
else
console.log('TTR checked');
if (!this.filterByTBR)
console.log('TBR not checked');
else
console.log('TBR checked');
this.filters[0] = this.filterByTTR;
this.filters[1] = this.filterByTBR;
this.filterByClass.emit(this.filters);
FilterComponent.html 的一部分:
<mat-card-content class="col">
<mat-checkbox [checked]="filterByTTR" (change)="filterByTTR = !filterByTTR">TTR</mat-checkbox>
<mat-checkbox [checked]="filterByTBR" (change)="filterByTBR = !filterByTBR">TBR</mat-checkbox>
<!-- <mat-checkbox [(ngModel)]="filterByTBR"ref-TBR>TBR</mat-checkbox> -->
<div class="d-flex justify-content-between">
<button mat-raised-button color="primary">Clear</button>
<button mat-raised-button (click)="log()" color="primary">Apply</button>
</div>
PoLinesCompoenent.ts:
import Component, OnInit, NgModule, ViewChild, Input from
'@angular/core';
@Component(
selector: 'app-po-lines',
templateUrl: './po-lines.component.html',
styleUrls: ['./po-lines.component.css']
)
export class POLinesComponent implements OnInit
count: boolean[];
constructor()
ngOnInit()
displayCounter(count)
console.log('first value is: ' + count[0] + ' second value is: ' + count[1]);
this.count = count;
PoLinesComponent.html:
<div class="container">
<div class="row">
<div class="side-bar col">
<app-filters (data)='displayCounter($event)'></app-filters>
</div>
<div class="content col">
<app-po-lines-list [sentData]="count"></app-po-lines-list>
</div>
</div>
</div>
【问题讨论】:
当@Input() sentData: boolean[];
从外部发生变化时你想检测变化吗?
没错,我正在尝试检测其他组件的变化。你知道这里出了什么问题吗?
好的,所以当sentData
通过父组件更改时,基本上你想在 POLinesListComponent 中执行一些操作,对吧?
就是这样,我选中复选框,单击过滤器按钮,然后将它们的状态数组转到 PoLinesComponent ,它是 PoLinesListComponent 和 FilterComponent 的父组件。
知道了。看我的回答
【参考方案1】:
Angular 更改检测仅检查是否通过引用更改了某些内容。在您的父组件中,您必须创建一个新数组并将其分配给变量,每当您的数组中的条目发生更改时。只需将数组视为不可变对象。
【讨论】:
我对 Immutables 很感兴趣,而且我知道当需要重新创建大对象时这不是最好的解决方案,这意味着必须有另一种方法来做到这一点。事实上,我会尝试与他们一起做,因为这个数组不会很大。 没有其他方法可以做到这一点。检查数组中的任何元素是否更改比重新创建它要慢得多。如果性能是一个问题,您可以做些什么来绕过这个问题是自己安排更改检测器 - 将数组更改为您想要的 mucb,重新创建一次,然后使用 ChangeDetectorRef 标记要检查的组件。此外,重新创建数组的开销通常并不多。【参考方案2】:如果父级更改了“sentData”输入,您可以使用带输入的 setter 来检测更改。每次输入从外部更改时,都会调用 setter 函数。例如,您可以执行以下操作:
@Input('sentData')
set sentData(value:any)
//this function will be called whenever value of input changes. the value parameter will contain the new changed
this._sentData=value;
//things to perform on change go here
_sentData;
有关输入更改检测的更多详细信息,请参阅此链接:Intercept @Input property change in Angular 希望这会有所帮助。
如果通过 parent 传入的值不是原始类型,则 angular 不会检测到该对象的变化,并且不会在子对象中调用 setter,即POLinesListComponent
。要解决此问题并检测到您的更改,您可以使用 javascript 的 Object.assign
函数,该函数将更改 this.count
的引用,从而触发角度变化检测。
您可以在 displayCounter
函数中执行以下操作:
displayCounter(count)
console.log('first value is: ' + count[0] + ' second value is: ' + count[1]);
//if count is changed, then only trigger change.
if(JSON.stringify(this.count)!=JSON.stringify(count))
this.count= [];
Object.assign(this.count, count);
我什至创建了一个类似于您的实现的 stackblitz,其中可以正确检测到更改,并且每次应用过滤器时都会将新计数的值记录到控制台。这里Input Change Detection for Object
【讨论】:
这是我之前做过的类似的事情,它的行为完全一样。我按照您的指示添加了 console.log 以查看每次过滤时它是否到达 setter。不幸的是,它第一次只看到一次更改。 父组件中的值是如何改变的?是两种方式绑定到控件还是在函数中手动更改? 稍后我将编辑我的问题并添加其他组件代码。以上是关于角阵列变化检测的主要内容,如果未能解决你的问题,请参考以下文章