'ANGULAR push element in formArray no rendering
I have a problem, as you will see in the images, when I add a new row that executes the add row function it creates a visual bug of the previous row, the autocoplete component in the html takes care of adding the options to the select and autocompletes what you write inside with a direct search to the db, below I report the images of the html bug of the component, file.ts and the servece form which is an additional ts for the final render, so after pressing the confirm button, what could be the problem? or a possible resolution without touching the compentee custom autocomplete component? enter image description hereenter image description hereenter image description hereenter image description hereenter image description here
esposizione-servizio.component.html
<!-- Esposizione del servizio -->
<div [formGroup]="servizioForm">
<h2 class="h5 text-uppercase">
{{ 'servizio.esposizione.title' | translate }}
<em *ngIf="f.esposizioneServizi.value.length == 0" [attr.id]="alertEsposizione" class="text-primary fa fa-exclamation-triangle cursor-pointer ml-2" title="Campo obbligatorio" appTippy></em>
</h2>
<!-- modalita visualizza -->
<div *ngIf="false == panels.panel.esposizioneServizi.inEdit" class="border" [attr.id]="esposizione" title="Punto di accesso, Area e Sezione in cui il servizio è esposto" appTippy>
<app-btn-modifica
[userInfo]="userInfo"
[panels]="panels"
[panelName]="'esposizioneServizi'"
(btnClick)="setEdit($event,true)"
></app-btn-modifica>
<div *ngIf="f.esposizioneServizi.length > 0" class="table-responsive">
<div class="pb-4 pb-5 pl-3 pr-3">
<table class="table">
<caption class="sr-only">{{ 'servizio.esposizione.caption' | translate }}</caption>
<thead>
<tr>
<!-- Primo campo nome punto di accesso -->
<th scope="col">{{ 'servizio.esposizione.puntoAccesso' | translate }}</th>
<!-- secondo campo nome area -->
<th scope="col">{{ 'servizio.esposizione.Area' | translate }}</th>
<!-- Terzo campo nome sezione-->
<th scope="col">{{ 'servizio.esposizione.Sezione' | translate }}</th>
</tr>
</thead>
<!-- iterazione sugli elementi -->
<tbody *ngFor="let esp of f.esposizioneServizi.value">
<tr>
<td>{{esp.puntoAccesso.nome}}</td>
<td>{{esp.area.nome}}</td>
<td>{{esp.sezione.nome}}</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Nessuna tripla -->
<p *ngIf="f.esposizioneServizi.length == 0" class="pr-4 pb-5 pl-4" [ngClass]="{'p-top-10': userInfo.data.ruolo == RUOLI.read}">
<em>{{ 'servizio.esposizione.nessunModello' | translate }}</em>
</p>
</div>
<!-- Modalita modifica -->
<div *ngIf="true == panels.panel.esposizioneServizi.inEdit" class="pt-4 pr-4 pl-4 border">
<!-- Tabella mapping presenti -->
<div *ngIf="f.esposizioneServizi.length > 0" class="table-responsive">
<table class="table ">
<caption class="sr-only">{{ 'servizio.esposizione.caption' | translate }}</caption>
<thead>
<tr>
<th scope="col">{{ 'servizio.esposizione.puntoAccesso' | translate }}</th>
<th scope="col">{{ 'servizio.esposizione.Area' | translate }}</th>
<th scope="col">{{ 'servizio.esposizione.Sezione' | translate }}</th>
<th scope="col">
<span class="sr-only">{{ 'servizio.esposizione.elimina' | translate }}</span>
</th>
</tr>
</thead>
<tbody *ngFor="let m of this.servizioForm['controls']['esposizioneServizi']['controls']; let $index = index">
<!-- Autocomplete per ogni mapping-->
<tr>
<td>
<!-- Autocomplete punto di accesso -->
<autocomplete-component
[rowId] = "$index"
[ident]="'puntoAccesso' + $index"
[label]="'Punto di accesso:'"
[settings]="apiUrl + '/search/punto-di-accesso'"
[selectionAttribute]="'puntoAccesso'"
[language]="'it'"
[minimumInputLength]="2"
[allowClear]="false"
[processResults]="processResults"
[valid]="panels.getError(m['controls']['puntoAccesso']) == ''"
[validFormat]="panels.getError(m['controls']['puntoAccesso']) == ''"
[errorMessage]="panels.getError(m['controls']['puntoAccesso'])"
(subscription)="setVal($event, m['controls']['puntoAccesso'], 'nome', m['controls'])"
[headerParams]="headerParams"
>
</autocomplete-component>
</td>
<td>
<!-- Autocomplete Area -->
<autocomplete-component
[rowId] = "$index"
[ident]="'area' + $index"
[label]="'Area:'"
[settings]="apiUrl + '/search/area'"
[selectionAttribute]="'area'"
[language]="'it'"
[minimumInputLength]="2"
[allowClear]="false"
[processResults]="processResults"
[valid]="panels.getError(m['controls']['area']) == ''"
[validFormat]="panels.getError(m['controls']['area']) == ''"
[errorMessage]="panels.getError(m['controls']['area'])"
(subscription)="setVal($event, m['controls']['area'], 'nome', m['controls'])"
[headerParams]="headerParams"
>
</autocomplete-component>
</td>
<td>
<!-- autocomplete Sezione -->
<autocomplete-component
[rowId] = "$index"
[ident]="'sezione' + $index"
[label]="'Sezione:'"
[settings]="apiUrl + '/search/sezione'"
[selectionAttribute]="'sezione'"
[language]="'it'"
[minimumInputLength]="2"
[allowClear]="false"
[processResults]="processResults"
[valid]="panels.getError(m['controls']['sezione']) == ''"
[validFormat]="panels.getError(m['controls']['sezione']) == ''"
[errorMessage]="panels.getError(m['controls']['sezione'])"
(subscription)="setVal($event, m['controls']['sezione'], 'nome', m['controls'])"
[headerParams]="headerParams"
>
</autocomplete-component>
</td>
<td class="pb-3 text-right align-bottom">
<!-- Botton elimina -->
<button-component
[aColor]="'lightblue'"
[aText]="'Elimina'"
(clickButton)="removeRow($index, f.esposizioneServizi)">
</button-component>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Messaggio di mapping assenti -->
<p *ngIf="f.esposizioneServizi.length == 0">
<em>{{ 'servizio.esposizione.nessunModello' | translate }}</em>
</p>
<!-- Aggiungi modello -->
<p class="mt-3 text-right">
<button-component
[aColor]="'default'"
[aText]="'Aggiungi esposizione'"
(clickButton)="addRow(f.esposizioneServizi)"
>
</button-component>
</p>
<!-- Salva modello -->
<p class="mt-5">
<button-component
[aColor]="'lightblue'"
[aText]="'Conferma'"
[isDisabled]="f.esposizioneServizi.invalid"
(clickButton)="panels.patch('esposizioneServizi', true)"
>
</button-component>
<!-- Annulla modifiche -->
<button-component
[aColor]="'default'"
[aText]="'Annulla'"
(clickButton)="panels.resume('esposizioneServizi')"
>
</button-component>
</p>
</div>
</div>
esposizione-servizio.component.ts
import {ChangeDetectorRef, Component, Input} from '@angular/core';
import {FormArray, FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms';
import {ResourcesService} from '@services/resources/resources.service';
import {RUOLI, servizi} from '../../../../model/Environment';
import {MessageService} from '@services/messages/message.service';
import {AutocompleteHelper} from 'noipa-kituxui/ngx'
import {AppConfigService} from '@services/app-config/app-config.service';
@Component({
selector: 'app-esposizione-servizio',
templateUrl: './esposizione-servizio.component.html',
styleUrls: ['./esposizione-servizio.component.css']
})
export class EsposizioneServizioComponent {
//costante per verificare se il ruolo è di sola lettura o lettura e scrittura
readonly RUOLI = RUOLI
/*
Informazioni utente ricevute da parent
*/
@Input() userInfo: any
/*
Helpers ricevuti da parent
*/
@Input() panels: any
/*
Elenchi dati condivisi con parent
*/
@Input() lists: any
/*
Dati del servizio ricevuti da parent
*/
@Input() servizio: any
/*
Oggetto form del servizio ricevuto da parent
*/
@Input() servizioForm: FormGroup
/*
Struttura ausiliaria per la memorizzazione dei dati
*/
data: any = []
/*
Headers per request nelle chiamate di autocomplete
*/
headerParams: any
/*
Url per request nelle chiamate di autocomplete
*/
apiUrl: string
tipologia: any
/*
Id progressivo fake per la generazione di oggetti custom
*/
idCustom = 0
//variabili per la gestione dei tooltip
public esposizione = 'esposizione';
public alertEsposizione = 'alertEsposizione';
/*
Alias per l'oggetto form del servizio
*/
get f() {
return this.servizioForm['controls']
}
/*
Override - Attiva/disattiva la modifica del pannello mappings
*/
setEdit(panel, value) {
this.panels.setEdit(panel, value)
/*
Inizializza l'autocomplete sui campi di mapping
*/
this.f['esposizioneServizi'].value.forEach((mapping, index) => {
new AutocompleteHelper().select('puntoAccesso' + index, mapping.puntoAccesso.nome)
new AutocompleteHelper().select('area' + index, mapping.area.nome)
new AutocompleteHelper().select('sezione' + index, mapping.sezione.nome)
})
}
/*
Rimuove un mapping dalla lista
index: indice ordinale
fa: oggetto form
*/
removeRow(index, fa: FormArray) {
fa.removeAt(index)
this.servizioForm.markAsDirty()
this.servizioForm.markAsTouched()
}
/*
Aggiunge una riga vuota all'oggetto form mappings
integrando la validazione sui campi
*/
addRow(fa: FormArray) {
fa.push(
this.formBuilder.group({
id: ['', []],
puntoAccesso: this.formBuilder.group({
nome: ['', [Validators.required, Validators.minLength(2), Validators.maxLength(2000)]],
id: ['', []]
}),
area: this.formBuilder.group({
nome: ['', [Validators.required, Validators.minLength(2), Validators.maxLength(2000)]],
id: ['', []]
}),
sezione: this.formBuilder.group({
nome: ['', [Validators.required, Validators.minLength(2), Validators.maxLength(2000)]],
id: ['', []]
})
})
)
}
/*
Elabora la lista dei risultati ricevuta da servizio di autocomplete
Vale per puntoAccesso, area, sezione
*/
processResults = (data, params) => {
/*
Rielabora la lista ritornata da servizio
*/
let list = {
results: data.map(item => {
return {id: item.id, text: item.nome}
})
}
/*
Se il valore non è presente, ne crea uno custom
*/
if (list.results.filter(l => (l.text || '').toUpperCase() == params.term.toUpperCase()).length == 0)
list.results.push({id: '$' + (++this.idCustom), text: params.term})
/*
Elimina i duplicati dalla lista
*/
list.results = list.results.filter(item => (new RegExp(params.term, 'i')).test(item.text))
/*
Ordina i valori per nome
*/
list.results.sort((e1, e2) => e1.text.localeCompare(e2.text))
/*
Resituisce la lista rielaborata per l'autocomplete
*/
return list
}
/*
Imposta il valore del model sull'onChange del campo
event: il valore
fc: oggetto form
field: nome del campo (puntoAccesso, area, sezione)
fg: oggetto form del campo
*/
setVal(event, fc: FormControl, field, fg) {
const {id} = event.$event
const {text} = event.$event
const {rowId} = event.$event
let value: any = {}
/*
Ripulisce l'id se il valore è custom
*/
value.id = /^\$/.test(id) ? '' : id
/*
Assegna il valore
*/
value[field] = text
/*
Invia il valore al servizio
*/
fc.patchValue(value)
/*
Ripulisce il campo
*/
fg['id'].setValue('')
this.servizioForm.markAsDirty()
this.servizioForm.markAsTouched()
new AutocompleteHelper().select(field + rowId, text)
}
constructor(
private appConfig: AppConfigService,
private formBuilder: FormBuilder,
private messageService: MessageService,
private resourcesService: ResourcesService
) {
const config = this.appConfig.getConfig()
this.apiUrl = config[servizi].apiUrl
}
ngOnInit() {
/*
Riceve l'headers per l'invocazione del servizio di autocomplete
*/
this.headerParams = this.resourcesService.getHeaders(servizi)
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
