Search

#### Docs

- Guide
  - Overview
  - Setup
  - Learning Angular
  - Architecture
  - Displaying Data
  - User Input
  - Forms
  - Dependency Injection
  - Template Syntax
- Tutorial
  - Introduction
  - 0. The Starter App
  - 1. The Hero Editor
  - 2. Master/Detail
  - 3. Multiple Components
  - 4. Services
  - 5. Routing
  - 6. HTTP
- Advanced
  - Attribute Directives
  - Component Styles
  - Deployment
  - Hierarchical Injectors
  - HTTP Client
  - Lifecycle Hooks
  - Pipes
  - Router
  - Security
  - Structural Directives
  - Testing

#### Components

- Components Gallery

#### Resources

- Cheat Sheet
- Glossary
- FAQ
- Tools
- Community and Support

#### Reference

- Videos
- API Documentation

new_releases We're currently in the progress of migrating this site from Angular version 5.3.1 to 7.0.0. Package versions might be out-of-date or inconsistent! Visit the repo for more info.

# Imperative Navigation and Route Parameters (DRAFT) 6.0

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

<?code-excerpt path-base=”examples/ng/doc/router”?> <?code-excerpt replace=”/_\d((.template)?.dart)/$1/g”?>

Milestone

You’ve seen how to navigate using the `RouterLink` directive, now you’ll learn how to

- Organize the app into features, or feature groups
- Navigate imperatively from one component to another
- Pass required and optional information in route parameters

This example has capabilities very similar to the Tour of Heroes Tutorial Part 5, and you’ll be copying much of the code from there.

Here’s how the user will experience this version of the app:

A typical app has multiple features or feature groups, each dedicated to a particular business purpose.

While you could continue to add files to the `lib/src` folder, developers generally prefer to organize their apps so that most, if not all, files implementing a feature are grouped into a separate folder.

You are about to break up the app into different feature groups, each with its own concerns.

## Heroes functionality

Follow these steps:

1. Create a `hero` folder under `lib/src` — you’ll be adding files implementing hero management there.
2. Copy the following files from toh-5 `lib/src` into the new `hero` folder, adjusting import paths as necessary:
   - `hero.dart`
   - `hero_component.*`, that is, the CSS, Dart and HTML files
   - `hero_service.dart`
   - `hero_list_component.*`, the CSS, Dart and HTML files
   - `mock_heroes.dart`
3. In `hero_list_component.html`, rename the `<ul>` element class to `items` (from `heroes`).
4. In `app_component.dart`, import and then add `ClassProvider(HeroService)` to the `providers` list so that the service is available everywhere in the app.
5. In `src/routes.dart`, adjust the hero component import path because the new file is under `heroes`.
6. Delete the old `lib/src/hero_list_component.dart`.

open_in_browser Refresh the browser and you should see heroes in the heroes list. You can select heroes, but not yet view hero details. You’ll address that next.

## Hero routing requirements

The heroes feature has two interacting components, the hero list and the hero detail. The list view is self-sufficient; you navigate to it, it gets a list of heroes and displays them.

The detail view is different. It displays a particular hero. It can’t know which hero to show on its own. That information must come from outside.

When the user selects a hero from the list, the app should navigate to the detail view and show that hero. You tell the detail view which hero to display by including the selected hero’s ID in the route URL.

## Hero detail route

Create a hero route path and route definition like you did in the Tour of Heroes Tutorial Part 5. Adding the following route path and `getId()` helper function:

<?code-excerpt “lib/src/route_paths.dart (hero)” replace=”/hero\b/[!$&!]/g” title?>

      const idParam = 'id';

      class RoutePaths {
        // ···
        static final [!hero!] = RoutePath(path: '${heroes.path}/:$idParam');
      }

      int getId(Map<String, String> parameters) {
        final id = parameters[idParam];
        return id == null ? null : int.tryParse(id);
      }

After including an appropriate import for `HeroComponent`, add the following route definition:

<?code-excerpt “lib/src/routes_3.dart (hero)” replace=”/import.*|RouteDef[\S\s]+?);|\bhero,/[!$&!]/g” title?>

      [!import 'hero/hero_component.template.dart' as hero_template;!]
      // ···
      class Routes {
        // ···
        static final hero = [!RouteDefinition(
          routePath: RoutePaths.hero,
          component: hero_template.HeroComponentNgFactory,
        );!]

        static final all = <RouteDefinition>[
          // ···
          [!hero,!]
          // ···
        ];
      }

## Route definition with a parameter

Notice that the hero path ends with `:id` (the result of interpolating `idParam` in the path `'${heroes.path}/:$idParam'`). That creates a slot in the path for a route parameter. In this case, the router will insert the ID of a hero into that slot.

If you tell the router to navigate to the detail component and display “Magneta” (having ID 15), you’d expect a hero ID to appear in the browser URL like this: localhost:8080/#/heroes/15.

If a user enters that URL into the browser address bar, the router should recognize the pattern and go to the same “Magneta” detail view.

## Navigating imperatively

Users won’t navigate to the hero component by clicking a link so you won’t be adding a new `RouterLink` anchor tag to the shell. Instead, when the user clicks a hero in the list, you’ll ask the router to navigate to the hero view for the selected hero.

