У меня есть форма поиска, которая обновляет строку запроса и URL-адрес с записью пользователя. Таким образом, пользователи могут поделиться URL-адресом с кем-то еще, и что кто-то другой получит те же результаты поиска и форму поиска, заполненную таким же образом.
Кроме того, часть формы может иметь много Записи: пользователь может нажать кнопку, чтобы добавить произвольное количество дополнительных пар полей ввода, чтобы добавить ограничения для определенного аспекта поиска.
Моя борьба с успешным повторным заполнением пар полей ввода с переменным количеством из строки запроса.
Если я использую URL localhost:4200/search?type-0=foo&value-0=bar&type-1=fun&value-1=lala&type-2=&value-2=
, я бы хотел, чтобы компонент имел два элемента в searchForm.assignments
: {type: 'foo', value: 'bar'}
и {type: 'fun', value: 'lala'}
.
Код во фрагменте очень некрасиво, и все равно не работает. Я не могу найти хороший способ заполнить форму из строки запроса. Я борюсь с множеством ошибок "объект, возможно, ноль" (отсюда и все // @ts-ignore
на данный момент), а также с ошибкой ERROR Error: "Must supply a value for form control with name: 'type'."
при попытке setValue
(см. [0]).
Это компонент:
export class SearchComponent implements OnInit {
public searchForm: FormGroup;
// `| undefined` so that TS doesn't explode when it's not assigned in the
// constructor
public assignments: FormArray | undefined;
public results: [] | IssueEntity.AsObject[] = [];
public dueFrom: string = '';
public dueTo: string = '';
// the inputs on the page have
// [value]="this.searchFormValues.dueDateRange.dueFrom" etc
// to populate them from query string. Probably not the right
// way of doing it.
public searchFormValues: { [key: string]: string | [] } = {};
// this keeps track of how many times the "add fields" button
// was clicked, to not add too many sets
public extraAssignmentsSets: number = 0;
constructor(
private formBuilder: FormBuilder,
// service talking to the API and doing the search
private issuesService: IssuesService,
private router: Router,
private route: ActivatedRoute,
) {
this.searchForm = this.formBuilder.group({
dueDateRange: this.formBuilder.group(
{ due_from: '', due_to: '' },
{ validator: this.DateRangeValidator },
),
refIds: [],
refTypes: [],
assignments: this.formBuilder.array([this.createAss()]),
});
}
public ngOnInit(): void {
// on loading the component, check if there are any params,
// and if so populate the form, and execute the search
this.route.queryParams.subscribe(params => {
this.rehydrateSearchForm(params);
this.search(params);
});
}
// my nightmare method. tries to figure out how many
// extra fieldsets are required and add them. Then wrangles
// the type-\d and value-\d parameters to format them
// back into a form that fits this.searchForm.
public rehydrateSearchForm(params: { [key: string]: string }): void {
this.searchFormValues = params;
// @ts-ignore
const assSetsInParamsCount = Object.keys(this.searchFormValues).filter(k =>
k.match(/^value-\d+$/),
).length;
// the page comes with one ass set on load. So to get
// the current count, we add one => 1 already on
// page + extraAssignmentsSets added = total count
const assSetsOnPageCount = this.extraAssignmentsSets + 1;
const requiredAssSetsCount = assSetsInParamsCount - assSetsOnPageCount;
this.addAss(requiredAssSetsCount);
// there are constraints in the query string, so we must
// attempt to restore their value into the form
if (assSetsInParamsCount > 0) {
// extract the relevant keys' names amongst all the
// params from the query string
const assData = Object.keys(this.searchFormValues).filter(k =>
k.match(/^value-\d+$/),
);
// and then for each relevant key name, extract its index
// so we know which set to update in this.searchForm
assData.forEach(k => {
// @ts-ignore
const assIndex = parseInt(k.match(/^(value|type)-(\d+)$/)[2], 10);
// keep the old value so we add the pair to the rest,
// rather than overwrite everything
// @ts-ignore
const oldValue = this.searchForm.get('assignments').value;
const newValue = [...oldValue];
// and add the extra set of {type, value}
newValue[assIndex] = params[k];
// finally update the form (this blows up and never works)
// @ts-ignore
this.searchForm.get('assignments').setValue(newValue); // [0] error here ?
});
}
}
// create a new field pair
public createAss(): FormGroup {
return this.formBuilder.group(
{ type: '', value: '' },
{ validator: this.AssValidator },
);
}
// add one or more fieldsets to the existing form
public addAss(count: number = 1): void {
this.assignments = this.searchForm.get('assignments') as FormArray;
// supports adding more than one field at once, useful
// when restoring search from query string.
for (let i = 0; i < count; i++) {
this.extraAssignmentsSets += 1;
this.assignments.push(this.createAss());
}
}
// on submit, update the query params and redirect to the
// search route but with the updated query params. onInit
// will perfrom a search based on these new query params
// and show the result.
public updateQueryParams(query: {
[key: string]: string | [] | object;
}): void {
const queryParams = this.buildQueryParams(query);
this.router.navigate(['/search'], {
queryParams,
queryParamsHandling: 'merge',
});
}
public search(query: {
refIds?: string;
refTypes?: string;
assignments?: { [key: string]: string }[];
dueDateRange?: { due_from: string; due_to: string };
}): void {
const filters = mergeAll([
this.buildDueDateFilter(query),
this.buildRefFilter(query),
this.buildAssignmentsFilter(query),
]);
this.issuesService.getIssueEntities(filters).subscribe(issues => {
this.results = issues;
});
}
}
Каким был бы разумный и эффективный способ добиться этого?