|
| 1 | +--- |
| 2 | +sidebar_position: 1 |
| 3 | +--- |
| 4 | + |
| 5 | +# Prevent form closing on error |
| 6 | +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. |
| 7 | + |
| 8 | +## Block form |
| 9 | +### Front |
| 10 | +**NOTE :** these configuration instructions are already included into features generated from the version **4.1.0** of BIA Framework. |
| 11 | +1. 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. |
| 12 | +Example with the `update$` effect, the store action on success is on **line 127** and on fail on **line 134** : |
| 13 | + |
| 14 | +1. Add in your feature service that inherits from `CrudItemService<>` the values for the properties `_updateSuccessActionType`, `_createSuccessActionType` and `_updateFailureActionType` matching the same store action : |
| 15 | +``` typescript title="my-feature.service.ts" |
| 16 | +export class MyFeatureService extends CrudItemService<MyFeature> { |
| 17 | + /** |
| 18 | + * Type of store action called after update effect is successful. |
| 19 | + * See update effect in store effects of CRUD item. |
| 20 | + */ |
| 21 | + _updateSuccessActionType = FeatureMyFeaturesActions.loadAllByPost.type; |
| 22 | + |
| 23 | + /** |
| 24 | + * Type of store action called after create effect is successful. |
| 25 | + * See create effect in store effects of CRUD item. |
| 26 | + */ |
| 27 | + _createSuccessActionType = FeatureMyFeaturesActions.loadAllByPost.type; |
| 28 | + |
| 29 | + /** |
| 30 | + * Type of store action called after update effect has failed. |
| 31 | + * See update effect in store effects of CRUD item. |
| 32 | + */ |
| 33 | + _updateFailureActionType = FeatureEnginesActions.failure.type; |
| 34 | +} |
| 35 | +``` |
| 36 | + |
| 37 | +## Inform user of conflict |
| 38 | +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. |
| 39 | +### Back |
| 40 | +**NOTE :** these configuration instructions are already included into features generated from the version **4.1.0** of BIA Framework. |
| 41 | +1. Your entity must derived of `VersionedTable` type |
| 42 | +2. Your mapper should include in the `EntityToDto()` method a mapping for the `RowVersion` property of the DTO based on the same property from the entity : |
| 43 | +``` csharp title="MyEntityMapper.cs" |
| 44 | +public override Expression<Func<MyEntity, MyEntityDto>> EntityToDto() |
| 45 | +{ |
| 46 | + return entity => new MyEntityDto |
| 47 | + { |
| 48 | + // [...] |
| 49 | +
|
| 50 | + // Add the RowVersion mapping |
| 51 | + RowVersion = Convert.ToBase64String(entity.RowVersion), |
| 52 | + } |
| 53 | +} |
| 54 | +``` |
| 55 | +3. Add in your `Update()` endpoint of your entity controller the catch of `OutDatedException` and the handle of `409` status code : |
| 56 | +``` csharp title="MyEntityController.cs" |
| 57 | +// Add this attribute |
| 58 | +[ProducesResponseType(StatusCodes.Status409Conflict)] |
| 59 | +public async Task<IActionResult> Update(int id, [FromBody] MyEntityDto dto) |
| 60 | +{ |
| 61 | + try |
| 62 | + { |
| 63 | + // [...] |
| 64 | + } |
| 65 | + //Add this catch block |
| 66 | + catch (OutDatedException) |
| 67 | + { |
| 68 | + return this.Conflict(); |
| 69 | + } |
| 70 | +} |
| 71 | +``` |
| 72 | +### Front |
| 73 | +1. Add in your feature model the assign of the `BiaFieldConfig` for the `rowVersion` : |
| 74 | +``` typescript title="my-feature.ts" |
| 75 | +export const myFeatureFieldsConfiguration : BiaFieldsConfig<MyFeature> = { |
| 76 | + columns: [ |
| 77 | + // [...] |
| 78 | + Object.assign(new BiaFieldConfig('rowVersion', 'myFeature.rowVersion'), { |
| 79 | + isVisible: false, |
| 80 | + isHideByDefault: true, |
| 81 | + }), |
| 82 | + ] |
| 83 | +} |
| 84 | +``` |
| 85 | +2. In your feature edit component HTML template, add the binding for the `isCrudItemOutdated` property between the feature edit component and the feature form component : |
| 86 | +``` html title="my-feature-edit.component.html" |
| 87 | +<app-my-feature-form |
| 88 | + [isCrudItemOutdated]="isCrudItemOutdated"></app-my-feature-form> |
| 89 | +``` |
| 90 | + |
| 91 | +Warning displayed into the edit popup : |
| 92 | + |
| 93 | + |
| 94 | +Warning displayed as toast on calc mode : |
| 95 | + |
| 96 | + |
| 97 | +### SignalR |
| 98 | +You can inform your user with SignalR that the data on the opened form has been updated by another user. |
| 99 | +This feature works only with the **edit component** on **popup mode**. |
| 100 | +#### Back |
| 101 | +1. On your feature controller, enable the `UseHubForClientInAirport` constant definition and add a sending instruction to the client with the dedicated identifier and hub : |
| 102 | +``` csharp title="MyFeatureController" |
| 103 | +#define UseHubForClientInAirport |
| 104 | + |
| 105 | +namespace MyCompany.MyProject.Presentation.Api.Controllers.MyFeature |
| 106 | +{ |
| 107 | + // [...] |
| 108 | + public class MyFeatureController : BiaControllerBase |
| 109 | + { |
| 110 | + // [...] |
| 111 | + public async Task<IActionResult> Update(int id, [FromBody] MyEntityDto dto) |
| 112 | + { |
| 113 | + try |
| 114 | + { |
| 115 | + // [...] |
| 116 | + var updatedDto = await this.airportService.UpdateAsync(dto); |
| 117 | + |
| 118 | + // Add the signalR notification |
| 119 | +#if UseHubForClientInAirport |
| 120 | + await this.clientForHubService.SendTargetedMessage(string.Empty, "myfeatures", "update-myfeature", updatedDto); |
| 121 | +#endif |
| 122 | + |
| 123 | + return this.Ok(updatedDto); |
| 124 | + } |
| 125 | + catch |
| 126 | + { |
| 127 | + // [...] |
| 128 | + } |
| 129 | + } |
| 130 | + } |
| 131 | +} |
| 132 | +``` |
| 133 | + |
| 134 | +#### Front |
| 135 | +1. Edit your constant feature file to use signalR : |
| 136 | +``` typescript title="my-feature.constants.ts" |
| 137 | +export const airportCRUDConfiguration: CrudConfig<Airport> = new CrudConfig({ |
| 138 | + // [...] |
| 139 | + useSignalR: true, |
| 140 | +}); |
| 141 | +``` |
| 142 | +2. Create or complete your feature signalR service with following methods `registerUpdate()` and `unregisterUpdate()` : |
| 143 | +``` typescript title="my-feature-signalr.service.ts" |
| 144 | +import { Injectable } from '@angular/core'; |
| 145 | +import { BiaSignalRService } from 'src/app/core/bia-core/services/bia-signalr.service'; |
| 146 | +import { TargetedFeature } from 'src/app/shared/bia-shared/model/signalR'; |
| 147 | + |
| 148 | +@Injectable({ |
| 149 | + providedIn: 'root', |
| 150 | +}) |
| 151 | +export class MyFeatureSignalRService { |
| 152 | + private targetedFeature: TargetedFeature; |
| 153 | + |
| 154 | + constructor(private signalRService: BiaSignalRService) {} |
| 155 | + |
| 156 | + initialize() { |
| 157 | + this.targetedFeature = { |
| 158 | + parentKey: '', |
| 159 | + featureName: 'myFeatures', |
| 160 | + }; |
| 161 | + this.signalRService.joinGroup(this.targetedFeature); |
| 162 | + } |
| 163 | + |
| 164 | + registerUpdate(callback: (args: any) => void) { |
| 165 | + console.log( |
| 166 | + '%c [MyFeatures] Register SignalR : update-myFeature', |
| 167 | + 'color: purple; font-weight: bold' |
| 168 | + ); |
| 169 | + this.signalRService.addMethod('update-myFeature', args => { |
| 170 | + callback(args); |
| 171 | + }); |
| 172 | + } |
| 173 | + |
| 174 | + destroy() { |
| 175 | + this.unregisterUpdate(); |
| 176 | + this.signalRService.leaveGroup(this.targetedFeature); |
| 177 | + } |
| 178 | + |
| 179 | + private unregisterUpdate() { |
| 180 | + console.log( |
| 181 | + '%c [MyFeatures] Unregister SignalR : update-myFeature', |
| 182 | + 'color: purple; font-weight: bold' |
| 183 | + ); |
| 184 | + this.signalRService.removeMethod('update-myFeature'); |
| 185 | + } |
| 186 | +} |
| 187 | +``` |
| 188 | +3. Complete your feature edit component in order to react to signalR notifications : |
| 189 | +``` typescript title="my-feature-edit.component.ts" |
| 190 | +import { Component, Injector, OnDestroy, OnInit } from '@angular/core'; |
| 191 | +import { firstValueFrom } from 'rxjs'; |
| 192 | +import { CrudItemEditComponent } from 'src/app/shared/bia-shared/feature-templates/crud-items/views/crud-item-edit/crud-item-edit.component'; |
| 193 | +import { myFeatureCRUDConfiguration } from '../../my-feature.constants'; |
| 194 | +import { MyFeature } from '../../model/my-feature'; |
| 195 | +import { MyFeatureSignalRService } from '../../services/my-feature-signalr.service'; |
| 196 | +import { MyFeatureService } from '../../services/my-feature.service'; |
| 197 | + |
| 198 | +@Component({ |
| 199 | + selector: 'app-my-feature-edit', |
| 200 | + templateUrl: './my-feature-edit.component.html', |
| 201 | +}) |
| 202 | +export class MyFeatureEditComponent |
| 203 | + extends CrudItemEditComponent<MyFeature> |
| 204 | + implements OnInit, OnDestroy |
| 205 | +{ |
| 206 | + private useSignalR = myFeatureCRUDConfiguration.useSignalR; |
| 207 | + |
| 208 | + constructor( |
| 209 | + protected injector: Injector, |
| 210 | + public myFeatureService: MyFeatureService, |
| 211 | + protected signalrService: MyFeatureSignalRService |
| 212 | + ) { |
| 213 | + super(injector, myFeatureService); |
| 214 | + this.crudConfiguration = myFeatureCRUDConfiguration; |
| 215 | + } |
| 216 | + |
| 217 | + ngOnInit() { |
| 218 | + super.ngOnInit(); |
| 219 | + |
| 220 | + if (this.useSignalR) { |
| 221 | + this.signalrService.initialize(); |
| 222 | + |
| 223 | + this.signalrService.registerUpdate(async args => { |
| 224 | + const updatedCrudItem = JSON.parse(args) as MyFeature; |
| 225 | + const currentCrudItem = await firstValueFrom( |
| 226 | + this.myFeatureService.crudItem$ |
| 227 | + ); |
| 228 | + |
| 229 | + if ( |
| 230 | + currentCrudItem.id === updatedCrudItem.id && |
| 231 | + currentCrudItem.rowVersion !== updatedCrudItem.rowVersion |
| 232 | + ) { |
| 233 | + this.isCrudItemOutdated = true; |
| 234 | + } |
| 235 | + }); |
| 236 | + } |
| 237 | + } |
| 238 | + |
| 239 | + ngOnDestroy() { |
| 240 | + super.ngOnDestroy(); |
| 241 | + |
| 242 | + if (this.useSignalR) { |
| 243 | + this.signalrService.destroy(); |
| 244 | + } |
| 245 | + } |
| 246 | +} |
| 247 | +``` |
| 248 | + |
| 249 | + |
0 commit comments