Skip to content

Commit

Permalink
feat: add router code
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonroberts committed May 2, 2020
1 parent e4b6703 commit 5068b0f
Show file tree
Hide file tree
Showing 22 changed files with 1,362 additions and 32 deletions.
40 changes: 40 additions & 0 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,46 @@
}
}
}
},
"router": {
"projectType": "library",
"root": "projects/router",
"sourceRoot": "projects/router/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-ng-packagr:build",
"options": {
"tsConfig": "projects/router/tsconfig.lib.json",
"project": "projects/router/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "projects/router/tsconfig.lib.prod.json"
}
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "projects/router/src/test.ts",
"tsConfig": "projects/router/tsconfig.spec.json",
"karmaConfig": "projects/router/karma.conf.js"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"projects/router/tsconfig.lib.json",
"projects/router/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
}
},
"defaultProject": "blog"
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.901.4",
"@angular-devkit/build-ng-packagr": "~0.901.4",
"@angular/cli": "~9.1.4",
"@angular/compiler-cli": "~9.1.4",
"@angular/language-service": "~9.1.4",
"@types/node": "^12.11.1",
"codelyzer": "^5.1.2",
"ng-packagr": "^9.0.0",
"ts-node": "~7.0.0",
"tslint": "~5.11.0",
"typescript": "~3.8.3"
Expand Down
24 changes: 24 additions & 0 deletions projects/router/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Router

This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 9.1.4.

## Code scaffolding

