Skip to content

Commit 4b9dc22

Browse files
authored
fix: apply nullable checks for async inputs (#74)
1 parent 66ed9b9 commit 4b9dc22

File tree

7 files changed

+54
-16
lines changed

7 files changed

+54
-16
lines changed

apps/responsive-app-e2e/src/integration/app.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ describe('responsive-app', () => {
3939
});
4040

4141
it('should show links and info on desktop', () => {
42-
getNavigationLinks().should('have.length', 5);
42+
getNavigationLinks().should('have.length', 6);
4343
cy.get('h2').contains('Select user profile!');
4444
});
4545

@@ -51,7 +51,7 @@ describe('responsive-app', () => {
5151

5252
it('should show links on desktop profile', () => {
5353
cy.visit('/3');
54-
getNavigationLinks().should('have.length', 5);
54+
getNavigationLinks().should('have.length', 6);
5555
});
5656

5757
it('should not show back button on desktop profile', () => {

apps/responsive-app/src/app/app.component.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@
4848
linkActive="mat-list-single-selected-option"
4949
>Profile 5</a
5050
>
51+
<a
52+
mat-list-item
53+
[linkTo]="'../6' | valueToAsyncValue | async"
54+
[linkActive]="
55+
'mat-list-single-selected-option' | valueToAsyncValue | async
56+
"
57+
>Profile 6 (Async)</a
58+
>
5159
</mat-nav-list>
5260
</mat-grid-tile>
5361
<mat-grid-tile style="background-color: lightcoral;">

apps/responsive-app/src/app/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { MediaDirective } from './use-media/use-media.directive';
1414
import { SelectUserComponent } from './select-user/select-user.component';
1515
import { UserProfileComponent } from './user-profile/user-profile.component';
1616
import { UserListComponent } from './user-list/user-list.component';
17+
import { ValueToAsyncValuePipe } from './value-to-async-value.pipe';
1718

1819
@NgModule({
1920
declarations: [
@@ -22,6 +23,7 @@ import { UserListComponent } from './user-list/user-list.component';
2223
SelectUserComponent,
2324
UserProfileComponent,
2425
UserListComponent,
26+
ValueToAsyncValuePipe,
2527
],
2628
imports: [
2729
BrowserModule,
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Pipe, PipeTransform } from '@angular/core';
2+
import { Observable, of } from 'rxjs';
3+
import { delay } from 'rxjs/operators';
4+
5+
@Pipe({
6+
name: 'valueToAsyncValue',
7+
})
8+
export class ValueToAsyncValuePipe implements PipeTransform {
9+
transform(value: string): Observable<string> {
10+
return of(value).pipe(delay(10));
11+
}
12+
}

libs/router/src/lib/link-active.directive.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { LinkTo } from './link-to.directive';
1515
import { Router } from './router.service';
1616
import { combineLatest, of, Subject, Subscription } from 'rxjs';
1717
import { map, mapTo, startWith, takeUntil } from 'rxjs/operators';
18+
import { filterNullable } from './operators/filter-nullable.operator';
1819

1920
export interface LinkActiveOptions {
2021
exact: boolean;
@@ -39,8 +40,8 @@ export class LinkActive implements AfterContentInit, OnDestroy, OnChanges {
3940
@ContentChildren(LinkTo, { descendants: true }) public links: QueryList<
4041
LinkTo
4142
>;
42-
@Input('linkActive') activeClass = 'active';
43-
@Input() activeOptions: LinkActiveOptions;
43+
@Input('linkActive') activeClass: string | null = 'active';
44+
@Input() activeOptions?: LinkActiveOptions | null;
4445
private _activeOptions: LinkActiveOptions = { exact: true };
4546
private _destroy$ = new Subject();
4647
private _linksSub!: Subscription;
@@ -83,16 +84,20 @@ export class LinkActive implements AfterContentInit, OnDestroy, OnChanges {
8384
.map((link) =>
8485
link.hrefUpdated.pipe(
8586
startWith(link.linkHref),
86-
mapTo(link.linkHref)
87+
mapTo(link.linkHref),
88+
filterNullable()
8789
)
8890
)
8991
: [];
92+
9093
const link$ = this.link
9194
? this.link.hrefUpdated.pipe(
9295
startWith(this.link.linkHref),
93-
mapTo(this.link.linkHref)
96+
mapTo(this.link.linkHref),
97+
filterNullable()
9498
)
9599
: of('');
100+
96101
const router$ = this.router.url$.pipe(
97102
map((path) => this.router.getExternalUrl(path || '/'))
98103
);
@@ -109,14 +114,10 @@ export class LinkActive implements AfterContentInit, OnDestroy, OnChanges {
109114
checkActive(linkHrefs: string[], path: string) {
110115
const active = linkHrefs.reduce((isActive, current) => {
111116
const [href] = current.split('?');
112-
113117
if (this._activeOptions.exact) {
114-
isActive = isActive ? isActive : href === path;
115-
} else {
116-
isActive = isActive ? isActive : path.startsWith(href);
118+
return isActive ? isActive : href === path;
117119
}
118-
119-
return isActive;
120+
return isActive ? isActive : path.startsWith(href);
120121
}, false);
121122

122123
this.updateClasses(active);

libs/router/src/lib/link-to.directive.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,22 @@ const DEFAULT_TARGET = '_self';
2323
@Directive({ selector: 'a[linkTo]' })
2424
export class LinkTo {
2525
@Input() target = DEFAULT_TARGET;
26-
@HostBinding('href') linkHref: string;
26+
@HostBinding('href') linkHref?: string | null;
2727

28-
@Input() set linkTo(href: string) {
28+
@Input() set linkTo(href: string | null | undefined) {
29+
if (href === null || href === undefined) {
30+
return;
31+
}
2932
this._href = href;
3033
this._updateHref();
3134
}
3235

33-
@Input() set queryParams(params: Params) {
36+
@Input() set queryParams(params: Params | null | undefined) {
3437
this._query = params;
3538
this._updateHref();
3639
}
3740

38-
@Input() set fragment(hash: string) {
41+
@Input() set fragment(hash: string | null | undefined) {
3942
this._hash = hash;
4043
this._updateHref();
4144
}
@@ -54,6 +57,9 @@ export class LinkTo {
5457
*/
5558
@HostListener('click', ['$event'])
5659
onClick(event: any) {
60+
if (!this._href) {
61+
return;
62+
}
5763
if (!this._comboClick(event) && this.target === DEFAULT_TARGET) {
5864
this.router.go(this._href, this._query, this._hash);
5965

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Observable } from 'rxjs';
2+
import { filter } from 'rxjs/operators';
3+
4+
export function isNotNullable<T>(it: T): it is NonNullable<T> {
5+
return it !== null && it !== undefined;
6+
}
7+
8+
export const filterNullable = <T>() => (source: Observable<T>) =>
9+
source.pipe(filter(isNotNullable));

0 commit comments

Comments
 (0)