markdown Angular2 Snippets - 路由

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了markdown Angular2 Snippets - 路由相关的知识,希望对你有一定的参考价值。


## Routing

### Registering our routes

* Register our routes in **app.module.ts** 

```javascript
// import necessary modules
import { Routes, RouterModule } from '@angular/router';

// declare our routes
const appRoutes: Routes = [ //this is the type that informs angular / gives structure to our routes. You don't necessary need to add it but it's a good practice
  //now we define an array of objects:
  {
    path: 'users' //is the URL part, you don't need to add the slash! ex. localhost:4200/users
    component: UsersComponent //inform angular that when this path is reached, which component has to be loaded. So when configuring your components, take care of which components will act as 'pages' or 'views', so these components have all the necessary content
  },
  {
    path: '', //you'll normally define an 'empty' path that acts as a index for when localhost:4200/ for example is reached
    component: HomeComponent
  }
];

@NgModule({
  declarations: [...],
  imports: [
    BrowserModule,
    ...
    RouterModule.forRoot(appRoutes) //add ther router functionality to our app, and register (with .forRoot) some routes for our main application
  ],
  ...
})
```

#### Outsourcing the route configuration to an external module

Create `app-routing.module.ts`:

```javascript
imports
...

const appRoutes: Routes = [
  ...
];

@NgModule({
  imports: [
    RouterModule.forRoot(appRoutes)
  ],
  declarations: [], // you don't need to add declarations here because the components are already declared in your app.module.ts
  exports: [RouterModule] // we define here: if I want to add this module to the imports of another module, what should be accesible to this module?
})
export class AppRoutingModule { }
```

* **REMEMBER** to import `AppRoutingModule` in your main app.module.


### Template modifications for routing

* Define some place to render our selected component in **app.component.html**

```javascript
<div class="row">
  <div class="col">
    <router-outlet></router-outlet> //marks the place in our document where we want Angular Router to load the component of the currently selected route
  </div>
</div>
```

* Add navigation:

```javascript
<a routerLink="/servers">Servers</a>
```

or using property binding that allows you to construct more complex paths

```javascript
<a [routerLink]="['/users', 'something']">Users</a> //allows you to specify our individual path elements --all the segments of your path as elements in this array, for example if you have /users/something
```
Add *active* class to the current link:

```html
<a routerLinkActive="active"> 
```

You put here the name of the class we want to add when this path is active. Angular analyses your currently loaded path and checks which links points to a route that uses this path. The problem with empty path '/' is that it is part of all paths, so Home link will be always active.  
To fix this, we can add a special configuration to our `routerLinkActive` directive:

```html
<li routerLinkActive="active"
    [routerLinkActiveOptions]="{exact: true}">
  <a routerLink="/">Home</a>
</li>
```

#### Absolute and relative paths

* Absolute path: `<a routerLink="/servers">`
* Relative path: `<a routerLink="servers">` appends the path fragment to the current path, so if you are already in `localhost:4200/servers` this link will point to `localhost:4200/servers/servers`

The current path depends on **what component we are**. If our navigation is in the root component (app.component.html) we can use relative paths because they will be relative to `localhost:4200/`.

You can navigate in routes as if they where folders:  
* `./servers` is the same than `servers`
* `../servers` goes up a level (removes the current segment of the current path:
  - if you are in `/servers`, will remove `servers` before going into `servers`
  - if you are in `/servers/something`, will remove `servers/something` before going into `servers`


### Navigating programmatically

```html
<button (click)="onLoadServers">Go to servers</button>
```

Unlike the `routerLink`, the `navigate` method doesn't know on which route you're currenly on, we have to tell it:

```javascript
import { Router, ActivatedRoute } from '@angular/router';

constructor(private router: Router, private route: ActivatedRoute) { }

this.router.navigate(['servers'], {relativeTo: this.route}); //relative to which route this route will be loaded (by default, this is always the root)
```
* `ActivatedRoute` simple inject the currently active route --so this will be the route which loaded this component. This route is a complex object that keeps a lot of meta information about the currently active route


### Passing and fetching route parameters (`servers/5/edit`)

* Passing route parameters:

```javascript
const appRoutes: Routes = [ 
  {
    path: 'users/:id/:name', //the : indicates angular that this is a dynamic part of the path. You can pass any parameters that you want
    component: UserComponent 
  }
];
```

Now we compose a link like so:  
```html
<a [routerLink]="['/users', 10, 'Anna']">Load Anna</a>
```

* Fetching route parameters:

```javascript
ngOnInit() {
  this.user = {
    id: this.route.snapshot.params['id'], //you only have acces here to params you have defined as route params
    name: this.route.snapshot.params['name']
  } // this will only work the first time we load the component, but if we change the URL, Angular cleverly doesn't reinstantiate the component

  // in order to react to subsequent changes we need to subscribe:
  this.route.params.subscribe(
    // The difference between 'route.params' and 'route.snapshot.params' is that the first is an Observable. This callback function will be fired when new data is sent through the observable (whenever the parameters change)
    (params: Params) => { // params is an object containing the parameters you defined in the route as properties
      this.user.id = params['id'],
      this.user.name = params['name']
    }
  );
}
``` 
* Of course, you don't need to add the Observable part if you know that the component may never been reloaded from within the component.
* Also, it's important to know that Angular cleans up the subscription you set up here whenever this component gets destroyed.


### Passing and fetching query parameters and fragments (`servers/5/edit?allowEdit=1&something=true#loading`)

* passing from a template link:

```html
<a
  [routerLink]="['/servers', 5, 'edit']"
  [queryParams]="{
    allowEdit: '1'
  }" //queryParams is not a new directive it's just another bindable property of the routerLink directive, where you define key-value for your query parameters
  [fragment]="'loading'" //you can only define one fragment
  ...
</a>
```

* passing programmatically:
```html
<a (click)="onLoadServer(1)">Load server 1</a>
```

```javascript
onLoadServer(id: number) {
  this.router.navigate(['servers', id, 'edit'], {
    queryParams: {
      allowEdit: '1'
    },
    fragment: 'loading'
  });
}
```

* Retrieving query parameters and fragments

```javascript
constructor(private route: ActivatedRoute) {}
...
ngOnInit() {
  console.log(this.route.snapshot.queryParams);
  console.log(this.route.snapshot.fragment);

  // Best approach would be to subscribe, as we've seen for the route params
  this.route.queryParams.subscribe();
  this.route.fragment.subscribe();
}
```

### Passing query parameters when navigating to another route

```javascript
onEdit() {
  this.router.navigate(['edit'], { relativeTo: this.route, queryParamsHandling: 'preserve' });
}
```
* `merge` to add the new query params to the existing ones in the path (or overwrite them)
* `preserve` to maintain the existing query params when navigating to another route


### Setting up nested routes

```javascript
const appRoutes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'users', component: UsersComponent, children: [
    { path: ':id/:name', component: UserComponent }, // users/:id/:name
  ]},
  { path: 'servers', component: ServersComponent, children: [
    { path: ':id', component: ServerComponent },
    { path: ':id/edit', component: EditServerComponent }
  ]},
];
```

Now our current `<router-outlet></router-outlet>` only applies now to the routes in the root level.  
So in our child components we need to add another `<router-outlet></router-outlet>`.  

### Redirecting and wildcard routes

* **ORDER IS IMPORTANT HERE**, MAKE SURE THIS VERY GENERIC ROUTE GOES **AT THE END OF YOUR ROUTE DEFINITIONS**

```javascript
{ path: 'not-found', component: NotFoundComponent },
{ path: '**', redirectTo: '/not-found' } //if you don't want to specify a component to load, you can use a redirection with redirectTo path. With the '**'' wildcard you say, 'catch all the paths you don't know'. 
```

* OTRO EJEMPLO MUY IMPORTANTE de la importancia del orden de las rutas:
```javascript
{ path: ':id', component: RecipeDetailComponent }
{ path: 'new', component: RecipeEditComponent }
// gives an error because is trying to parse 'new' as an id. 
// So the correct order in our declaration is:
{ path: 'new', component: RecipeEditComponent }
{ path: ':id', component: RecipeDetailComponent }
```

#### Redirecting the empty route

By default, Angular matching strategy is `prefix`: checks if the path you entered in the URL does start with the path specified in the route. 
```javascript
{ path: '', redirectTo: '/somewhere-else' } //as every path starts with '', you'll always be redirected.
```

To fix this behavior, you need to change the matching strategy to `full`:
```javascript
{ path: '', redirectTo: '/somewhere-else', pathMatch: 'full' } 
```

### Passing static data to a route

Example: customisable error page where we can pass the error message

* Routes:

```javascript
{ path: 'not-found',
  component: ErrorPageComponent,
  data: { //data property allows you to pass any key/value pairs for any properties you want to pass
    message: 'Page not found!'
  }
},
```

* Component:

```javascript
export class ErrorPageComponent implements OnInit {

  message: String;

  constructor(private route: ActivatedRoute) { }

  ngOnInit() {
    // this.message = this.route.snapshot.data['message'];
    // if you want to subscribe to changes...
    this.route.data.subscribe(
      (data: Data) => {
        this.message = data['message'];
      }
    );
  }

}
```


### Resolving dynamic data with the resolve guard

**Resolver**: a service like `canActivate` or `canDeactivate` which allow us to run some code before a route is rendered.  
The resolver will always render the component (difference with `canActivate`), but it will do some preloading/fetch some data that the component will need later on.  
*Of course the alternative is to render the component, and then in the `onInit` method you can fetch your data.*

* server-resolver.service.ts

```javascript
...
imports...

// we need to have a 'type' for our data
interface Server { // of course would be better to outsource this to a file / model
    id: number;
    name: string;
    status: string;
}

@Injectable()
export class ServerResolver implements Resolve<Server> { // Resolve is a generic type and it will wrap whichever item or data field you will get/fetch (here, a 'server'), so we define its type here (we could use a model, or define it inline)
    constructor(private serversService: ServersService) {}

    resolve( // the Resolve interface requires us to implement a resolve() method. It gets two arguments: the current route and state
        route: ActivatedRouteSnapshot,
        state: RouterStateSnapshot
    ): Observable<Server> | Promise<Server> | Server {
    // one option is to instantly (synchronously) return the data, as we currently do in our services
        return this.serversService.getServer(+route.params['id']); //we need to know the id of the server we want to fetch. This service here will actually run each time we re-render the route, so the snapshot is all we need. Unlike the component itself, this is executed each time, so no need to set up an observable

    // This will work the same way if we return an observable or a promise, for example when fetching data from a server with http request (asynchronously)

    }
}
```

Now in our routes:

```javascript
{ path: ':id',
  component: ServerComponent,
  resolve: { // here we map all the resolves, using key/value parameters for all the resolvers we want to use: name of the property / resolver
    server: ServerResolver // this will now map the data this resolver gives us back, to this server property/object in this to-be-loaded component
  }
},
```

(and don't forget to import `ServerResolver` in our app module)

* server.component.ts

```javascript
ngOnInit() {

   this.route.data.subscribe( //I'm using an observable here because the server can change what we already have on the page (...)
    (data: Data) => {
      this.server = data['server'];
    }
   );
...
```

以上是关于markdown Angular2 Snippets - 路由的主要内容,如果未能解决你的问题,请参考以下文章

markdown [快照] #snippet

markdown Snippet Utile

markdown [查看寻呼机不完整,添加片段示例] #android_snippet #android

Visual Studio Code配置Markdown文档的snippet不生效的解决

markdown Angular2 Snippets - 路由

markdown Angular2 Snippets - Observables