mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +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');
|
}>('select-customer');
|
||||||
|
|
||||||
if (context?.autoTriggerContinueFn) {
|
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
|
// Auto-trigger continue() ONLY when coming from Kundenkarte
|
||||||
this.continue();
|
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?)`
|
#### `restoreContext<T>(customScope?)`
|
||||||
|
|
||||||
Retrieve preserved context **without** removing it.
|
Retrieve preserved context **without** removing it.
|
||||||
@@ -710,6 +772,7 @@ export const NAVIGATION_CONTEXT_METADATA_KEY = 'navigation-contexts';
|
|||||||
| Method | Parameters | Returns | Purpose |
|
| Method | Parameters | Returns | Purpose |
|
||||||
|--------|-----------|---------|---------|
|
|--------|-----------|---------|---------|
|
||||||
| `preserveContext(state, customScope?)` | state: T, customScope?: string | void | Save context |
|
| `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) |
|
| `restoreContext(customScope?)` | customScope?: string | T \| null | Get context (keep) |
|
||||||
| `restoreAndClearContext(customScope?)` | customScope?: string | T \| null | Get + remove |
|
| `restoreAndClearContext(customScope?)` | customScope?: string | T \| null | Get + remove |
|
||||||
| `clearPreservedContext(customScope?)` | customScope?: string | boolean | Remove context |
|
| `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.
|
* 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);
|
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.
|
* Restore preserved navigation state.
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user