Skip to content

Commit

Permalink
feat(ng-app-state): nasModel accepts null for the store, for comp…
Browse files Browse the repository at this point in the history
…atibility with an async pipe

Closes #60
  • Loading branch information
ersimont committed Nov 6, 2021
1 parent 45599aa commit f5f8f7a
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 20 deletions.
3 changes: 2 additions & 1 deletion angular.json
Expand Up @@ -335,7 +335,8 @@
"options": {
"main": "projects/ng-app-state/src/test.ts",
"tsConfig": "projects/ng-app-state/tsconfig.spec.json",
"karmaConfig": "projects/ng-app-state/karma.conf.js"
"karmaConfig": "projects/ng-app-state/karma.conf.js",
"polyfills": "projects/ng-app-state/src/test-polyfills.ts"
}
},
"lint": {
Expand Down
27 changes: 25 additions & 2 deletions projects/ng-app-state/src/lib/nas-model.directive.spec.ts
@@ -1,4 +1,4 @@
import { Type } from '@angular/core';
import { Component, Type } from '@angular/core';
import {
ComponentFixture,
fakeAsync,
Expand All @@ -9,7 +9,11 @@ import {
} from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { Store } from '@s-libs/app-state';
import { noop } from '@s-libs/micro-dash';
import { ComponentContext } from '@s-libs/ng-dev';
import { Subject } from 'rxjs';
import { setValue } from '../../../ng-core/src/test-helpers';
import {
CityComponent,
citySelectWithCustomCompareFnTemplate,
Expand Down Expand Up @@ -637,7 +641,7 @@ describe('value accessors', () => {
});

// `nasModel` is tested pretty thoroughly above, by the tests adapted from angular's suite. Here we hit a few more cases to complete code coverage.
describe('nasModel', () => {
describe('NasModelDirective', () => {
it('can bind to different store objects over time', fakeAsync(() => {
const store = initTest(MenuComponent, MenuStore, {
template: `<input [nasModel]="textStore">`,
Expand Down Expand Up @@ -680,4 +684,23 @@ describe('nasModel', () => {
flushMicrotasks();
expect(input.disabled).toBe(false);
}));

it('handles `null` from an async pipe', () => {
@Component({ template: `<input [nasModel]="store$ | async" />` })
class StoreStreamComponent {
store$ = new Subject<Store<string>>();
}

const ctx = new ComponentContext(StoreStreamComponent, {
imports: [NasModelModule],
});
expect(() => {
ctx.run(() => {
const input: HTMLInputElement = ctx.fixture.debugElement.query(
By.css('input'),
).nativeElement;
setValue(input, 'updated value');
});
}).not.toThrowError();
});
});
28 changes: 13 additions & 15 deletions projects/ng-app-state/src/lib/nas-model.directive.ts
Expand Up @@ -12,42 +12,40 @@ import { Subscription } from 'rxjs';

@Directive({ selector: '[nasModel]' })
export class NasModelDirective<T> implements AfterViewInit, OnDestroy {
private store!: Store<T>;
private subscription!: Subscription;
private valueAccessor: ControlValueAccessor;
#store!: Store<T> | null;
#subscription?: Subscription;
#valueAccessor: ControlValueAccessor;

constructor(
@Self()
@Inject(NG_VALUE_ACCESSOR)
valueAccessors: ControlValueAccessor[],
) {
this.valueAccessor = valueAccessors[0];
this.#valueAccessor = valueAccessors[0];
}

@Input()
set nasModel(store: Store<T>) {
if (this.subscription) {
this.subscription.unsubscribe();
}
set nasModel(store: Store<T> | null) {
this.#subscription?.unsubscribe();

this.store = store;
this.subscription = store.$.subscribe((value) => {
this.valueAccessor.writeValue(value);
this.#store = store;
this.#subscription = store?.$.subscribe((value) => {
this.#valueAccessor.writeValue(value);
});
}

@Input()
set disabled(isDisabled: boolean) {
this.valueAccessor.setDisabledState?.(isDisabled);
this.#valueAccessor.setDisabledState?.(isDisabled);
}

ngAfterViewInit(): void {
this.valueAccessor.registerOnChange((value: T) => {
this.store.set(value);
this.#valueAccessor.registerOnChange((value: T) => {
this.#store?.set(value);
});
}

ngOnDestroy(): void {
this.subscription.unsubscribe();
this.#subscription?.unsubscribe();
}
}
2 changes: 2 additions & 0 deletions projects/ng-app-state/src/test-polyfills.ts
@@ -0,0 +1,2 @@
// This file can be deleted after https://github.com/angular/angular-cli/issues/14432. Be sure to delete it from `angular.json`, `tsconfig.lib.json`, and `tsconfig.spec.json`, too.
import 'zone.js';
2 changes: 1 addition & 1 deletion projects/ng-app-state/tsconfig.lib.json
Expand Up @@ -8,5 +8,5 @@
"inlineSources": true,
"types": []
},
"exclude": ["src/test.ts", "**/*.spec.ts"]
"exclude": ["src/test.ts", "**/*.spec.ts", "src/test-polyfills.ts"]
}
2 changes: 1 addition & 1 deletion projects/ng-app-state/tsconfig.spec.json
Expand Up @@ -6,5 +6,5 @@
"types": ["jasmine"]
},
"files": ["src/test.ts"],
"include": ["**/*.spec.ts", "**/*.d.ts"]
"include": ["**/*.spec.ts", "**/*.d.ts", "src/test-polyfills.ts"]
}

0 comments on commit f5f8f7a

Please sign in to comment.