У меня есть реактивная форма с выбором элемента управления. Все работает нормально, но я не могу запустить тест по умолчанию Жасмин, я получаю эту ошибку:
xxxxComponent> должен создать
Ошибка: не удается найти другой поддерживающий объект '[object Object]' типа 'object'. NgFor поддерживает только привязку к итерациям, таким как массивы.
Вот моя разметка:
<div class="container-fluid">
<div class="row">
<div class="col-md-3">
<h1 class="blue">Jobs</h1>
</div>
</div>
<form [formGroup]="jobsFilterForm" (ngSubmit)="onSubmitGetAllJobs(jobsFilterForm.value)">
<div id="filtercontrols" class="row">
<div class="col-sm-12 col-md-3">
<div class="form-group d-flex" style="text-align: left">
<label [for]="startDate" class="col-form-label w-25">Date From</label>
<input type="date" formControlName="from" class="form-control">
</div>
<div>
<ul *ngFor="let validation of filterValidationMessages.from">
<li class="error-message" *ngIf="jobsFilterForm.get('from').hasError(validation.type)">{{validation.message}}</li>
</ul>
</div>
<div class="form-group d-flex" style="text-align: left">
<label [for]="endDate" class="col-form-label w-25">Date To</label>
<input type="date" formControlName="to" class="form-control">
</div>
<div>
<ul *ngFor="let validation of filterValidationMessages.to">
<li class="error-message" *ngIf="jobsFilterForm.get('to').hasError(validation.type)">{{validation.message}}</li>
</ul>
</div>
</div>
<div class="col-sm-12 col-md-3">
<div class="form-group d-flex" style="text-align: left">
<label [for]="reportingPeriod" class="col-form-label w-25">and/or</label>
<select formControlName="reportingPeriod" class="form-control" id="reportingPeriod">
<option [ngValue]="null" hidden>Reporting Period</option>
<option *ngFor="let reportingPeriod of (reportingPeriods$ | async)" [ngValue]="reportingPeriod">{{ reportingPeriod.name }}</option>
</select>
</div>
</div>
<div class="col-sm-12 col-md-3">
<div class="form-group d-flex flex-row-reverse">
<select formControlName="restateVersion" class="form-control" id="restateVersion" style="width:80%">
<option [ngValue]="null" hidden>Restate Version</option>
<option *ngFor="let restateVersion of (restateVersions$ | async)" [ngValue]="restateVersion">{{ restateVersion.name }}</option>
</select>
</div>
</div>
<div class="col-sm-12 col-md-3">
<div class="form-group d-flex">
<button type="submit" [disabled] = "jobsFilterForm.invalid || (!jobsFilterForm.get('from').value && !jobsFilterForm.get('restateVersion').value && !jobsFilterForm.get('reportingPeriod').value)" class="btn btn-primary">Refresh <i class="fa fa-refresh"></i></button>
<button type="button" (click) = "onReset()" class="btn btn-warning">Reset <i class="fa fa-times"></i></button>
</div>
</div>
</div>
</form>
<div class="jobs-card border rounded">
<div class="row">
<div class="col-sm-12" >
<ag-grid-angular
domLayout='autoHeight'
style="width: 100%;"
class="ag-theme-balham"
[rowData]="(jobs$ | async)"
[columnDefs]="columnDefs"
>
</ag-grid-angular>
</div>
</div>
</div>
</div>
Это сложный компонент, но код, где я создаю форму, находится здесь:
this.jobsFilterForm = new FormGroup({
from: new FormControl(),
to: new FormControl(),
restateVersion: new FormControl(null)
});
У кого-нибудь есть идеи?
Полный код компонента здесь:
import { Component, OnInit } from '@angular/core';
import { Store, select } from '@ngrx/store';
import { distinctUntilChanged } from 'rxjs/operators';
import { LoadJobs, LoadReportingPeriods, LoadRestateVersions } from '../../actions/jobs.actions';
import { Filter, FilterExpression, GetAllRequest, Pagination, SortOrder, SortOrderExpression } from '../../../services/client';
import { AbstractControl, FormControl, FormGroup, Validators, ValidatorFn } from '@angular/forms';
import { JobsValidator } from '../../validators/jobs.validator';
import 'date-input-polyfill';
import * as moment from 'moment';
@Component({
selector: 'app-jobs',
templateUrl: './jobs.component.html',
styleUrls: ['./jobs.component.scss']
})
export class JobsComponent implements OnInit {
columnDefs = [
{headerName: 'Reporting Period', field: 'reportingPeriod' },
{headerName: 'Restate Version', field: 'restateVersion' },
{headerName: 'Model Name', field: 'model'},
{headerName: 'Company', field: 'company'},
{headerName: 'Purpose', field: 'reportingPurpose'},
{headerName: 'Data Set Category', field: 'dataSetCategory'},
{headerName: 'Data Set Name', field: 'dataSetName'},
{headerName: 'Data Set Version', field: 'dataSetVersion'},
{headerName: 'Job Status', field: 'jobStatus'},
{headerName: 'Job Priority', field: 'jobPriority'},
{headerName: 'Number of Sims', field: 'numberOfSimulations'},
{headerName: 'Random Seed', field: 'randomSeed'}
];
filterValidationMessages = {
from: [
{ type: 'required', message: 'From date is required' }
],
to: [
{ type: 'invalidDateRange', message: 'Date To should be earlier than Date From' },
{ type: 'required', message: 'To date is required' }
]
};
constructor(private store: Store<any>) {}
jobsFilterForm: FormGroup;
startDate: Date;
endDate: Date;
jobs$ = this.store.pipe(select
(
(state) =>
state.jobs.jobsList
)
);
reportingPeriods$ = this.store.pipe(select
(
(state) =>
state.jobs.reportingPeriods
)
);
restateVersions$ = this.store.pipe(select
(
(state) =>
state.jobs.restateVersions
)
);
ngOnInit() {
this.createForm();
this.loadReportingPeriods();
this.loadRestateVersions();
}
createForm() {
this.jobsFilterForm = new FormGroup({
from: new FormControl(),
to: new FormControl(),
reportingPeriod: new FormControl(null),
restateVersion: new FormControl(null)
});
const {from, to } = this.jobsFilterForm.controls;
from.valueChanges.pipe(distinctUntilChanged()).subscribe(() => this.onFilterDateChange());
to.valueChanges.pipe(distinctUntilChanged()).subscribe(() => this.onFilterDateChange());
}
// dynamically set / remove validators on date controls depending on their selected state
onFilterDateChange() {
const {from, to } = this.jobsFilterForm.controls;
// neither date is set so no validation needed
if (!from.value && !to.value) {
this.setDateValidators(from, null);
this.setDateValidators(to, null);
}
if (!(from.validator)) {// no need to check both, if validation is on one, it's on the other
this.setDateValidators(from, [Validators.required]);
// only need to have date range validator on one control
this.setDateValidators(to, [Validators.required, JobsValidator.validDateRange(this.jobsFilterForm)]);
}
// need to call updateValueAndValidity after setting the validators because calling either will trigger
// valueChanges and onFilterDateChange is called again before the original call completes
// and the desired sequence isn't completed and results are unpredictable.
from.updateValueAndValidity();
to.updateValueAndValidity();
}
setDateValidators(control: AbstractControl, validators: ValidatorFn[]) {
control.setErrors(null);
control.clearValidators();
control.setValidators(validators);
}
onSubmitGetAllJobs(value) {
const request = new GetAllRequest(
{
filter: new Filter({
filterExpressions: [
]
}),
sortOrder: new SortOrder({
sortOrderExpressions: [
new SortOrderExpression({
sortOrderColumnName: 'reportingPeriod',
sortOrderDirection: 1
})
]
}),
pagination: new Pagination({ startIndex: 0, pageSize: 25})
}
);
if (value.from && value.to) {
request.filter.filterExpressions.push(
new FilterExpression({filterColumnName: 'SubmittedDate', filterOperator: 3, filterValue: moment(value.from).format('YYYY/MM/DD')}),
new FilterExpression({filterColumnName: 'SubmittedDate', filterOperator: 4, filterValue: moment(value.to).format('YYYY/MM/DD')})
);
}
if (value.reportingPeriod) {
request.filter.filterExpressions.push(
new FilterExpression({filterColumnName: 'ReportingPeriod', filterOperator: 1, filterValue: value.reportingPeriod.name})
);
}
if (value.restateVersion) {
request.filter.filterExpressions.push(
new FilterExpression({filterColumnName: 'RestateVersion', filterOperator: 1, filterValue: value.restateVersion.name})
);
}
this.store.dispatch(new LoadJobs(request));
}
loadReportingPeriods() {
this.store.dispatch(new LoadReportingPeriods());
}
loadRestateVersions() {
this.store.dispatch(new LoadRestateVersions());
}
После комментария пользователя 2216584 я исправил это, назначив наблюдаемое для restteVersions $ (и reportsPeriod $) в component.spec.ts:
beforeEach(() => {
fixture = TestBed.createComponent(JobsComponent);
component = fixture.componentInstance;
Object.defineProperty(component, 'reportingPeriods$', { writable: true });
component.reportingPeriods$ = of([]);
Object.defineProperty(component, 'restateVersions$', { writable: true });
component.restateVersions$ = of([]);
fixture.detectChanges();
});