Skip to content

Commit

Permalink
feat(Link): support for local anchor links
Browse files Browse the repository at this point in the history
ISSUES CLOSED: #1607
  • Loading branch information
benjamincharity committed Aug 6, 2019
1 parent c27c67d commit 9360cc1
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 34 deletions.
10 changes: 8 additions & 2 deletions demo/app/app-routing.module.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import {
ExtraOptions,
RouterModule,
Routes,
} from '@angular/router';

import { ComponentsComponent } from './components/components.component';
import { componentsList } from './components/components.constant';


const routerOptions: ExtraOptions = {anchorScrolling: 'enabled'};

const routes: Routes = [
{
path: 'components',
Expand All @@ -19,7 +25,7 @@ const routes: Routes = [


@NgModule({
imports: [RouterModule.forRoot(routes)],
imports: [RouterModule.forRoot(routes, routerOptions)],
exports: [RouterModule],
})
export class AppRoutingModule { }
26 changes: 25 additions & 1 deletion demo/app/components/link/link.component.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<ts-card>
<ts-card id="top">
<div>
<ts-link
[destination]="localDestination"
Expand All @@ -19,6 +19,30 @@
[destination]="externalDestination"
[isExternal]="external"
theme="warn"
tsVerticalSpacing
>An external link using the 'warn' theme.</ts-link>
</div>

<div>
<ts-link
fragment="bottom"
tsVerticalSpacing
>A local fragment link to go down</ts-link>
</div>
</ts-card>


<div style="height:400px;"></div>


<ts-card id="bottom">
<div>
<ts-link
fragment="top"
tsVerticalSpacing
>A local fragment link to go up</ts-link>
</div>
</ts-card>


<div style="height:400px;"></div>
4 changes: 2 additions & 2 deletions demo/app/components/link/link.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Component } from '@angular/core';
templateUrl: './link.component.html',
})
export class LinkComponent {
localDestination: string[] = ['/components/copy'];
externalDestination: string = `http://google.com`;
localDestination = ['/components/copy'];
externalDestination = `http://google.com`;
external = true;
}
8 changes: 5 additions & 3 deletions terminus-ui/link/src/link.component.html
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
<a
*ngIf="!isExternal"
class="c-link qa-link qa-link-internal"
[routerLink]="destination"
[routerLink]="destination || localRoute"
fragment="{{ fragment }}"
tabindex="{{ tabIndex }}"
*ngIf="!isExternal"
>
<ng-template *ngTemplateOutlet="contentTemplate"></ng-template>
</a>


<a
*ngIf="isExternal"
class="c-link qa-link qa-link-external"
[href]="destination"
target="_blank"
tabindex="{{ tabIndex }}"
*ngIf="isExternal"
>
<ng-template *ngTemplateOutlet="contentTemplate"></ng-template>

