X Tutup
Skip to content

Commit a69e7fe

Browse files
committed
fix(RouterLink): do not prevent default behavior if target set on anchor element
If the anchor element on which the "router-link" directive is present has a target attribute other than "_self," the handler will not prevent default behavior of the browser. Closes #4233 Closes #5082
1 parent a9b1270 commit a69e7fe

File tree

4 files changed

+71
-7
lines changed

4 files changed

+71
-7
lines changed

modules/angular2/src/router/router_link.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {Directive} from '../core/metadata';
2-
import {StringMapWrapper} from 'angular2/src/core/facade/collection';
2+
import {isString} from 'angular2/src/core/facade/lang';
33

44
import {Router} from './router';
55
import {Location} from './location';
@@ -36,7 +36,7 @@ import {Instruction, stringifyInstruction} from './instruction';
3636
*/
3737
@Directive({
3838
selector: '[router-link]',
39-
inputs: ['routeParams: routerLink'],
39+
inputs: ['routeParams: routerLink', 'target: target'],
4040
host: {
4141
'(click)': 'onClick()',
4242
'[attr.href]': 'visibleHref',
@@ -48,6 +48,7 @@ export class RouterLink {
4848

4949
// the url displayed on the anchor element.
5050
visibleHref: string;
51+
target: string;
5152

5253
// the instruction passed to the router to navigate
5354
private _navigationInstruction: Instruction;
@@ -65,7 +66,11 @@ export class RouterLink {
6566
}
6667

6768
onClick(): boolean {
68-
this._router.navigateByInstruction(this._navigationInstruction);
69-
return false;
69+
// If no target, or if target is _self, prevent default browser behavior
70+
if (!isString(this.target) || this.target == '_self') {
71+
this._router.navigateByInstruction(this._navigationInstruction);
72+
return false;
73+
}
74+
return true;
7075
}
7176
}

modules/angular2/test/router/router_link_spec.ts

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ export function main() {
5656
tcb.createAsync(TestComponent)
5757
.then((testComponent) => {
5858
testComponent.detectChanges();
59-
let anchorElement = testComponent.debugElement.query(By.css('a')).nativeElement;
59+
let anchorElement =
60+
testComponent.debugElement.query(By.css('a.detail-view')).nativeElement;
6061
expect(DOM.getAttribute(anchorElement, 'href')).toEqual('detail');
6162
async.done();
6263
});
@@ -70,11 +71,38 @@ export function main() {
7071
.then((testComponent) => {
7172
testComponent.detectChanges();
7273
// TODO: shouldn't this be just 'click' rather than '^click'?
73-
testComponent.debugElement.query(By.css('a')).triggerEventHandler('click', null);
74+
testComponent.debugElement.query(By.css('a.detail-view'))
75+
.triggerEventHandler('click', null);
7476
expect(router.spy('navigateByInstruction')).toHaveBeenCalledWith(dummyInstruction);
7577
async.done();
7678
});
7779
}));
80+
81+
it('should call router.navigate when a link is clicked if target is _self',
82+
inject([AsyncTestCompleter, Router], (async, router) => {
83+
84+
tcb.createAsync(TestComponent)
85+
.then((testComponent) => {
86+
testComponent.detectChanges();
87+
testComponent.debugElement.query(By.css('a.detail-view-self'))
88+
.triggerEventHandler('click', null);
89+
expect(router.spy('navigateByInstruction')).toHaveBeenCalledWith(dummyInstruction);
90+
async.done();
91+
});
92+
}));
93+
94+
it('should NOT call router.navigate when a link is clicked if target is set to other than _self',
95+
inject([AsyncTestCompleter, Router], (async, router) => {
96+
97+
tcb.createAsync(TestComponent)
98+
.then((testComponent) => {
99+
testComponent.detectChanges();
100+
testComponent.debugElement.query(By.css('a.detail-view-blank'))
101+
.triggerEventHandler('click', null);
102+
expect(router.spy('navigateByInstruction')).not.toHaveBeenCalled();
103+
async.done();
104+
});
105+
}));
78106
});
79107
}
80108

@@ -94,7 +122,20 @@ class UserCmp {
94122
@View({
95123
template: `
96124
<div>
97-
<a [router-link]="['/Detail']">detail view</a>
125+
<a [router-link]="['/Detail']"
126+
class="detail-view">
127+
detail view
128+
</a>
129+
<a [router-link]="['/Detail']"
130+
class="detail-view-self"
131+
target="_self">
132+
detail view with _self target
133+
</a>
134+
<a [router-link]="['/Detail']"
135+
class="detail-view-blank"
136+
target="_blank">
137+
detail view with _blank target
138+
</a>
98139
</div>`,
99140
directives: [RouterLink]
100141
})

modules/playground/e2e_test/hash_routing/hash_location_spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,19 @@ describe('hash routing example app', function() {
2626

2727
expect(element(by.css('goodbye-cmp')).getText()).toContain('goodbye');
2828
});
29+
30+
31+
it('should open in new window if target is _blank', () => {
32+
var URL = 'playground/src/hash_routing/index.html';
33+
browser.get(URL + '#/');
34+
waitForElement('hello-cmp');
35+
36+
element(by.css('#goodbye-link-blank')).click();
37+
expect(browser.driver.getCurrentUrl()).not.toContain('#/bye');
38+
browser.getAllWindowHandles().then(function(windows) {
39+
browser.switchTo()
40+
.window(windows[1])
41+
.then(function() { expect(browser.driver.getCurrentUrl()).toContain("#/bye"); });
42+
});
43+
});
2944
});

modules/playground/src/hash_routing/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ class GoodByeCmp {
2929
<nav>
3030
<a href="#/" id="hello-link">Navigate via href</a> |
3131
<a [router-link]="['/GoodbyeCmp']" id="goodbye-link">Navigate with Link DSL</a>
32+
<a [router-link]="['/GoodbyeCmp']" id="goodbye-link-blank" target="_blank">
33+
Navigate with Link DSL _blank target
34+
</a>
3235
</nav>
3336
<router-outlet></router-outlet>
3437
`,

0 commit comments

Comments
 (0)
X Tutup