Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip: feat(linked-query-param): add initial implementation for linked query… #526

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

eneajaho
Copy link
Collaborator

… param

some tests are missing

  • arrays
  • objects
  • booleans

docs are missing

  • gemini should fix that one

correct types

  • didn't spend much time there, but should be fixed before merged

).toBe(undefined);
}));

//
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: also test what happens when we update a query param and some other linkedQueryParam is listening to the same thing, test the timing of that and try to fix it.

@eneajaho
Copy link
Collaborator Author

Should I just have two effects instead of the hack?

/**
 * Creates a signal that is linked to a query parameter.
 *
 * @param paramKey The name of the query parameter.
 * @param config Configuration options for the signal.
 * @returns A signal that is linked to the query parameter.
 */
export function linkedQueryParam<T = string | null>(
	paramKey: string,
	config?: {
		/**
		 * Function to parse the query param value before it is passed to the signal.
		 * This is useful if the query param is a string and you want to parse it to a number or boolean.
		 */
		parse?: (x: string | null) => T;

		/**
		 * Function to transform the value you pass to the set function.
		 * This is useful if you want to transform the value before it is passed to the query param.
		 */
		transform?: (x: T | null) => string | null;

		/**
		 * The default value to use if the query param is not present.
		 */
		defaultValue?: T;

		/**
		 * The injector to use to inject the router and activated route.
		 */
		injector?: Injector;
	} & Pick<
		NavigationExtras,
		| 'queryParamsHandling'
		| 'onSameUrlNavigation'
		| 'replaceUrl'
		| 'skipLocationChange'
	>,
): WritableSignal<T> {
	const injector = assertInjector(linkedQueryParam, config?.injector);

	const parseFn = config?.parse ?? ((x: string | null) => x as unknown as T);
	const transformFn =
		config?.transform ??
		((x: T | null | undefined) =>
			x as unknown as string | number | boolean | null);

	// @ts-ignore
	return runInInjectionContext(injector, () => {
		const route = inject(ActivatedRoute);
		const coalescedNavigation = inject(CoalescedNavigation);

		const {
			queryParamsHandling,
			onSameUrlNavigation,
			replaceUrl,
			skipLocationChange,
		} = config ?? {};
		coalescedNavigation._navigationExtras = {
			queryParamsHandling,
			onSameUrlNavigation,
			replaceUrl,
			skipLocationChange,
		};

		// get the initial value to use below
		const initialValue =
			parseFn(route.snapshot.queryParams[paramKey]) || config?.defaultValue || undefined;

		// create a signal that is updated whenever the query param changes
		const queryParamValue = toSignal(
			route.queryParams.pipe(
				map((x) => parseFn(x[paramKey]) || config?.defaultValue),
			),
			{ initialValue },
		);


    // TODO: convert this to a linked signal to remove the effect
    // that synchronizes the source signal with the query param value
    // the source signal is what we return to the caller
		const source = signal<T | undefined>(queryParamValue());

		effect(() => {
			const x = queryParamValue();
			// update the source signal whenever the query param changes
			untracked(() => source.set(x));
		});

    effect(() => {
      const sourceValue = source();

      untracked(() => {
        // when the source signal changes, update the query param
        // store the new value in the current keys so that we can coalesce the navigation
        coalescedNavigation._currentKeys[paramKey] = transformFn(sourceValue!);
        // schedule the navigation event
        coalescedNavigation.scheduleNavigation();
      });
    });

		return source;
	});
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant