Routing Basics (DRAFT) 5.1


If you notice any issues with this page, please report them.

Milestone

For this first milestone, you’ll create a rudimentary version of the app that navigates between two (placeholder) views.

App in action

App setup

Create a new project named router_example, and populate it as described in Setup for Development.

Add angular_router

Add the router and forms packages as pubspec dependencies:

pubspec.yaml (dependencies)

dependencies:
  angular: ^5.0.0
  angular_forms: ^2.0.0
  angular_router: ^2.0.0-alpha+19

Add router providers

To tell Angular that your app uses the router, specify routerProvidersHash in your app’s bootstrap function:

web/main.dart

import 'package:angular/angular.dart';
import 'package:angular_router/angular_router.dart';
import 'package:router_example/app_component.template.dart' as ng;

import 'main.template.dart' as self;

const useHashLS = false;
@GenerateInjector(
  routerProvidersHash, // You can use routerProviders in production
)
final InjectorFactory injector = self.injector$Injector;

void main() {
  runApp(ng.AppComponentNgFactory, createInjector: injector);
}

Which location strategy to use

The default LocationStrategy is PathLocationStrategy so, in production, you can use routerProviders. During development, it is more convenient to use routerProvidersHash (which has a HashLocationStrategy provider override for LocationStrategy) because webdev serve does not support deep linking. See the Router Appendix on LocationStrategy and browser URL styles for details.

Set the base href

Add a <base href> element to the app’s index.html. The browser uses the base href value to prefix relative URLs when referencing CSS files, scripts, and images. The router uses the base href value to prefix relative URLs when navigating.

The starter app sets the <base> element dynamically, so that sample apps built from it can be run and tested during development using any of the officially recommended tools:

web/index.html (base-href)

<script>
  // WARNING: DO NOT set the <base href> like this in production!
  // Details: https://webdev.dartlang.org/angular/guide/router
  (function () {
    var m = document.location.pathname.match(/^(\/[-\w]+)+\/web($|\/)/);
    document.write('<base href="' + (m ? m[0] : '/') + '" />');
  }());
</script>

For a production build, replace the <script> with a <base> element where the href is set to your app’s root path. If the path is empty, then use "/":

<head>
  <base href="/">
  ...
</head>

You can also statically set the <base href> during development. When serving from the command line, use href="/". When running apps from WebStorm, use href="my_project/web/", where my_project is the name of your project.

<base href="/my_project/web/">

Create crisis and hero list components

Create the following skeletal components under lib/src. You’ll be using these as router navigation targets in the next section:

import 'package:angular/angular.dart'; @Component( selector: 'crises', template: ''' <h2>Crisis Center</h2> <p>Get your crisis here</p> ''', ) class CrisisListComponent {} import 'package:angular/angular.dart'; @Component( selector: 'my-heroes', template: ''' <h2>Heroes</h2> <p>Get your heroes here</p> ''', ) class HeroListComponent {}

Routes

Routes tell the router which views to display when a user clicks a link or pastes a URL into the browser address bar.

Route paths

First define a route path (RoutePath) for each of the app’s views:

lib/src/route_paths.dart

import 'package:angular_router/angular_router.dart';

class RoutePaths {
  static final crises = RoutePath(path: 'crises');
  static final heroes = RoutePath(path: 'heroes');
}

Route definitions

The router coordinates app navigation based on a list of route defintions. A route definition (RouteDefinition) associates a route path with a component. The component is responsible for handling navigation to the path, and rendering of the associated view.

Define the following routes:

lib/src/routes.dart

import 'package:angular_router/angular_router.dart';

import 'crisis_list_component.template.dart' as crisis_list_template;
import 'hero_list_component.template.dart' as hero_list_template;
import 'route_paths.dart';

export 'route_paths.dart';

class Routes {
  static final crises = RouteDefinition(
    routePath: RoutePaths.crises,
    component: crisis_list_template.CrisisListComponentNgFactory,
  );

  static final heroes = RouteDefinition(
    routePath: RoutePaths.heroes,
    component: hero_list_template.HeroListComponentNgFactory,
  );

  static final all = <RouteDefinition>[
    crises,
    heroes,
  ];
}

AppComponent navigation

Next, you’ll edit AppComponent so that it has a title, a navigation bar with two links, and a router outlet where the router swaps views on and off the page. This is what it will look like:

Shell

Update the AppComponent code to the following:

import 'package:angular/angular.dart';
import 'package:angular_router/angular_router.dart';

import 'src/routes.dart';

@Component(
  selector: 'my-app',
  template: '''
    <h1>Angular Router</h1>
    <nav>
      <a [routerLink]="RoutePaths.crises.toUrl()"
         [routerLinkActive]="'active-route'">Crisis Center</a>
      <a [routerLink]="RoutePaths.heroes.toUrl()"
         [routerLinkActive]="'active-route'">Heroes</a>
    </nav>
    <router-outlet [routes]="Routes.all"></router-outlet>
  ''',
  styles: ['.active-route {color: #039be5}'],
  directives: [routerDirectives],
  exports: [RoutePaths, Routes],
)
class AppComponent {}

RouterOutlet

RouterOutlet is one of the routerDirectives. To use a router directive like RouterOutlet within a component, add it individually to the component’s directives list, or for convenience you can add the routerDirectives list:

template: '''
  ...
  <router-outlet [routes]="Routes.all"></router-outlet>
''',
directives: [routerDirectives],

In the DOM, the router diplays views (for the routes bound to the routes input property) by inserting view elements as siblings immediately after <router-outlet>.

Above the outlet, within anchor tags, you see property bindings to RouterLink directives. Each router link is bound to a route path.

<nav>
  <a [routerLink]="RoutePaths.crises.toUrl()"
     [routerLinkActive]="'active-route'">Crisis Center</a>
  <a [routerLink]="RoutePaths.heroes.toUrl()"
     [routerLinkActive]="'active-route'">Heroes</a>
</nav>
<router-outlet [routes]="Routes.all"></router-outlet>

Set the RouterLink routerLinkActive property to the CSS class that the router will apply to the element when its route is active.

template: '''...
    <a [routerLink]="RoutePaths.crises.toUrl()"
       [routerLinkActive]="'active-route'">Crisis Center</a>
    <a [routerLink]="RoutePaths.heroes.toUrl()"
       [routerLinkActive]="'active-route'">Heroes</a>
...''',
styles: ['.active-route {color: #039be5}'],

App code

You’ve got a very basic, navigating app, one that can switch between two views when the user clicks a link. The app’s structure looks like this:

  • router_example
    • lib
      • app_component.dart
      • src
        • crisis_list_component.dart
        • hero_list_component.dart
        • route_paths.dart
        • routes.dart
    • web
      • index.html
      • main.dart
      • styles.css

Here are the files discussed in this milestone

import 'package:angular/angular.dart'; import 'package:angular_router/angular_router.dart'; import 'src/routes.dart'; @Component( selector: 'my-app', template: ''' <h1>Angular Router</h1> <nav> <a [routerLink]="RoutePaths.crises.toUrl()" [routerLinkActive]="'active-route'">Crisis Center</a> <a [routerLink]="RoutePaths.heroes.toUrl()" [routerLinkActive]="'active-route'">Heroes</a> </nav> <router-outlet [routes]="Routes.all"></router-outlet> ''', styles: ['.active-route {color: #039be5}'], directives: [routerDirectives], exports: [RoutePaths, Routes], ) class AppComponent {} import 'package:angular_router/angular_router.dart'; class RoutePaths { static final crises = RoutePath(path: 'crises'); static final heroes = RoutePath(path: 'heroes'); } import 'package:angular_router/angular_router.dart'; import 'crisis_list_component.template.dart' as crisis_list_template; import 'hero_list_component.template.dart' as hero_list_template; import 'route_paths.template.dart'; export 'route_paths.template.dart'; class Routes { static final crises = RouteDefinition( routePath: RoutePaths.crises, component: crisis_list_template.CrisisListComponentNgFactory, ); static final heroes = RouteDefinition( routePath: RoutePaths.heroes, component: hero_list_template.HeroListComponentNgFactory, ); static final all = <RouteDefinition>[ crises, heroes, ]; } import 'package:angular/angular.dart'; @Component( selector: 'crises', template: ''' <h2>Crisis Center</h2> <p>Get your crisis here</p> ''', ) class CrisisListComponent {} import 'package:angular/angular.dart'; @Component( selector: 'my-heroes', template: ''' <h2>Heroes</h2> <p>Get your heroes here</p> ''', ) class HeroListComponent {} <html> <head> <script> // WARNING: DO NOT set the <base href> like this in production! // Details: https://webdev.dartlang.org/angular/guide/router (function () { var m = document.location.pathname.match(/^(\/[-\w]+)+\/web($|\/)/); document.write('<base href="' + (m ? m[0] : '/') + '" />'); }()); </script> <title>Angular Router</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="styles.css"> <link rel="icon" type="image/png" href="favicon.png"> <script defer src="main.dart.js"></script> </head> <body> <my-app>Loading...</my-app> </body> </html> import 'package:angular/angular.dart'; import 'package:angular_router/angular_router.dart'; import 'package:router_example/app_component.template.dart' as ng; import 'main.template.dart' as self; const useHashLS = false; @GenerateInjector( routerProvidersHash, // You can use routerProviders in production ) final InjectorFactory injector = self.injector$Injector; void main() { runApp(ng.AppComponentNgFactory, createInjector: injector); }