Skip to content

Commit f1cdfeb

Browse files
committed
feat(core): Support changing the bound standalone parameter
We now allow the parameter bound using [queryParam] to be changed at runtime by using our get/remove APIs. Signed-off-by: Ingo Bürk <ingo.buerk@tngtech.com>
1 parent 6420b9e commit f1cdfeb

2 files changed

Lines changed: 101 additions & 8 deletions

File tree

projects/ngqp/core/src/lib/directives/query-param.directive.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,24 +31,29 @@ export class QueryParamDirective implements QueryParamAccessor, OnChanges, OnDes
3131
/** @internal */
3232
public valueAccessor: ControlValueAccessor | null = null;
3333

34+
/** @internal */
35+
private group = new QueryParamGroup({});
36+
3437
constructor(
3538
@Optional() private groupService: QueryParamGroupService,
3639
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[],
3740
) {
3841
this.valueAccessor = selectValueAccessor(valueAccessors);
42+
this.groupService.setQueryParamGroup(this.group);
3943
}
4044

4145
/** @ignore */
4246
public ngOnChanges(changes: SimpleChanges): void {
4347
const paramChange = changes['queryParam'];
4448

4549
if (paramChange) {
46-
if (!paramChange.firstChange) {
47-
throw new Error('Changing the QueryParam bound in standalone mode is currently not supported.');
50+
if (this.group.get(this.name)) {
51+
this.groupService.deregisterQueryParamDirective(this.name);
52+
this.group.remove(this.name);
4853
}
4954

5055
if (paramChange.currentValue) {
51-
this.groupService.setQueryParamGroup(new QueryParamGroup({[this.name]: paramChange.currentValue}));
56+
this.group.add(this.name, paramChange.currentValue);
5257
this.groupService.registerQueryParamDirective(this);
5358
}
5459
}

projects/ngqp/core/src/test/standalone-input-text.spec.ts renamed to projects/ngqp/core/src/test/standalone.spec.ts

Lines changed: 93 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { captureObservable, scheduler, setupNavigationWarnStub } from './util';
88
@Component({
99
template: `<input type="text" [queryParam]="param" />`,
1010
})
11-
class BasicTestComponent {
11+
class StaticStandaloneExampleComponent {
1212

1313
public param: QueryParam<string>;
1414

@@ -18,9 +18,23 @@ class BasicTestComponent {
1818

1919
}
2020

21+
@Component({
22+
template: `<input type="text" [queryParam]="bound ? param : null" />`,
23+
})
24+
class ToggleStandaloneExampleComponent {
25+
26+
public param: QueryParam<string>;
27+
public bound = false;
28+
29+
constructor(private qpb: QueryParamBuilder) {
30+
this.param = qpb.stringParam('q');
31+
}
32+
33+
}
34+
2135
describe('ngqp standalone parameters', () => {
22-
let fixture: ComponentFixture<BasicTestComponent>;
23-
let component: BasicTestComponent;
36+
let fixture: ComponentFixture<StaticStandaloneExampleComponent>;
37+
let component: StaticStandaloneExampleComponent;
2438
let input: HTMLInputElement;
2539
let router: Router;
2640

@@ -33,7 +47,7 @@ describe('ngqp standalone parameters', () => {
3347
QueryParamModule,
3448
],
3549
declarations: [
36-
BasicTestComponent,
50+
StaticStandaloneExampleComponent,
3751
],
3852
});
3953

@@ -43,7 +57,7 @@ describe('ngqp standalone parameters', () => {
4357
}));
4458

4559
beforeEach(() => {
46-
fixture = TestBed.createComponent(BasicTestComponent);
60+
fixture = TestBed.createComponent(StaticStandaloneExampleComponent);
4761
component = fixture.componentInstance;
4862
fixture.detectChanges();
4963
input = (fixture.nativeElement as HTMLElement).querySelector('input') as HTMLInputElement;
@@ -115,4 +129,78 @@ describe('ngqp standalone parameters', () => {
115129
expectObservable(value$).toBe('');
116130
});
117131
}));
132+
});
133+
134+
describe('Standalone parameters', () => {
135+
let fixture: ComponentFixture<ToggleStandaloneExampleComponent>;
136+
let component: ToggleStandaloneExampleComponent;
137+
let input: HTMLInputElement;
138+
let router: Router;
139+
140+
beforeEach(() => setupNavigationWarnStub());
141+
142+
beforeEach(async(() => {
143+
TestBed.configureTestingModule({
144+
imports: [
145+
RouterTestingModule.withRoutes([]),
146+
QueryParamModule,
147+
],
148+
declarations: [
149+
ToggleStandaloneExampleComponent,
150+
],
151+
});
152+
153+
router = TestBed.get(Router);
154+
TestBed.compileComponents();
155+
router.initialNavigation();
156+
}));
157+
158+
beforeEach(() => {
159+
fixture = TestBed.createComponent(ToggleStandaloneExampleComponent);
160+
component = fixture.componentInstance;
161+
fixture.detectChanges();
162+
input = (fixture.nativeElement as HTMLElement).querySelector('input') as HTMLInputElement;
163+
fixture.detectChanges();
164+
});
165+
166+
it('can be bound at runtime', fakeAsync(() => {
167+
component.bound = true;
168+
fixture.detectChanges();
169+
170+
input.value = 'Test';
171+
input.dispatchEvent(new Event('input'));
172+
tick();
173+
174+
expect(router.url).toBe(`/?q=Test`);
175+
}));
176+
177+
it('can be unbound at runtime', fakeAsync(() => {
178+
component.bound = true;
179+
fixture.detectChanges();
180+
181+
router.navigateByUrl(`/?q=One`);
182+
tick();
183+
expect(input.value).toBe('One');
184+
185+
component.bound = false;
186+
fixture.detectChanges();
187+
188+
router.navigateByUrl(`/?q=Two`);
189+
tick();
190+
expect(input.value).toBe('One');
191+
}));
192+
193+
it('synchronizes with the URL upon being bound', fakeAsync(() => {
194+
scheduler.run(({ expectObservable }) => {
195+
router.navigateByUrl(`/?q=One`);
196+
tick();
197+
198+
const paramValueChanges$ = captureObservable(component.param.valueChanges);
199+
200+
component.bound = true;
201+
fixture.detectChanges();
202+
203+
expectObservable(paramValueChanges$).toBe('a', {a: 'One'});
204+
});
205+
}));
118206
});

0 commit comments

Comments
 (0)