fix(router): router-link works without params

Router-link attaches a listener to prevent default behavior and
navigate.

Closes: 1689
This commit is contained in:
Rado Kirov 2015-05-06 18:30:37 -07:00
parent c2a42d5d2b
commit 77d1fc149a
6 changed files with 68 additions and 13 deletions

View File

@ -127,7 +127,12 @@ export class Parse5DomAdapter extends DomAdapter {
return this.createEvent(eventType); return this.createEvent(eventType);
} }
createEvent(eventType) { createEvent(eventType) {
return {type: eventType}; var evt = {
type: eventType,
defaultPrevented: false,
preventDefault: () => {evt.defaultPrevented = true}
};
return evt;
} }
getInnerHTML(el) { getInnerHTML(el) {
return serializer.serialize(this.templateAwareRoot(el)); return serializer.serialize(this.templateAwareRoot(el));

View File

@ -1,4 +1,4 @@
import {RegExp, RegExpWrapper, RegExpMatcherWrapper, StringWrapper, isPresent} from 'angular2/src/facade/lang'; import {RegExp, RegExpWrapper, RegExpMatcherWrapper, StringWrapper, isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
import {Map, MapWrapper, StringMap, StringMapWrapper, List, ListWrapper} from 'angular2/src/facade/collection'; import {Map, MapWrapper, StringMap, StringMapWrapper, List, ListWrapper} from 'angular2/src/facade/collection';
import {escapeRegex} from './url'; import {escapeRegex} from './url';
@ -27,6 +27,9 @@ class DynamicSegment {
} }
generate(params:StringMap) { generate(params:StringMap) {
if (!StringMapWrapper.contains(params, this.name)) {
throw new BaseException(`Route generator for '${this.name}' was not included in parameters passed.`)
}
return StringMapWrapper.get(params, this.name); return StringMapWrapper.get(params, this.name);
} }
} }
@ -118,6 +121,7 @@ export class PathRecognizer {
} }
generate(params:StringMap):string { generate(params:StringMap):string {
return ListWrapper.join(ListWrapper.map(this.segments, (segment) => '/' + segment.generate(params)), ''); return ListWrapper.join(ListWrapper.map(this.segments, (segment) =>
'/' + segment.generate(params)), '');
} }
} }

View File

@ -1,5 +1,6 @@
import {Directive} from 'angular2/src/core/annotations_impl/annotations'; import {Directive, onAllChangesDone} from 'angular2/src/core/annotations_impl/annotations';
import {ElementRef} from 'angular2/core'; import {ElementRef} from 'angular2/core';
import {StringMap, StringMapWrapper} from 'angular2/src/facade/collection';
import {isPresent} from 'angular2/src/facade/lang'; import {isPresent} from 'angular2/src/facade/lang';
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
@ -32,33 +33,40 @@ import {Router} from './router';
properties: { properties: {
'route': 'routerLink', 'route': 'routerLink',
'params': 'routerParams' 'params': 'routerParams'
} },
lifecycle: [onAllChangesDone]
}) })
export class RouterLink { export class RouterLink {
_domEl; _domEl;
_route:string; _route:string;
_params:any; _params:any;
_router:Router; _router:Router;
//TODO: handle click events _href:string;
constructor(elementRef:ElementRef, router:Router) { constructor(elementRef:ElementRef, router:Router) {
this._domEl = elementRef.domElement; this._domEl = elementRef.domElement;
this._router = router; this._router = router;
this._params = StringMapWrapper.create();
DOM.on(this._domEl, 'click', (evt) => {
evt.preventDefault();
this._router.navigate(this._href);
});
} }
set route(changes) { set route(changes) {
this._route = changes; this._route = changes;
this.updateHref();
} }
set params(changes) { set params(changes) {
this._params = changes; this._params = changes;
this.updateHref();
} }
updateHref() { onAllChangesDone() {
if (isPresent(this._route) && isPresent(this._params)) { if (isPresent(this._route) && isPresent(this._params)) {
var newHref = this._router.generate(this._route, this._params); var newHref = this._router.generate(this._route, this._params);
this._href = newHref;
// Keeping the link on the element to support contextual menu `copy link`
// and other in-browser affordances.
DOM.setAttribute(this._domEl, 'href', newHref); DOM.setAttribute(this._domEl, 'href', newHref);
} }
} }

View File

@ -18,7 +18,7 @@ export class RouterOutlet {
_router:routerMod.Router; _router:routerMod.Router;
_viewContainer:ViewContainerRef; _viewContainer:ViewContainerRef;
constructor(viewContainer:ViewContainerRef, compiler:Compiler, router:routerMod.Router, injector:Injector, @Attribute('name') nameAttr) { constructor(viewContainer:ViewContainerRef, compiler:Compiler, router:routerMod.Router, injector:Injector, @Attribute('name') nameAttr:String) {
if (isBlank(nameAttr)) { if (isBlank(nameAttr)) {
nameAttr = 'default'; nameAttr = 'default';
} }

View File

@ -34,7 +34,7 @@ import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_meta
export function main() { export function main() {
describe('Outlet Directive', () => { describe('Outlet Directive', () => {
var ctx, tb, view, rtr; var ctx, tb, view, rtr, location;
beforeEachBindings(() => [ beforeEachBindings(() => [
Pipeline, Pipeline,
@ -46,10 +46,11 @@ export function main() {
}, [RouteRegistry, Pipeline, Location]) }, [RouteRegistry, Pipeline, Location])
]); ]);
beforeEach(inject([TestBed, Router], (testBed, router) => { beforeEach(inject([TestBed, Router, Location], (testBed, router, loc) => {
tb = testBed; tb = testBed;
ctx = new MyComp(); ctx = new MyComp();
rtr = router; rtr = router;
location = loc;
})); }));
function compile(template:string = "<router-outlet></router-outlet>") { function compile(template:string = "<router-outlet></router-outlet>") {
@ -131,8 +132,18 @@ export function main() {
}); });
})); }));
it('should generate link hrefs without params', inject([AsyncTestCompleter], (async) => {
compile('<a href="hello" router-link="user"></a>')
.then((_) => rtr.config({'path': '/user', 'component': UserCmp, 'as': 'user'}))
.then((_) => rtr.navigate('/a/b'))
.then((_) => {
view.detectChanges();
expect(DOM.getAttribute(view.rootNodes[0].childNodes[0], 'href')).toEqual('/user');
async.done();
});
}));
it('should generate link hrefs', inject([AsyncTestCompleter], (async) => { it('should generate link hrefs with params', inject([AsyncTestCompleter], (async) => {
ctx.name = 'brian'; ctx.name = 'brian';
compile('<a href="hello" router-link="user" [router-params]="{name: name}">{{name}}</a>') compile('<a href="hello" router-link="user" [router-params]="{name: name}">{{name}}</a>')
.then((_) => rtr.config({'path': '/user/:name', 'component': UserCmp, 'as': 'user'})) .then((_) => rtr.config({'path': '/user/:name', 'component': UserCmp, 'as': 'user'}))
@ -145,6 +156,27 @@ export function main() {
}); });
})); }));
it('should generate link hrefs without params', inject([AsyncTestCompleter], (async) => {
compile('<a href="hello" router-link="user"></a>')
.then((_) => rtr.config({'path': '/user', 'component': UserCmp, 'as': 'user'}))
.then((_) => rtr.navigate('/a/b'))
.then((_) => {
view.detectChanges();
var anchorEl = view.rootNodes[0].childNodes[0];
expect(DOM.getAttribute(anchorEl, 'href')).toEqual('/user');
var dispatchedEvent = DOM.createMouseEvent('click');
DOM.dispatchEvent(anchorEl, dispatchedEvent);
expect(dispatchedEvent.defaultPrevented).toBe(true);
// router navigation is async.
rtr.subscribe((_) => {
expect(location.urlChanges).toEqual(['/user']);
async.done();
});
});
}));
}); });
} }

View File

@ -68,5 +68,11 @@ export function main() {
recognizer.addConfig('/app/user/:name', handler, 'user'); recognizer.addConfig('/app/user/:name', handler, 'user');
expect(recognizer.generate('user', {'name' : 'misko'})).toEqual('/app/user/misko'); expect(recognizer.generate('user', {'name' : 'misko'})).toEqual('/app/user/misko');
}); });
it('should throw in the absence of required params URLs', () => {
recognizer.addConfig('/app/user/:name', handler, 'user');
expect(() => recognizer.generate('user', {})).toThrowError(
'Route generator for \'name\' was not included in parameters passed.');
});
}); });
} }