Make the following changes to the hero list component template:

- Drop “My” from the `<h2>` element in the template so it reads “Heroes”.
- Drop the `<div *ngIf="selectedHero != null">...</div>` element.

<?code-excerpt “lib/src/hero/hero_list_component.html” title?>

      <h2>Heroes</h2>
      <ul class="items">
        <li *ngFor="let hero of heroes"
          [class.selected]="hero === selected"
          (click)="onSelect(hero)">
          <span class="badge">{{hero.id}}</span> {{hero.name}}
        </li>
      </ul>

The template has an `*ngFor` element that you’ve seen before. There’s a `(click)` event binding to the component’s `onSelect()` method to which you’ll add a call to `gotoDetail()`.

But first, make `gotoDetail()` private by prefixing the name with an underscore, since it won’t be used in the template anymore:

<?code-excerpt “lib/src/hero/hero_list_component.dart (_gotoDetail)” replace=”/int id//g; /_gotoDetail/[!$&!]/g” title?>

      Future<NavigationResult> [!_gotoDetail!]() =>
          _router.navigate(_heroUrl(id));

The `onSelect()` method is currently defined as follows:

<?code-excerpt “lib/src/hero/hero_list_component_0.dart (onSelect)”?>

      void onSelect(Hero hero) => selected = hero;

By selecting a hero in the hero list, the router will navigate away from the hero list view to a hero view. Because of this, you don’t need to record the selected hero in the hero list component. Just navigate to the selected hero’s view.

First parameterize `_gotoDetail()` with a hero ID, then update the `onSelect()` method as follows:

<?code-excerpt “lib/src/hero/hero_list_component.dart (onSelect & _gotoDetail)” replace=”/_gotoDetail(.*?)/[!$&!]/g” title?>

      void onSelect(Hero hero) => [!_gotoDetail(hero.id)!];
      // ···
      Future<NavigationResult> [!_gotoDetail(int id)!] =>
          _router.navigate(_heroUrl(id));

open_in_browser Refresh the browser and select a hero. The app navigates to the hero view.

## Navigating back and selecting a hero

Use a route parameter to specify a required parameter within the route path, for example, when navigating to the detail for the hero with ID 15: /heroes/15.

You sometimes want to add optional information to a route request. For example, when returning to the heroes list from the hero detail view, it would be nice if the viewed hero was preselected in the list:

You can achieve this by passing an optional ID as a query parameter when navigating back to the hero list. You’ll address that next.

### Location service back()

The hero detail’s Back button has an event binding to the goBack() method, which currently navigates backward one step in the browser’s history stack using the `Location` service:

<?code-excerpt “lib/src/hero/hero_component_0.dart (goBack)”?>

      void goBack() => _location.back();

### Router navigation

You’ll be implementing `goBack()` using the router rather than the location service, so you can replace the `Location` field by `Router _router`, initialized in the constructor:

<?code-excerpt “lib/src/hero/hero_component.dart (router)” title?>

      final Router _router;

      HeroComponent(this._heroService, this._router);

Use the router’s `navigate()` method like you did previously in `HeroListComponent`, but encode the hero ID as a query parameter instead:

<?code-excerpt “lib/src/hero/hero_component.dart (goBack)” title?>

      Future<NavigationResult> goBack() => _router.navigate(
          RoutePaths.heroes.toUrl(),
          NavigationParams(queryParameters: {idParam: '${hero.id}'}));

open_in_browser Refresh the browser, select a hero and then click the Back button to return to the heroes list. Notice that the URL now ends with a query parameter like this: `/#/heroes?id=15`.

The router can also encode parameters using the matrix URL notation, such as `/heroes;id=15;foo=bar`. You’ll see this later, once the crises feature is fully fleshed out.

### Extracting query parameters

Despite the URL query parameter, the hero isn’t selected. Using the hero component as a model, make the following changes to the hero list component:

- Implement `OnActivate` rather than `OnNgInit`.
- Replace `ngOnInit()` by `onActivate()`, a router lifecycle hook. Read about other router lifecycle hooks in Milestone 5.
- Fetch the hero ID from the router state `queryParameters`.

<?code-excerpt “lib/src/hero/hero_list_component.dart (onActivate)” title?>

      @override
      void onActivate(_, RouterState current) async {
        await _getHeroes();
        selected = _select(current);
      }

      Hero _select(RouterState routerState) {
        final id = getId(routerState.queryParameters);
        return id == null
            ? null
            : heroes.firstWhere((e) => e.id == id, orElse: () => null);
      }

open_in_browser Refresh the browser, select a hero and then click the Back button to return to the heroes list. The previously selected hero will be selected again. Try deep linking to another selected hero by visiting this URL: localhost:8080/#/hero?id=15.

## App code

After these changes, the folder structure looks like this:

- router_example
  - lib
    - app_component.dart
    - src
      - crisis_list_component.dart
      - hero
        - hero.dart
        - hero_component.{css,dart,html}
        - hero_service.dart
        - hero_list_component.{css,dart,html}
        - mock_heroes.dart
      - route_paths.dart
      - routes.dart
  - web
    - index.html
    - main.dart
    - styles.css

Here are the relevant files for this version of the sample app: