mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Merged PR 2042: fix(navigation): prevent autoTriggerContinueFn from persisting across navigat...
fix(navigation): prevent autoTriggerContinueFn from persisting across navigations The autoTriggerContinueFn flag was remaining in navigation context after being read, causing incorrect auto-triggering on subsequent page visits. Changes: - Add patchContext() method to NavigationContextService and NavigationStateService for partial context updates without full replacement - Update details-main-view.component to use patchContext() to clear the flag immediately after reading, while preserving returnUrl - Add comprehensive JSDoc and README documentation for patchContext() - Include structured logging for patch operations The new patchContext() method provides a cleaner API for updating specific context properties without manually preserving all other properties. Closes #5500 Related work items: #5500
This commit is contained in:
committed by
Nino Righi
parent
7a6a2dc49d
commit
bf87df6273
@@ -329,6 +329,12 @@ export class CustomerDetailsViewMainComponent
|
||||
}>('select-customer');
|
||||
|
||||
if (context?.autoTriggerContinueFn) {
|
||||
// Clear the autoTriggerContinueFn flag immediately (preserves returnUrl automatically)
|
||||
await this._navigationState.patchContext(
|
||||
{ autoTriggerContinueFn: undefined },
|
||||
'select-customer',
|
||||
);
|
||||
|
||||
// Auto-trigger continue() ONLY when coming from Kundenkarte
|
||||
this.continue();
|
||||
}
|
||||
|
||||
@@ -146,6 +146,68 @@ navState.preserveContext(
|
||||
|
||||
---
|
||||
|
||||
#### `patchContext<T>(partialState, customScope?)`
|
||||
|
||||
Partially update preserved context without replacing the entire context.
|
||||
|
||||
This method merges partial state with the existing context, preserving properties you don't specify. Properties explicitly set to `undefined` will be removed from the context.
|
||||
|
||||
```typescript
|
||||
// Existing context: { returnUrl: '/cart', autoTriggerContinueFn: true, customerId: 123 }
|
||||
|
||||
// Clear one property while preserving others
|
||||
await navState.patchContext(
|
||||
{ autoTriggerContinueFn: undefined },
|
||||
'select-customer'
|
||||
);
|
||||
// Result: { returnUrl: '/cart', customerId: 123 }
|
||||
|
||||
// Update one property while preserving others
|
||||
await navState.patchContext(
|
||||
{ customerId: 456 },
|
||||
'select-customer'
|
||||
);
|
||||
// Result: { returnUrl: '/cart', customerId: 456 }
|
||||
|
||||
// Add new property to existing context
|
||||
await navState.patchContext(
|
||||
{ selectedTab: 'details' },
|
||||
'select-customer'
|
||||
);
|
||||
// Result: { returnUrl: '/cart', customerId: 456, selectedTab: 'details' }
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `partialState`: Partial state object to merge (set properties to `undefined` to remove them)
|
||||
- `customScope` (optional): Custom scope within the tab
|
||||
|
||||
**Use Cases:**
|
||||
- Clear trigger flags while preserving flow data
|
||||
- Update specific properties without affecting others
|
||||
- Remove properties from context
|
||||
- Add properties to existing context
|
||||
|
||||
**Comparison with `preserveContext`:**
|
||||
- `preserveContext`: Replaces entire context (overwrites all properties)
|
||||
- `patchContext`: Merges with existing context (updates only specified properties)
|
||||
|
||||
```typescript
|
||||
// ❌ preserveContext - must manually preserve existing properties
|
||||
const context = await navState.restoreContext();
|
||||
await navState.preserveContext({
|
||||
returnUrl: context?.returnUrl, // Must specify
|
||||
customerId: context?.customerId, // Must specify
|
||||
autoTriggerContinueFn: undefined, // Clear this
|
||||
});
|
||||
|
||||
// ✅ patchContext - automatically preserves unspecified properties
|
||||
await navState.patchContext({
|
||||
autoTriggerContinueFn: undefined, // Only specify what changes
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `restoreContext<T>(customScope?)`
|
||||
|
||||
Retrieve preserved context **without** removing it.
|
||||
@@ -710,6 +772,7 @@ export const NAVIGATION_CONTEXT_METADATA_KEY = 'navigation-contexts';
|
||||
| Method | Parameters | Returns | Purpose |
|
||||
|--------|-----------|---------|---------|
|
||||
| `preserveContext(state, customScope?)` | state: T, customScope?: string | void | Save context |
|
||||
| `patchContext(partialState, customScope?)` | partialState: Partial<T>, customScope?: string | void | Merge partial updates |
|
||||
| `restoreContext(customScope?)` | customScope?: string | T \| null | Get context (keep) |
|
||||
| `restoreAndClearContext(customScope?)` | customScope?: string | T \| null | Get + remove |
|
||||
| `clearPreservedContext(customScope?)` | customScope?: string | boolean | Remove context |
|
||||
|
||||
@@ -143,6 +143,75 @@ export class NavigationContextService {
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch a context in the active tab's metadata.
|
||||
*
|
||||
* Merges partial data with the existing context, preserving unspecified properties.
|
||||
* Properties explicitly set to `undefined` will be removed from the context.
|
||||
* If the context doesn't exist, creates a new one (behaves like setContext).
|
||||
*
|
||||
* @template T The type of data being patched
|
||||
* @param partialData The partial navigation data to merge
|
||||
* @param customScope Optional custom scope (defaults to 'default')
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Clear one property while preserving others
|
||||
* contextService.patchContext({ autoTriggerContinueFn: undefined }, 'select-customer');
|
||||
*
|
||||
* // Update one property while preserving others
|
||||
* contextService.patchContext({ selectedTab: 'details' }, 'customer-flow');
|
||||
* ```
|
||||
*/
|
||||
async patchContext<T extends NavigationContextData>(
|
||||
partialData: Partial<T>,
|
||||
customScope?: string,
|
||||
): Promise<void> {
|
||||
const tabId = this.#tabService.activatedTabId();
|
||||
if (tabId === null) {
|
||||
throw new Error('No active tab - cannot patch navigation context');
|
||||
}
|
||||
|
||||
const scopeKey = customScope || 'default';
|
||||
const existingContext = await this.getContext<T>(customScope);
|
||||
|
||||
const mergedData = {
|
||||
...(existingContext ?? {}),
|
||||
...partialData,
|
||||
};
|
||||
|
||||
// Remove properties explicitly set to undefined
|
||||
const removedKeys: string[] = [];
|
||||
Object.keys(mergedData).forEach((key) => {
|
||||
if (mergedData[key] === undefined) {
|
||||
removedKeys.push(key);
|
||||
delete mergedData[key];
|
||||
}
|
||||
});
|
||||
|
||||
const contextsMap = this.#getContextsMap(tabId);
|
||||
const context: NavigationContext = {
|
||||
data: mergedData,
|
||||
createdAt:
|
||||
existingContext && contextsMap[scopeKey]
|
||||
? contextsMap[scopeKey].createdAt
|
||||
: Date.now(),
|
||||
};
|
||||
|
||||
contextsMap[scopeKey] = context;
|
||||
this.#saveContextsMap(tabId, contextsMap);
|
||||
|
||||
this.#log.debug('Context patched in tab metadata', () => ({
|
||||
tabId,
|
||||
scopeKey,
|
||||
patchedKeys: Object.keys(partialData),
|
||||
removedKeys,
|
||||
totalDataKeys: Object.keys(mergedData),
|
||||
wasUpdate: existingContext !== null,
|
||||
totalContexts: Object.keys(contextsMap).length,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a context from the active tab's metadata without removing it.
|
||||
*
|
||||
|
||||
@@ -112,6 +112,50 @@ export class NavigationStateService {
|
||||
await this.#contextService.setContext(state, customScope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch preserved navigation state.
|
||||
*
|
||||
* Merges partial state with existing preserved context, keeping unspecified properties intact.
|
||||
* This is useful when you need to update or clear specific properties without replacing
|
||||
* the entire context. Properties set to `undefined` will be removed.
|
||||
*
|
||||
* Use cases:
|
||||
* - Clear a trigger flag while preserving return URL
|
||||
* - Update one property in a multi-property context
|
||||
* - Remove specific properties from context
|
||||
*
|
||||
* @template T The type of state data being patched
|
||||
* @param partialState The partial state to merge (properties set to undefined will be removed)
|
||||
* @param customScope Optional custom scope within the tab
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Clear the autoTriggerContinueFn flag while preserving returnUrl
|
||||
* await navigationStateService.patchContext(
|
||||
* { autoTriggerContinueFn: undefined },
|
||||
* 'select-customer'
|
||||
* );
|
||||
*
|
||||
* // Update selectedTab while keeping other properties
|
||||
* await navigationStateService.patchContext(
|
||||
* { selectedTab: 'rewards' },
|
||||
* 'customer-flow'
|
||||
* );
|
||||
*
|
||||
* // Add a new property to existing context
|
||||
* await navigationStateService.patchContext(
|
||||
* { shippingAddressId: 123 },
|
||||
* 'checkout-flow'
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
async patchContext<T extends NavigationContextData>(
|
||||
partialState: Partial<T>,
|
||||
customScope?: string,
|
||||
): Promise<void> {
|
||||
await this.#contextService.patchContext(partialState, customScope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore preserved navigation state.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user