Expand Down
55 changes: 54 additions & 1 deletion terminus-ui/link/src/link.component.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
- [Basic usage](#basic-usage)
- [External links](#external-links)
- [Tab index](#tab-index)
- [Local URL fragments](#local-url-fragments)
- [Router changes to support local links](#router-changes-to-support-local-links)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

Expand All @@ -18,7 +20,7 @@ Wrap your link text and define a destination:

```html
<ts-link
[destination]="['your/', 'path/']"
[destination]="['your', 'path']"
>My link</ts-link>
```

Expand Down Expand Up @@ -47,3 +49,54 @@ A custom tabindex can also be set:
[tabIndex]="2"
>My link</ts-link>
```


## Local URL fragments

Local fragements are supported for deep linking within a page:

```html
<ts-link
[destination]="['your', 'path']"
fragment="myFragment"
[tabIndex]="2"
>My link</ts-link>
<!-- This would route to: `/your/path#myFragment -->
```

If no destination is defined, it will fallback to the local page: `['.']`:

```html
<ts-link
fragment="myFragment"
[tabIndex]="2"
>My link</ts-link>
<!-- If used on the route `/my/home`, this would route to: `/my/home#myFragment` -->
```

### Router changes to support local links

There are a couple needed Router configuration changes to support local links:

```typescript
import { NgModule } from '@angular/core';
import {
ExtraOptions,
RouterModule,
Routes,
} from '@angular/router';

const myRoutes: Routes = [...];
const routerOptions: ExtraOptions = {
anchorScrolling: 'enabled',
scrollPositionRestoration: 'enabled',
};

@NgModule({
...
imports: [RouterModule.forRoot(myRoutes, routerOptions)],
...
})
```

> Learn more about Angular's `ExtraOptions` here: https://angular.io/api/router/ExtraOptions
75 changes: 50 additions & 25 deletions terminus-ui/link/src/link.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { APP_BASE_HREF } from '@angular/common';
import {
Component,
Provider,
Type,
ViewChild,
} from '@angular/core';
import {
async,
ComponentFixture,
TestBed,
tick,
} from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { getDomAttribute } from '@terminus/ngx-tools';
import { createComponent } from '@terminus/ngx-tools/testing';
import { TsStyleThemeTypes } from '@terminus/ui/utilities';

Expand All @@ -18,18 +23,20 @@ import { TsLinkModule } from './link.module';

@Component({
template: `
<ts-link
[destination]="destination"
[isExternal]="isExternal"
[tabIndex]="tabIndex"
[theme]="theme"
>
My Link Text
</ts-link>
<ts-link
[destination]="destination"
[fragment]="fragment"
[isExternal]="isExternal"
[tabIndex]="tabIndex"
[theme]="theme"
>
My Link Text
</ts-link>
`,
})
class TestHostComponent {
public destination!: any;
public destination!: undefined | string | string[];
public fragment!: undefined | string;
public isExternal!: boolean;
public tabIndex!: number | undefined;
public theme: TsStyleThemeTypes = 'primary';
Expand All @@ -45,19 +52,12 @@ describe(`TsLinkComponent`, function() {
let link: HTMLElement;
let linkComponent: TsLinkComponent;

beforeEach(() => {
fixture = createComponent(
TestHostComponent,
[{
provide: APP_BASE_HREF,
useValue: '/my/app',
}],
[TsLinkModule, RouterModule.forRoot([])],
);
beforeEach(async(() => {
fixture = createComponent(TestHostComponent, [], [TsLinkModule, RouterTestingModule]);
component = fixture.componentInstance;
linkComponent = component.linkComponent;
fixture.detectChanges();
});
}));


test(`should exist`, () => {
Expand All @@ -68,13 +68,38 @@ describe(`TsLinkComponent`, function() {
describe(`isInternal`, () => {

test(`should default and retrieve`, () => {
link = fixture.debugElement.query(By.css('.c-link')).nativeElement as HTMLElement;
link = fixture.debugElement.query(By.css('.c-link')).nativeElement;
component.isExternal = false;
component.destination = ['/#'];

expect(link.classList).toContain('qa-link-internal');
});


describe(`fragment`, function() {

test(`should correctly add the fragment`, function() {
fixture.componentInstance.destination = ['foo', 'bar'];
fixture.componentInstance.fragment = 'fooBar-bing';
fixture.detectChanges();
link = fixture.debugElement.query(By.css('.ts-link')).nativeElement;

const href = fixture.debugElement.query(By.css('a')).nativeElement.getAttribute('href');
expect(href).toEqual('/foo/bar#fooBar-bing');
});


test(`should use a route to the current page if none is passed in`, function() {
fixture.componentInstance.fragment = 'fooBar-bing';
fixture.detectChanges();
link = fixture.debugElement.query(By.css('.ts-link')).nativeElement;

const href = fixture.debugElement.query(By.css('a')).nativeElement.getAttribute('href');
expect(href).toEqual('/#fooBar-bing');
});

});

});


Expand All @@ -84,7 +109,7 @@ describe(`TsLinkComponent`, function() {
component.destination = 'www.google.com';
component.isExternal = true;
fixture.detectChanges();
link = fixture.debugElement.query(By.css('.c-link')).nativeElement as HTMLElement;
link = fixture.debugElement.query(By.css('.c-link')).nativeElement;

expect(link.classList).toContain('qa-link-external');
expect(link.children[0].textContent).toContain('open_in_new');
Expand All @@ -100,7 +125,7 @@ describe(`TsLinkComponent`, function() {

component.tabIndex = 9;
fixture.detectChanges();
link = fixture.debugElement.query(By.css('.c-link')).nativeElement as HTMLElement;
link = fixture.debugElement.query(By.css('.c-link')).nativeElement;

expect(link.tabIndex).toEqual(9);
});
Expand All @@ -111,13 +136,13 @@ describe(`TsLinkComponent`, function() {
describe(`theme`, function() {

test(`should set the appropriate class`, function() {
link = fixture.debugElement.query(By.css('.ts-link')).nativeElement as HTMLElement;
link = fixture.debugElement.query(By.css('.ts-link')).nativeElement;

expect(link.classList).toContain('ts-link--primary');

component.theme = 'accent';
fixture.detectChanges();
link = fixture.debugElement.query(By.css('.ts-link')).nativeElement as HTMLElement;
link = fixture.debugElement.query(By.css('.ts-link')).nativeElement;

expect(link.classList).not.toContain('ts-link--primary');
expect(link.classList).toContain('ts-link--accent');
Expand Down
12 changes: 12 additions & 0 deletions terminus-ui/link/src/link.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { TsStyleThemeTypes } from '@terminus/ui/utilities';
*
* <ts-link
* destination="http://google.com"
* fragment="myElementId"
* [isExternal]="true"
* tabIndex="2"
* theme="warn"
Expand Down Expand Up @@ -50,12 +51,23 @@ export class TsLinkComponent {
*/
public externalIcon = `open_in_new`;

/**
* Define the route needed when only using a fragment
*/
public localRoute = ['.'];

/**
* Define the link's destination
*/
@Input()
public destination: string | string[] | undefined;

/**
* Define the link's fragment
*/
@Input()
public fragment: string | undefined;

/**
* Define if the link is to an external page
*/
Expand Down

0 comments on commit 9360cc1

Please sign in to comment.