Prevent form closing on error
When opening a form on popup or calc mode, we want sometimes prevent the closing of the form even in error case. This page explains how to handle this.
Block form
Front
These configuration instructions are already included into features generated from the version 4.1.0 of BIA Framework.
- Ensure that your feature has store action called after success or fail to create or edit an item using the matching effect into the
my-feature-effects.ts
file.
Example with theupdate$
effect, the store action on success is on line 127 and on fail on line 134 :
- Add in your feature service that inherits from
CrudItemService<>
the values for the properties_updateSuccessActionType
,_createSuccessActionType
and_updateFailureActionType
matching the same store action :
export class MyFeatureService extends CrudItemService<MyFeature> {
/**
* Type of store action called after update effect is successful.
* See update effect in store effects of CRUD item.
*/
_updateSuccessActionType = FeatureMyFeaturesActions.loadAllByPost.type;
/**
* Type of store action called after create effect is successful.
* See create effect in store effects of CRUD item.
*/
_createSuccessActionType = FeatureMyFeaturesActions.loadAllByPost.type;
/**
* Type of store action called after update effect has failed.
* See update effect in store effects of CRUD item.
*/
_updateFailureActionType = FeatureEnginesActions.failure.type;
}
Now, each time a user will have an error when validating a form inside a popup or an editable row with calc mode, the form will stay opened.
Inform user of conflict
Sometimes, the data opened for edit in the form can be changed by another user at the same time. You can handle these changes directly in your form to inform your user of this conflict.
Back
These configuration instructions are already included into features generated from the version 4.1.0 of BIA Framework.
- Your entity must derived of
VersionedTable
type - Your mapper should include in the
EntityToDto()
method a mapping for theRowVersion
property of the DTO based on the same property from the entity :
public override Expression<Func<MyEntity, MyEntityDto>> EntityToDto()
{
return entity => new MyEntityDto
{
// [...]
// Add the RowVersion mapping
RowVersion = Convert.ToBase64String(entity.RowVersion),
}
}
- Add in your
Update()
endpoint of your entity controller the catch ofOutDatedException
and the handle of409
status code :
// Add this attribute
[ProducesResponseType(StatusCodes.Status409Conflict)]
public async Task<IActionResult> Update(int id, [FromBody] MyEntityDto dto)
{
try
{
// [...]
}
//Add this catch block
catch (OutDatedException)
{
return this.Conflict();
}
}
Front
- Add in your feature model the assign of the
BiaFieldConfig
for therowVersion
:
export const myFeatureFieldsConfiguration : BiaFieldsConfig<MyFeature> = {
columns: [
// [...]
Object.assign(new BiaFieldConfig('rowVersion', 'myFeature.rowVersion'), {
isVisible: false,
isHideByDefault: true,
}),
]
}
- In your feature edit component HTML template, add the binding for the
isCrudItemOutdated
property between the feature edit component and the feature form component :
<app-my-feature-form
[isCrudItemOutdated]="isCrudItemOutdated"></app-my-feature-form>
Warning displayed into the edit popup :
Warning displayed as toast on calc mode :
SignalR
You can inform your user with SignalR that the data on the opened form has been updated by another user.
This feature works only with the edit component on popup mode.
Back
- On your feature controller, enable the
UseHubForClientInAirport
constant definition and add a sending instruction to the client with the dedicated identifier and hub :
#define UseHubForClientInAirport
namespace MyCompany.MyProject.Presentation.Api.Controllers.MyFeature
{
// [...]
public class MyFeatureController : BiaControllerBase
{
// [...]
public async Task<IActionResult> Update(int id, [FromBody] MyEntityDto dto)
{
try
{
// [...]
var updatedDto = await this.airportService.UpdateAsync(dto);
// Add the signalR notification
#if UseHubForClientInAirport
await this.clientForHubService.SendTargetedMessage(string.Empty, "myfeatures", "update-myfeature", updatedDto);
#endif
return this.Ok(updatedDto);
}
catch
{
// [...]
}
}
}
}
Front
- Edit your constant feature file to use signalR :
export const airportCRUDConfiguration: CrudConfig<Airport> = new CrudConfig({
// [...]
useSignalR: true,
});
- Create or complete your feature signalR service with following methods
registerUpdate()
andunregisterUpdate()
:
import { Injectable } from '@angular/core';
import { BiaSignalRService } from 'src/app/core/bia-core/services/bia-signalr.service';
import { TargetedFeature } from 'src/app/shared/bia-shared/model/signalR';
@Injectable({
providedIn: 'root',
})
export class MyFeatureSignalRService {
private targetedFeature: TargetedFeature;
constructor(private signalRService: BiaSignalRService) {}
initialize() {
this.targetedFeature = {
parentKey: '',
featureName: 'myFeatures',
};
this.signalRService.joinGroup(this.targetedFeature);
}
registerUpdate(callback: (args: any) => void) {
console.log(
'%c [MyFeatures] Register SignalR : update-myFeature',
'color: purple; font-weight: bold'
);
this.signalRService.addMethod('update-myFeature', args => {
callback(args);
});
}
destroy() {
this.unregisterUpdate();
this.signalRService.leaveGroup(this.targetedFeature);
}
private unregisterUpdate() {
console.log(
'%c [MyFeatures] Unregister SignalR : update-myFeature',
'color: purple; font-weight: bold'
);
this.signalRService.removeMethod('update-myFeature');
}
}
- Complete your feature edit component in order to react to signalR notifications :
import { Component, Injector, OnDestroy, OnInit } from '@angular/core';
import { firstValueFrom } from 'rxjs';
import { CrudItemEditComponent } from 'src/app/shared/bia-shared/feature-templates/crud-items/views/crud-item-edit/crud-item-edit.component';
import { myFeatureCRUDConfiguration } from '../../my-feature.constants';
import { MyFeature } from '../../model/my-feature';
import { MyFeatureSignalRService } from '../../services/my-feature-signalr.service';
import { MyFeatureService } from '../../services/my-feature.service';
@Component({
selector: 'app-my-feature-edit',
templateUrl: './my-feature-edit.component.html',
})
export class MyFeatureEditComponent
extends CrudItemEditComponent<MyFeature>
implements OnInit, OnDestroy
{
private useSignalR = myFeatureCRUDConfiguration.useSignalR;
constructor(
protected injector: Injector,
public myFeatureService: MyFeatureService,
protected signalrService: MyFeatureSignalRService
) {
super(injector, myFeatureService);
this.crudConfiguration = myFeatureCRUDConfiguration;
}
ngOnInit() {
super.ngOnInit();
if (this.useSignalR) {
this.signalrService.initialize();
this.signalrService.registerUpdate(async args => {
const updatedCrudItem = JSON.parse(args) as MyFeature;
const currentCrudItem = await firstValueFrom(
this.myFeatureService.crudItem$
);
if (
currentCrudItem.id === updatedCrudItem.id &&
currentCrudItem.rowVersion !== updatedCrudItem.rowVersion
) {
this.isCrudItemOutdated = true;
}
});
}
}
ngOnDestroy() {
super.ngOnDestroy();
if (this.useSignalR) {
this.signalrService.destroy();
}
}
}