npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@softeq/angular-route-data

v1.0.0-beta.3

Published

Allows to keep URL up to date with the page content

Downloads

1

Readme

@softeq/angular-route-data

@softeq/angular-route-data allows to keep URL up to date with the page content.

Motivation

Let's consider the following example to understand what problem this library tries to solve.
Imagine we have a page under the route /heroes displaying list of heroes and toggle that allows to show only active heroes.

When user turns switch on we need to show only active heroes and update URL to /heroes?onlyActive=true. In addition user can open this URL by putting it into the address bar of the browser and go to the same page having switch show only active heroes on.
In the real world, page can have more criteria, like name of hero, set of checkboxes, etc. Such behavior allows user to keep bookmarkable URLs or send URLs via messenger. For developer it can simplify implementation of back behavior (when user press on browser back button).

Example

The following code demonstrates behavior described in Motivation section.

interface Hero {
  name: string;
  active: boolean;
}

@Component({
  selector: 'hero-list',
  template: `
    <mat-slide-toggle [formControl]="onlyActiveControl">Show only active heroes</mat-slide-toggle>
    <mat-list>
      <mat-list-item *ngFor="let hero of filteredHeroes">{{ hero.name }}</mat-list-item>
    </mat-list>
  `,
})
export class HeroListComponent implements OnInit {
  onlyActiveControl = new FormControl(false);
  heroes: Hero[] = [
    { name: 'Black Widow', active: false },
    { name: 'Groot', active: true },
    { name: 'Hulk', active: true },
    { name: 'Iron Man', active: false },
    { name: 'Rocket Raccoon', active: true },
    { name: 'Thor', active: true },
  ];
  filteredHeroes: Hero[];

  constructor(private route: ActivatedRoute,
              private routeData: RouteDataService) {
  
  }

  ngOnInit(): void {
    const tracker = this.routeData.tracker(controlTracker('onlyActive', this.onlyActiveControl, booleanMapper()));

    tracker.listen(this.route);
    tracker.filterChanges.subscribe(([onlyActive]) => {
      this.filteredHeroes = onlyActive ? this.heroes.filter((hero) => hero.active) : this.heroes;
    });
  }
}

Here we track value of onlyActiveControl and URL of the current page.
If user switches value of control, URL and set of filteredHeroes are updated correspondingly.
From the other side when user opens page by /heroes?onlyActive=true URL, onlyActiveControl is already set to true and list of heroes shows only active heroes.

Let's consider this code in more details.

  1. All tracking logic is provided by RouteDataService. Also we need ActivatedRoute from @angular/router package to track route parameters

    constructor(private route: ActivatedRoute,
                private routeData: RouteDataService)
  2. Create instance of RouteTracker. RouteTracker is a key entity for tracking behavior. It can be created using RouteDataService.tracker method.

    const tracker = this.routeData.tracker(controlTracker('onlyActive', this.onlyActiveControl, booleanMapper()));

    tracker method accepts set of RouteParamTracker.
    RouteParamTracker tracks subset of route query parameters. It is responsible for

    • tracking of route query parameters (merge query parameters into the underlying page object)
    • tracking of filter changes (notify when underlying page object has changed, like toggling of switch or changing of text value);
    • notifying about new query parameters (generate set of query parameters to be merged into the current URL)

    In our example the following code create RouteParamTracker

    controlTracker('onlyActive', this.onlyActiveControl, booleanMapper())

    which tracks value of onlyActive query parameter, mirrors its value in the instance of FormControl (onlyActiveControl) and treats this value as boolean (using booleanMapper(). It is important to map query parameter value, because query parameter is always a string).

    This way RouteParamTracker created by controlTracker

    • merges value of onlyActive query parameter into the onlyActiveControl
    • notifies about new value coming from onlyActiveControl when user toggles switch
    • and emits new value of onlyActive query parameter when user toggles switch
  3. Bind route to the instance of RouteTracker

    tracker.listen(this.route);

    This way RouteTracker can listen for changes in route query parameters.

  4. Update displayed data when filter is changed

    tracker.filterChanges.subscribe(([onlyActive]) => {
      this.filteredHeroes = onlyActive ? this.heroes.filter((hero) => hero.active) : this.heroes;
    });

    filterChanges emits new value as soon as one of assigned RouteParamTrackers notifies about changes.

Default value

What if value of onlyActiveControl from the Example section should be set to true by default.
This way when user opens the page by URL /heroes, he should see switch Show only active heroes in toggled state. When user clicks on switch, URL become /heroes?onlyActive=false. One more click makes URL /heroes?onlyActive=true and shows only active heroes again.
There is no big sense to set onlyActive=true when true is a default value. From the perspective of page content URLs /heroes and /heroes?onlyActive=true are the same.
In order to hide query parameter when it accepts default value, you can tell RouteParamTracker what value is a default one.

const tracker = this.routeData
  .tracker(controlTracker('onlyActive', this.onlyActiveControl, booleanMapper()).withDefault(true));

Filters and effects

RouteParamTracker can be registered as filter or effect. Example of filter RouteParamTracker was shown in the Example section. Effect RouteParamTracker is used when you need to avoid dramatic changes in UI.

Look at the following snippet.
Say, you need to track scroll position and save it in the URL like /heroes?onlyActive=true&scroll=150. This allows to open page on the same scroll position it had before.

const tracker = this.routeData.tracker(
  controlTracker('onlyActive', this.onlyActiveControl, booleanMapper()),
  scrollTracker(...)); // this is just an example, library does not provide such tracker

tracker.listen(this.route);
tracker.filterChanges.subscribe(([onlyActive]) => {
  // filter set of heroes
});

Here, scrollTracker is registered as filter. It means each time scroll position is changed filterChanges will trigger new filter and set of filteredHeroes will be recalculated. This is barely expected behavior. We need the way to track scroll position, but should not emit new filter. To implement such behavior we can register scrollTracker as effect.

const tracker = this.routeData
  .tracker(controlTracker('onlyActive', this.onlyActiveControl, booleanMapper()))
  .withEffects(scrollTracker(...));

tracker.listen(this.route);
tracker.filterChanges.subscribe(([onlyActive]) => {
  // filter set of heroes
});

Now scroll position will be tracked, but this will not lead to emitting of new filter.

Existing RouteParamTrackers

|RouteParamTracker factory|Note| |---|---| |controlTracker|Creates tracker that mirrors value of single query parameter in the provided AbstractControl| |filterTracker|Creates tracker that mirrors value of single query parameter in the instance of Filterable object| |manualTracker|Creates the simplest tracker that allows to track value manually and mirror it in the single query parameter| |observableFilterTracker|Creates tracker that mirrors values of provided stream in the single query parameter| |observablePlainFilterTracker|Creates tracker that mirrors values of provided stream in the set of query parameters|