Automatically sync url path and query params with Angular component fields
Tags: angular, showdev, typescript, webdev
Published: 2021-07-25 14:23:02

Wouldn't it be nice if a user of you Angular application could just send a link to another person ant they would end up seeing the exact same things on his screen?
The user could also just bookmark the url and get back to the same view whenever he wants to.

Using the Angular router you can accomplish that even if you have very dynamic views.
However, the effort and boilerplate code for this is quite huge and can easily be avoided with a library I wrote. (My first npm package :heart:) 

The concept behind this is *deep linking*.

## What is deep linking?

_If you are already familiar with deep linking and how to implement it with the Angular Router continue [here](#the-downsides-of-the-angular-router)._

Imagine you have built a webapp for a library, and a user is browsing a list of available books. He selects a book and wants to show it to a friend. 

With deep linking the information of the selected book is part of the url and can easily be shared between users. The same thing can be done with any parameter that affects the current view in your Angular application. (e.g. additionally with a search string as shown below)

![Demo booklist](https://raw.githubusercontent.com/Nas3nmann/ngx-input-deep-linking/main/apps/demo-book-list/src/assets/demo.gif)

As shown in the example above you can write these parameters to the url as
* path parameter `library/:location/books/:selectedBookId` or
* query parameter `...?searchString=jon&secondParameter=1`.

Path parameters can be strings or numbers while query parameters also could be complex json objects

## How to implement "deep linking" with the Angular router?

In your components you can get the current path and query params by injecting the `ActivatedRouteSnapshot`:

```
export class YourComponent implements OnInit {
    private yourPathParam: number;
    private yourQueryParam: string;

    constructor(private readonly activatedRouteSnapshot: ActivatedRouteSnapshot) {}
    
    ngOnInit() {
        const pathParams: Params[] = this.activatedRouteSnapshot.params;
        this.yourPathParam = pathParams['yourPathParam'];
        
        const queryParams: Params[] = this.activatedRouteSnapshot.queryParams;
        this.yourQueryParam = queryParams['yourQueryParam'];
    }
}
```

You can even subscribe to changes in the url params by using the `ActivatedRoute` instead of the `ActivatedRouteSnapshot`:

```
this.activatedRoute.params.subscribe((newPathParams) => console.log(newPathParams));
this.activatedRoute.queryParams.subscribe((newQueryParams) => console.log(newQueryParams));
```

What about the other way around? If a field in you component changes you can navigate to the new url by injecting the `Router`:

```
this.router.navigateByUrl(newUrl);
```

The hard part here is figuring out the new url. You might not want to change existing parameters but ony change the one that is of interest in your component.
However, this is a crucial part for implementing "deep linking" as we need an updated url whenever the view changes.

## The downsides of the Angular router

As you can see synchronizing of url params with your component fields is quite hard and requires a lot of "boilerplate code".

The problems of the Angular router are
1. not providing a way to linking url parameters to component inputs via configuration
   * [A github issue](https://github.com/angular/angular/issues/18967) for this requirement exists since 2017 
2. not providing a simple way to update the url on component field changes (e.g. via component outputs)

## The simple way with ngx-deep-linking

What if you could simply configure synchronization of component fields with the url?
[Ngx-deep-linking](https://www.npmjs.com/package/@jdrks/ngx-deep-linking) provides a way to do this in the route configuration.

In the end you just need three things:
1. You need to install **@jdrks/ngx-deep-linking**:
   * ``npm install @jdrks/ngx-deep-linking`` or
   * ``yarn add @jdrks/ngx-deep-linking``
2. You need to provide `@Input` and `@Output` for the fields you want to deep link.
   * You *must provide* an `@Input` to initially populate the component field and change it if the url changes
   * You *may provide* an `@Output` to change the url whenever that component output emits. Make sure to follow the naming convention for Angular two way data binding by appending 'Change' to the field name.
3. You need to provide a router config with the names of all fields you want to "deep link":
   * See example below
   * You can use the provided interface `DeepLinkingRoute` for a type safe configuration

```
import {DeepLinkingRoute, DeepLinkingWrapperComponent} from '@jdrks/ngx-deep-linking';
...

const routes: DeepLinkingRoute[] = [
  {
    path: 'books/:selectedBookId',
    component: DeepLinkingWrapperComponent,
    wrappedComponent: BookListComponent,
    deepLinking: {
      params: [{name: 'selectedBookId', type: 'number'}],
      queryParams: [{name: 'searchString', type: 'string'}],
    }
  }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class BookListRoutingModule {
}
```

The book list component could look like this:

```
@Component({
  templateUrl: 'book-list.component.html'
})
export class BookListComponent {

  private _selectedBookId: number = 0;

  @Output()
  private selectedBookIdChange: EventEmitter<number> = new EventEmitter<number>();

  @Input()
  get selectedBookId(): number {
    return this.selectedBookId;
  }

  set selectedBookId(value: number) {
    this.selectedBookId = value;
    this.selectedBookIdChange.next(bookId);
  }
  
  private _searchString = '';

  @Output()
  searchStringChange: EventEmitter<string> = new EventEmitter<string>();
  
  @Input()
  get searchString(): string {
    return this._searchString;
  }

  set searchString(value: string) {
    this._searchString = value;
    this.searchStringChange.next(value);
  }
  
  ...
}
```

So basically it's just like two-way data binding. The above implementation will automatically emit on the `@Output` fields when using the setters, but this is just convenience.

## Features of ngx-deep-linking
* Easily synchronize your component fields with the url by simple route configuration
* Type safe configuration
* Supports synchronizing fields of type number, string and even complex types as json
* Supports lazy loading for Angular modules
* Supports hash routing strategy

## How does the library work under the hood?
The library wraps you component into the `DeepLinkingWrapperComponent`. The fields that should be "deep linked" are synced with the `DeepLinkingWrapperComponent` via "manual" two-way data binding. The wrapper reacts to changes on both url and wrapped component and updates vice versa.

![library structure](https://raw.githubusercontent.com/Nas3nmann/articles/master/ngx-deep-linking/structure.png)

## Links
Github: [https://github.com/Nas3nmann/ngx-deep-linking](https://github.com/Nas3nmann/ngx-deep-linking)
npm: [https://www.npmjs.com/package/@jdrks/ngx-deep-linking](https://www.npmjs.com/package/@jdrks/ngx-deep-linking)