Last active
January 30, 2019 08:15
-
-
Save Juraji/b36376b64780f336a48c8f2da9f6c92e to your computer and use it in GitHub Desktop.
Angular: Bind component property to a query parameter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { Component, OnInit } from "@angular/core"; | |
| @Component({ | |
| selector: "app-query-parameter-binding-example", | |
| template: "<p>Property 1: {{property1}}</p><p>Property 2: {{property2}}</p>" | |
| }) | |
| export class QueryParameterBindingExampleComponent implements OnInit { | |
| /** | |
| * When updated is reflected as query parameter "property-1=[value]" in url | |
| * Getter will return query parameter value | |
| * Setter will update query parameter value | |
| */ | |
| @QueryParameterBinding("property-1") | |
| public get property1(): string { | |
| // Property getter | |
| } | |
| public set property1(value: string) { | |
| // Property setter | |
| } | |
| /** | |
| * When updated is reflected as query parameter "property-2=[value]" in url | |
| */ | |
| @QueryParameterBinding("property-2") | |
| public property2: string; | |
| public ngOnInit() { | |
| // Set an interval to update the bound values, | |
| // demonstrating setting values and reflecting in query parameters | |
| let counter = 0; | |
| setInterval(() => { | |
| counter++; | |
| this.property1 = counter; | |
| this.property2 = counter; | |
| }, 1000); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { QueryParameterBinding } from "./query-parameter-binding"; | |
| class TestComponent { | |
| private _getSetProperty; | |
| public get getSetProperty() { | |
| return this._getSetProperty; | |
| } | |
| public set getSetProperty(value) { | |
| this._getSetProperty = value; | |
| } | |
| public primitiveProperty: string; | |
| } | |
| describe("QueryParameterBinding", () => { | |
| const decoratorFn = QueryParameterBinding("test"); | |
| it("should support get/set and primitive properties", () => { | |
| const cmpObject = new TestComponent(); | |
| const getSetPropertyDescriptor = Object.getOwnPropertyDescriptor(cmpObject, "getSetProperty"); | |
| // Calling factory with only an object and property name (Primitive properties) | |
| // should not throw a (compiler) error | |
| decoratorFn(cmpObject, "primitiveProperty"); | |
| // Calling factory with property descriptor should not throw a (compiler) error | |
| decoratorFn(cmpObject, "getSetProperty", getSetPropertyDescriptor); | |
| }); | |
| it("should update the property to the current query", () => { | |
| // Todo: Write tests | |
| }); | |
| it("should reflect property value changes to the query parameter", () => { | |
| // Todo: Write tests | |
| }); | |
| it("should support two-way binding (Using the *Change property if it exists)", () => { | |
| // Todo: Write tests | |
| }); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { EventEmitter } from "@angular/core"; | |
| /** | |
| * Bind a property or method to an url query parameter. | |
| * | |
| * Possible compiler warnings: | |
| * - TS2345: Decorator is being used on a non-string type property | |
| * This error is not thrown on primitive properties, beware unexpected behaviour! | |
| * - TS1238, TS2559 or TS2345: Decorator does not support usage on classes, constructor parameters or methods | |
| * | |
| * @param name The name for the query parameter | |
| * @decorator | |
| * @author Juraji 2019 (https://github.com/Juraji) | |
| */ | |
| export function QueryParameterBinding(name: string) { | |
| const getQMValue = (): string => { | |
| const urlParams = new URLSearchParams(window.location.search); | |
| if (urlParams.has(name)) { | |
| return urlParams.get(name); | |
| } else { | |
| return null; | |
| } | |
| }; | |
| const setQMValue = (v: string) => { | |
| const urlParams = new URLSearchParams(window.location.search); | |
| if (v == null) { | |
| if (urlParams.has(name)) { | |
| urlParams.delete(name); | |
| } | |
| } else { | |
| urlParams.set(name, v); | |
| } | |
| const newPath = window.location.pathname + "?" + urlParams.toString(); | |
| window.history.replaceState(null, "", newPath); | |
| }; | |
| return (target: any, key: string, descriptor?: TypedPropertyDescriptor<string>): any => { | |
| // if query parameter is present update descriptor to reflect value | |
| if (descriptor && descriptor.set) { | |
| const qmValue = getQMValue(); | |
| if (qmValue != null) { | |
| descriptor.set.apply(target, [qmValue]); | |
| } | |
| } | |
| const proxyDescriptor: TypedPropertyDescriptor<string> = { | |
| enumerable: true, | |
| configurable: true, | |
| get: function () { | |
| let q = getQMValue(); | |
| if (descriptor && descriptor.get) { | |
| // If there was a descriptor sync the parameter | |
| // and its value is different from the query parameter | |
| // update the query parameter | |
| const p = descriptor.get.apply(this); | |
| if (q !== p) { | |
| setQMValue(p); | |
| q = p; | |
| } | |
| } | |
| return q; | |
| }, | |
| set: function (v) { | |
| setQMValue(v); | |
| if (descriptor && descriptor.set) { | |
| // If there was a descriptor set its value as well | |
| descriptor.set.apply(this, [v]); | |
| } | |
| } | |
| }; | |
| // Two-way binding support: | |
| // if change property exists, subscribe to it, updating the query parameter on change | |
| const changePropKey = `${key}Change`; | |
| if (target.hasOwnProperty(changePropKey) && target[changePropKey] instanceof EventEmitter) { | |
| (target[changePropKey] as EventEmitter<string>) | |
| .subscribe(v => setQMValue(v)); | |
| } | |
| return proxyDescriptor; | |
| }; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Todo: Write tests/figure out how to mock usage of window.location