fix(router): routerLinkActive should only set classes after the router has successfully navigated

This commit is contained in:
vsavkin 2016-07-20 17:51:21 -07:00
parent eb6ff65af7
commit db54a84d14
10 changed files with 69 additions and 4 deletions

View File

@ -7,3 +7,4 @@
*/ */
export {SpyLocation} from './testing/location_mock'; export {SpyLocation} from './testing/location_mock';
export {MockLocationStrategy} from './testing/mock_location_strategy';

View File

@ -16,6 +16,8 @@ import {EventEmitter, ObservableWrapper} from '../src/facade/async';
/** /**
* A mock implementation of {@link LocationStrategy} that allows tests to fire simulated * A mock implementation of {@link LocationStrategy} that allows tests to fire simulated
* location events. * location events.
*
* @stable
*/ */
@Injectable() @Injectable()
export class MockLocationStrategy extends LocationStrategy { export class MockLocationStrategy extends LocationStrategy {

View File

@ -94,7 +94,7 @@ export class RouterLinkActive implements OnChanges, OnDestroy, AfterContentInit
ngOnDestroy(): any { this.subscription.unsubscribe(); } ngOnDestroy(): any { this.subscription.unsubscribe(); }
private update(): void { private update(): void {
if (!this.links || !this.linksWithHrefs) return; if (!this.links || !this.linksWithHrefs || !this.router.navigated) return;
const currentUrlTree = this.router.parseUrl(this.router.url); const currentUrlTree = this.router.parseUrl(this.router.url);
const isActiveLinks = this.reduceList(currentUrlTree, this.links); const isActiveLinks = this.reduceList(currentUrlTree, this.links);

View File

@ -133,6 +133,13 @@ export class Router {
private config: Routes; private config: Routes;
private configLoader: RouterConfigLoader; private configLoader: RouterConfigLoader;
/**
* Indicates if at least one navigation happened.
*
* @experimental
*/
navigated: boolean = false;
/** /**
* Creates the router service. * Creates the router service.
*/ */
@ -385,6 +392,7 @@ export class Router {
}) })
.then( .then(
() => { () => {
this.navigated = true;
this.routerEvents.next( this.routerEvents.next(
new NavigationEnd(id, this.serializeUrl(url), this.serializeUrl(appliedUrl))); new NavigationEnd(id, this.serializeUrl(url), this.serializeUrl(appliedUrl)));
resolvePromise(navigationIsSuccessful); resolvePromise(navigationIsSuccessful);

View File

@ -1194,6 +1194,29 @@ describe('Integration', () => {
expect(nativeButton.className).toEqual(''); expect(nativeButton.className).toEqual('');
}))); })));
it('should not set the class until the first navigation succeeds',
fakeAsync(inject(
[Router, TestComponentBuilder, Location],
(router: Router, tcb: TestComponentBuilder, location: Location) => {
@Component({
template:
'<router-outlet></router-outlet><a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" >'
})
class RootCmpWithLink {
}
const f = tcb.createFakeAsync(RootCmpWithLink);
advance(f);
const link = f.debugElement.nativeElement.querySelector('a');
expect(link.className).toEqual('');
router.initialNavigation();
advance(f);
expect(link.className).toEqual('active');
})));
it('should set the class on a parent element when the link is active', it('should set the class on a parent element when the link is active',
fakeAsync(inject( fakeAsync(inject(

View File

@ -7,8 +7,7 @@
*/ */
import {Location, LocationStrategy} from '@angular/common'; import {Location, LocationStrategy} from '@angular/common';
import {SpyLocation} from '@angular/common/testing'; import {MockLocationStrategy, SpyLocation} from '@angular/common/testing';
import {MockLocationStrategy} from '@angular/common/testing/mock_location_strategy';
import {AppModule, AppModuleFactory, AppModuleFactoryLoader, Compiler, ComponentResolver, Injectable, Injector} from '@angular/core'; import {AppModule, AppModuleFactory, AppModuleFactoryLoader, Compiler, ComponentResolver, Injectable, Injector} from '@angular/core';
import {Router, RouterOutletMap, Routes, UrlSerializer} from '../index'; import {Router, RouterOutletMap, Routes, UrlSerializer} from '../index';
@ -16,6 +15,7 @@ import {ROUTES} from '../src/router_config_loader';
import {ROUTER_DIRECTIVES, ROUTER_PROVIDERS} from '../src/router_module'; import {ROUTER_DIRECTIVES, ROUTER_PROVIDERS} from '../src/router_module';
/** /**
* A spy for {@link AppModuleFactoryLoader} that allows tests to simulate the loading of app module * A spy for {@link AppModuleFactoryLoader} that allows tests to simulate the loading of app module
* factories. * factories.

View File

@ -10,6 +10,7 @@
"paths": { "paths": {
"@angular/core": ["../../../dist/packages-dist/core"], "@angular/core": ["../../../dist/packages-dist/core"],
"@angular/common": ["../../../dist/packages-dist/common"], "@angular/common": ["../../../dist/packages-dist/common"],
"@angular/common/testing": ["../../../dist/packages-dist/common/testing"],
"@angular/compiler": ["../../../dist/packages-dist/compiler"], "@angular/compiler": ["../../../dist/packages-dist/compiler"],
"@angular/platform-browser": ["../../../dist/packages-dist/platform-browser"], "@angular/platform-browser": ["../../../dist/packages-dist/platform-browser"],
"@angular/platform-browser-dynamic": ["../../../dist/packages-dist/platform-browser-dynamic"] "@angular/platform-browser-dynamic": ["../../../dist/packages-dist/platform-browser-dynamic"]

View File

@ -10,6 +10,7 @@
"paths": { "paths": {
"@angular/core": ["../../../dist/packages-dist/core"], "@angular/core": ["../../../dist/packages-dist/core"],
"@angular/common": ["../../../dist/packages-dist/common"], "@angular/common": ["../../../dist/packages-dist/common"],
"@angular/common/testing": ["../../../dist/packages-dist/common/testing"],
"@angular/compiler": ["../../../dist/packages-dist/compiler"], "@angular/compiler": ["../../../dist/packages-dist/compiler"],
"@angular/platform-browser": ["../../../dist/packages-dist/platform-browser"], "@angular/platform-browser": ["../../../dist/packages-dist/platform-browser"],
"@angular/platform-browser-dynamic": ["../../../dist/packages-dist/platform-browser-dynamic"] "@angular/platform-browser-dynamic": ["../../../dist/packages-dist/platform-browser-dynamic"]

View File

@ -1,3 +1,21 @@
/** @stable */
export declare class MockLocationStrategy extends LocationStrategy {
internalBaseHref: string;
internalPath: string;
internalTitle: string;
urlChanges: string[];
constructor();
back(): void;
forward(): void;
getBaseHref(): string;
onPopState(fn: (value: any) => void): void;
path(includeHash?: boolean): string;
prepareExternalUrl(internal: string): string;
pushState(ctx: any, title: string, path: string, query: string): void;
replaceState(ctx: any, title: string, path: string, query: string): void;
simulatePopState(url: string): void;
}
/** @experimental */ /** @experimental */
export declare class SpyLocation implements Location { export declare class SpyLocation implements Location {
urlChanges: string[]; urlChanges: string[];

View File

@ -82,6 +82,8 @@ export declare class NavigationError {
/** @experimental */ /** @experimental */
export interface NavigationExtras { export interface NavigationExtras {
fragment?: string; fragment?: string;
preserveFragment?: boolean;
preserveQueryParams?: boolean;
queryParams?: Params; queryParams?: Params;
relativeTo?: ActivatedRoute; relativeTo?: ActivatedRoute;
} }
@ -130,10 +132,11 @@ export interface Route {
/** @stable */ /** @stable */
export declare class Router { export declare class Router {
events: Observable<Event>; events: Observable<Event>;
/** @experimental */ navigated: boolean;
routerState: RouterState; routerState: RouterState;
url: string; url: string;
constructor(rootComponentType: Type, resolver: ComponentResolver, urlSerializer: UrlSerializer, outletMap: RouterOutletMap, location: Location, injector: Injector, loader: AppModuleFactoryLoader, config: Routes); constructor(rootComponentType: Type, resolver: ComponentResolver, urlSerializer: UrlSerializer, outletMap: RouterOutletMap, location: Location, injector: Injector, loader: AppModuleFactoryLoader, config: Routes);
createUrlTree(commands: any[], {relativeTo, queryParams, fragment}?: NavigationExtras): UrlTree; createUrlTree(commands: any[], {relativeTo, queryParams, fragment, preserveQueryParams, preserveFragment}?: NavigationExtras): UrlTree;
dispose(): void; dispose(): void;
initialNavigation(): void; initialNavigation(): void;
navigate(commands: any[], extras?: NavigationExtras): Promise<boolean>; navigate(commands: any[], extras?: NavigationExtras): Promise<boolean>;
@ -152,6 +155,8 @@ export declare type RouterConfig = Route[];
/** @stable */ /** @stable */
export declare class RouterLink { export declare class RouterLink {
fragment: string; fragment: string;
preserveFragment: boolean;
preserveQueryParams: boolean;
queryParams: { queryParams: {
[k: string]: any; [k: string]: any;
}; };
@ -176,10 +181,16 @@ export declare class RouterLinkActive implements OnChanges, OnDestroy, AfterCont
export declare class RouterLinkWithHref implements OnChanges, OnDestroy { export declare class RouterLinkWithHref implements OnChanges, OnDestroy {
fragment: string; fragment: string;
href: string; href: string;
preserveFragment: boolean;
preserveQueryParams: boolean;
queryParams: { queryParams: {
[k: string]: any; [k: string]: any;
}; };
routerLink: any[] | string; routerLink: any[] | string;
routerLinkOptions: {
preserveQueryParams: boolean;
preserveFragment: boolean;
};
target: string; target: string;
urlTree: UrlTree; urlTree: UrlTree;
ngOnChanges(changes: {}): any; ngOnChanges(changes: {}): any;