Skip to content

15.0.0-rc.0

Pre-release
Pre-release
Compare
Choose a tag to compare
@LayZeeDK LayZeeDK released this 03 Sep 14:36
· 9 commits to main since this release
8228d7e

Features

  • LocalRouterStore matches ActivatedRoute more closely (#309)
    • Use ActivatedRoute to serialize the router state for the local router store implementation (LocalRouterStore)
    • LocalRouterStore.currentRoute$ matches ActivatedRoute.snapshot
  • Remove optional type parameter from RouterStore#selectRouteData (#316)
  • Replace MinimalRouteData with StrictRouteData (#319)
  • Change RouterStore#routeData$ and MinimalActivatedRouteSnapshot#data types from Data to StrictRouteData (#319)
  • Use strict and immutable route parameters (#319, #321)
  • Use strict and immutable query parameters (#320)

BREAKING CHANGES

LocalRouterStore.currentRoute$ matches ActivatedRoute.snapshot

This change in implementation will make the local router store more closely match ActivatedRoute while the global router store matches NgRx Router Store selectors. Through complex route configurations, the router store implementations are exercised to identify edge case differences between them and any breaking changes introduced to the local router store.

BEFORE:

// URL: /parent/child/grandchild

@Component({
  /* (...) */
  providers: [provideLocalRouterStore()],
})
export class ChildComponent implements OnInit {
  #route = inject(ActivatedRoute);
  #routerStore = inject(RouterStore);

  ngOnInit() {
    const currentRouteSnapshot = this.#route.snapshot;
    console.log(currentRouteSnapshot.routeConfig.path);
    // -> "child"
    console.log(currentRouteSnapshot.url[0].path);
    // -> "child"

    firstValueFrom(this.#routerStore.currentRoute$).then((currentRoute) => {
      console.log(currentRoute.routeConfig.path);
      // -> "grandchild"
      console.log(currentRoute.url[0].path);
      // -> "grandchild"
    });
  }
}

AFTER:

// URL: /parent/child/grandchild

@Component({
  /* (...) */
  providers: [provideLocalRouterStore()],
})
export class ChildComponent implements OnInit {
  #route = inject(ActivatedRoute);
  #routerStore = inject(RouterStore);

  ngOnInit() {
    const currentRouteSnapshot = this.#route.snapshot;
    console.log(currentRouteSnapshot.routeConfig.path);
    // -> "child"
    console.log(currentRouteSnapshot.url[0].path);
    // -> "child"

    firstValueFrom(this.#routerStore.currentRoute$).then((currentRoute) => {
      console.log(currentRoute.routeConfig.path);
      // -> "child"
      console.log(currentRoute.url[0].path);
      // -> "child"
    });
  }
}

The type parameter is removed from RouterStore#selectRouteData for stricter typing and to enforce coercion

BEFORE:

// heroes.component.ts
// (...)
import { RouterStore } from '@ngworker/router-component-store';
@Component({
  // (...)
})
export class HeroesComponent {
  #routerStore = inject(RouterStore);
  limit$ = this.#routerStore.selectRouteData<number>('limit');
}

AFTER:

// heroes.component.ts
// (...)
import { RouterStore } from '@ngworker/router-component-store';
@Component({
  // (...)
})
export class HeroesComponent {
  #routerStore = inject(RouterStore);
  limit$ = this.#routerStore.selectRouteData('limit').pipe(x => Number(x));

The RouterStore#routeData$ selector emits StrictRouteData instead of Data

BEFORE:

// heroes.component.ts
// (...)
import { RouterStore } from '@ngworker/router-component-store';
@Component({
  // (...)
})
export class HeroesComponent {
  #routerStore = inject(RouterStore);
  limit$: Observable<number> = this.#routerStore.routeData$.pipe(
    map((routeData) => routeData['limit'])
  );
}

AFTER:

// heroes.component.ts
// (...)
import { RouterStore } from '@ngworker/router-component-store';
@Component({
  // (...)
})
export class HeroesComponent {
  #routerStore = inject(RouterStore);
  limit$: Observable<number> = this.#routerStore.routeData$.pipe(
    map(routeData => routeData['limit']),
    map(x => Number(x))
  );

RouterStore#routeParams$ and MinimalActivatedRouteSnapshot#params use StrictRouteData instead of Params. Members are read-only and of type string | undefined instead of any

TypeScript will fail to compile application code that has assumed a route type parameter type other than string | undefined.

BEFORE:

// heroes.component.ts
// (...)
import { RouterStore } from '@ngworker/router-component-store';

@Component({
  // (...)
})
export class DashboardComponent {
  #routerStore = inject(RouterStore);

  limit$: Observable<number> = this.#routerStore.routeParams$.pipe(
    map((params) => params['limit'])
  );
}

AFTER:

// heroes.component.ts
// (...)
import { RouterStore } from '@ngworker/router-component-store';

@Component({
  // (...)
})
export class DashboardComponent {
  #routerStore = inject(RouterStore);

  limit$: Observable<number> = this.#routerStore.routeParams$.pipe(
    map((params) => Number(params['limit'] ?? 10))
  );
}

StrictRouteData members are now read-only

TypeScript will fail to compile application code that mutates route data data structures.

BEFORE:

// heroes.component.ts
// (...)
import { RouterStore } from '@ngworker/router-component-store';

@Component({
  // (...)
})
export class DashboardComponent {
  #routerStore = inject(RouterStore);

  limit$: Observable<number> = this.#routerStore.routeData$.pipe(
    map((data) => {
      data['limit'] = Number(data['limit']);

      return data;
    }),
    map((data) => data['limit'])
  );
}

AFTER:

// heroes.component.ts
// (...)
import { RouterStore } from '@ngworker/router-component-store';

@Component({
  // (...)
})
export class DashboardComponent {
  #routerStore = inject(RouterStore);

  limit$: Observable<number> = this.#routerStore.routeData$.pipe(
    map((data) => Number(data['limit']))
  );
}

RouterStore#queryParams$ and MinimalActivatedRouteSnapshot#queryParams use StrictRouteParams instead of Params. Members are read-only and of type string | undefined instead of any

TypeScript will fail to compile application code that has assumed a query parameter type other than string | undefined.

BEFORE:

// heroes.component.ts
// (...)
import { RouterStore } from '@ngworker/router-component-store';

@Component({
  // (...)
})
export class DashboardComponent {
  #routerStore = inject(RouterStore);

  limit$: Observable<number> = this.#routerStore.queryParams$.pipe(
    map((params) => params['limit'])
  );
}

AFTER:

// heroes.component.ts
// (...)
import { RouterStore } from '@ngworker/router-component-store';

@Component({
  // (...)
})
export class DashboardComponent {
  #routerStore = inject(RouterStore);

  limit$: Observable<number> = this.#routerStore.queryParams$.pipe(
    map((params) => Number(params['limit'] ?? 10))
  );
}

Compatibility

To avoid compatibility issues, we now require the same RxJS peer dependency as NgRx ComponentStore, namely at least RxJS version 7.5 (#311).

  • Require Angular 15.0
  • Require @ngrx/component-store 15.0
  • Require RxJS 7.5
  • Require TypeScript 4.8