Run `ng generate component component-name --project router` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project router`.
> Note: Don't forget to add `--project router` or else it will be added to the default project in your `angular.json` file.
## Build

Run `ng build router` to build the project. The build artifacts will be stored in the `dist/` directory.

## Publishing

After building your library with `ng build router`, go to the dist folder `cd dist/router` and run `npm publish`.

## Running unit tests

Run `ng test router` to execute the unit tests via [Karma](https://karma-runner.github.io).

## Further help

To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
32 changes: 32 additions & 0 deletions projects/router/karma.conf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html

module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../../coverage/router'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};
7 changes: 7 additions & 0 deletions projects/router/ng-package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/router",
"lib": {
"entryFile": "src/public-api.ts"
}
}
9 changes: 9 additions & 0 deletions projects/router/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "router",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^9.1.4",
"@angular/core": "^9.1.4",
"tslib": "^1.10.0"
}
}
74 changes: 74 additions & 0 deletions projects/router/src/lib/link.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Directive, HostBinding, HostListener, Input, Output, EventEmitter, Optional } from '@angular/core';
import { Router } from './router.service';
import { RouterComponent } from './router.component';

/**
* The LinkTo directive links to routes in your app
*
* Links are pushed to the `Router` service to trigger a route change.
* Query params can be represented as an object or a string of names/values
*
* <a linkTo="/home/page" [queryParams]="{ id: 123 }">Home Page</a>
* <a [linkTo]="'/pages' + page.id">Page 1</a>
*/
@Directive({ selector: '[linkTo]' })
export class LinkTo {
@Input() target: string;
@HostBinding('href') linkHref;

@Input() set linkTo(href: string){
this._href = href;
this._updateHref();
}

@Input() set queryParams(params: string) {
this._query = params;
this._updateHref();
}

@Output() hrefUpdated: EventEmitter<string> = new EventEmitter<string>();

private _href: string;
private _query: string;

constructor(private router: Router, @Optional() private routerComp: RouterComponent) {}

/**
* Handles click events on the associated link
* Prevents default action for non-combination click events without a target
*/
@HostListener('click', ['$event'])
onClick(event) {
if (!this._comboClick(event) && !this.target) {
this.router.go(this._href, this._query);

event.preventDefault();
}
}

private _updateHref() {
let path = this._cleanUpHref(this._href);

this.linkHref = this.router.getExternalUrl(path);
this.hrefUpdated.emit(this.linkHref);
}

/**
* Determines whether the click event happened with a combination of other keys
*/
private _comboClick(event) {
let buttonEvent = event.which || event.button;

return (buttonEvent > 1 || event.ctrlKey || event.metaKey || event.shiftKey);
}

private _cleanUpHref(href: string = ''): string {
// Check for trailing slashes in the path
while (href.length > 1 && href.substr(-1) === '/') {
// Remove trailing slashes
href = href.substring(0, href.length - 1);
}

return href;
}
}
7 changes: 7 additions & 0 deletions projects/router/src/lib/route-params.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Observable } from 'rxjs';

export interface Params {
[param: string]: any;
}

export class RouteParams extends Observable<Params> {}
117 changes: 117 additions & 0 deletions projects/router/src/lib/route.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import {
Component,
OnInit,
Input,
Type,
ViewChild,
ElementRef,
Injector,
ɵrenderComponent as renderComponent,
ɵmarkDirty as markDirty,
ɵcreateInjector as createInjector
} from "@angular/core";

import { Subject, BehaviorSubject, merge, of } from "rxjs";
import { tap, distinctUntilChanged, filter, takeUntil, mergeMap } from "rxjs/operators";

import { LoadComponent, Route } from "./route";
import { RouteParams, Params } from "./route-params.service";
import { RouterComponent } from "./router.component";


@Component({
selector: "route",
template: `
<div #outlet></div>
`
})
export class RouteComponent implements OnInit {
private destroy$ = new Subject();
@ViewChild("outlet", { read: ElementRef, static: true }) outlet: ElementRef;
@Input() path: string;
@Input() component: Type<any>;
@Input() loadComponent: LoadComponent;
route!: Route;
rendered = null;
private _routeParams$ = new BehaviorSubject<Params>({});
routeParams$ = this._routeParams$.asObservable();

constructor(private injector: Injector, private router: RouterComponent) {}

ngOnInit(): void {
// account for root level routes, don't add the basePath
const path = this.router.parentRouterComponent
? this.router.basePath + this.path
: this.path;

this.route = this.router.registerRoute({
path,
component: this.component,
loadComponent: this.loadComponent
});

const activeRoute$ = this.router.activeRoute$
.pipe(
filter(ar => ar !== null),
distinctUntilChanged(),
mergeMap(current => {
if (current.route === this.route) {
this._routeParams$.next(current.params);

if (!this.rendered) {
return this.loadAndRenderRoute(current.route);
}
} else if (this.rendered) {
return of(this.clearView());
}

return of(null);
})
);

const routeParams$ = this._routeParams$
.pipe(
distinctUntilChanged(),
filter(() => !!this.rendered),
tap(() => markDirty(this.rendered))
);

merge(activeRoute$, routeParams$).pipe(
takeUntil(this.destroy$),
).subscribe();
}

ngOnDestroy() {
this.destroy$.next();
}

loadAndRenderRoute(route: Route) {
if (route.loadComponent) {
return route.loadComponent().then(component => {
return this.renderView(component, this.outlet.nativeElement);
});
} else {
return of(this.renderView(route.component, this.outlet.nativeElement));
}
}

renderView(component: Type<any>, host: any) {
const cmpInjector = createInjector({}, this.injector, [
{ provide: RouteParams, useValue: this.routeParams$ }
]);

this.rendered = renderComponent(component, {
host,
injector: cmpInjector
});

return this.rendered;
}

clearView() {
this.outlet.nativeElement.innerHTML = "";
this.rendered = null;

return this.rendered;
}
}
17 changes: 17 additions & 0 deletions projects/router/src/lib/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Type } from '@angular/core';

import { Params } from './route-params.service';

export type LoadComponent = () => Promise<Type<any>>;

export interface Route {
path: string;
component?: Type<any>;
loadComponent?: LoadComponent;
matcher?: RegExp;
}

export interface ActiveRoute {
route: Route;
params: Params;
}

0 comments on commit 5068b0f

Please sign in to comment.