Routes
=
> app.module.ts
```typescript=
import { Routes } from '@angular/router';
// declare routes
const appRoutes: Routes =[
{ path: '', component: HomeComponent }
{ path: 'users', component: UsersComponent }
{ path: 'servers', component: ServersComponent }
//in case we want the opening page to redirect to servers - we will do:
{ path: '', redirectTo: '/servers', pathMatch: 'full' }
// pathMatch must be added or else this cause an error:
// because '' matches every route
]
// register the routes to our angular app
imports: [
RouterModule.forRoot(appRoutes)
]
```
when `localhost:4200/users` reached, `UserComponent` is called
this register the routes to our angular app
> template.html
```htmlmixed=
<li role="presentation">
<a
routerLink="/"
routerLinkActive="active"
[routerLinkActiveOptions]="{exact: true}"
> Home </a> </li>
<li role="presentation">
<a
routerLink="/users"
routerLinkActive="active"
> Users </a> </li>
<li role="presentation">
<a
[routerLink]="['/users','10','Anna']" // `/users/10/Anna`
routerLinkActive="active"
> Users </a> </li>
<router-outlet> </router-outlet>
```
`routerLink` - Avoid reloading on each route activation
`routerLinkActive` - Mark as active only when contains this path
`routerLinkActiveOptions` - only at this exact path `/` mark as active
Trigger route from within our code - load a route automatically on button click
-
1. Inject router into the component
2. Call a function from the button
```htmlmixed=
<button class="btn btn-primary" (click)="onLoadServers">
Load Servers
</button>
```
```typescript=
import { Router, ActivatedRoute } from `@angular/router`;
constructor(
private router: Router
private route: ActivatedRoute) { }
onLoadServers() {
this.router.navigate(['/servers']);
// absolute path. navigate takes an array of elements in the path
this.router.navigate(['servers'], {relativeTo: this.route});
// let navigate know on what route it is currently in
// reload the page
// results with /servers/servers
}
```
Passing parameter to Routes
-
> app.module
```typescript=
{ path: 'users/:id/:name', component: UsersComponent }
// dynamic id, name
```
> app.component.ts
```typescript=
ngOnInit() {
this.user = {
id: this.route.snapshot.params[id],
name: this.route.snapshot.params[name]
// retrieve id and name into component dynamically from route path
}
// Or with + to convert string from route to number
const id =+this.route.snapshot.params['id'];
this.user = this.UserService.getUser(id);
// observer - deal with asynchronous events and
// be fired whenever new data is sent to this route
// not executed on ngOnInit
this.route.params.subscribe(
(params: Params) => {
this.user.id = params['id'];
this.user.name = params['name'];
}
);
}
```
* If we add our own observer --> we need to unsubscribe via onDistroy (on course)
Query parameters, Fragment (Pass & retrieve)
-
`localhost:4200/users/1/Max?mode=editing#loading`
Route to an edit page and decide if allowed
### Pass
```typescript
{ path: 'servers/:id/edit', component: EditServerComponent }
```
```htmlmixed
<a
[routerLink]="['/servers', server.id,'edit']"
[queryParams]="{allowEdit: '1'}"
fragment="loading"
routerLinkActive="active"
> Edit Server </a> </li>
```
### Retrieve
> app.component.ts
>
```typescript
this.route.snapshot.queryParams;
this.route.queryParams.subscribe();
```
### Nested Router
load menu next to current menu
```typescript=
const appRoutes: Routes =[
{ path: 'servers', component: ServersComponent, children: [
{ path: '/:id', component: ServersComponent }
{ path: '/:id/edit', component: EditServersComponent }
] }
]
```
```htmlmixed=
<router-outlet></router-outlet>
// instead of app-servers, app-servers-edit components
```
Wildcard Route
-
For Routes we don't have - 404/Redirect page
```typescript=
{ path: 'page-not-found', component: PageNotFoundComponent }
{ path: '**', redirectTo: '/page-not-found' }
```
`**` always put last in Routes
Cleaner code
-
Creatring a router mdoule and remove routing from app.module
> app.module
```typescript=
import { AppRoutingModule } from './app-routing.module.ts';
imports: [
AppRoutingModule
]
```
> app-routing.module.ts
```typescript=
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import {HomeComponent } from '/home/home.component';
// import all Components
const appRoutes: Routes =[
{ path: '', component: HomeComponent }
{ path: 'users', component: UsersComponent }
{ path: 'servers', component: ServersComponent }
]
@NgModule({
imports: [
RouterModule.forRoot(appRoutes)
],
exports: [RouterModule]
})
export class AppRoutingModule {
}
```
Guards
-
Enables access to routes only for logined users
> auth-guard.service.ts
(dont forget importing to `app.module.ts`)
```typescript=
import {
CanActivate,
ActivatedRouterSnapshot,
RouterStateSanpshot } from `@angular/core`;
import { AuthService } from './auth.service';
@Injectable()
export class AuthGuard implements CanActivate, CanActivateChild {
constructor(
private authService: AuthService
private router: Router) {}
canActivate(route: ActivateRouteSnapshot,
state: RouterStateSnapshot):
Observable<boolean> | Promise<boolean> | boolean {
return his.authService.isAuthenticated().then(
(authenticated: boolen) => {
if (authenticated)
return true;
else
this.router.navigate(['/']);
// navigate home or just return false
});
}
// To guard only the childs of this route
canActivateChild(route: ActivateRouteSnapshot,
state: RouterStateSnapshot):
Observable<boolean> | Promise<boolean> | boolean {
return this.canActivate(route,state);
}
```
> auth.service.ts (dummy service - in realworld, here we access the server)
```typescript=
export class AuthService {
loggedIn = false;
isAuthenticated() {
const promise = new Promise(
(resolve, reject) => {
setTimeout( () => {
resolve(this.loggedIn);
}, 800); //ms
}
);
return promise;
}
login(){
this.loggedIn = true;
}
logout(){
this.loggedIn = false;
}
}
```
> app-routing.module.ts
```typescript=
// Inside the array, enter all guards should be used on this route
{ path: 'users', canActivate: [AuthGuard], component: UsersComponent }
// Guard childs, add this in parent
{ path: 'users', canActivateChild: [AuthGuard], component: UsersComponent }
```
Enable Login with AuthService
-
> home.component.html
```htmlmixed=
<button class="btn btn-default" (click)="onLogin()">
Login
</button>
<button class="btn btn-default" (click)="onLogout()">
Logout
</button>
```
> home.component.ts
```typescript=
constructor(... private authService: AuthService) { }
onLogin() {
this.authService.login();
}
onLogout() {
this.authService.logout();
}
```
no visual indication yet, but content is now available only for logged in users.
Control wheter you're allowed to leave a Route
-
like leaving without saving/submitting.
> edit.server.ts
```typescript=
import { Observeable } from 'rxjs/Observable';
export class EditServerComponent implements CanComponentDeactivate {
allowEdit = false;
changesSaved = false;
onUpdateServer(){
this.changesSaved = true;
}
canDeactivate() {
if (!this.allowEdit)
return true;
// onInit we binded serverName with server.name
// serverName: bound property to input
// server.name: previous value
if (this.serverName !== this.server.name ||
this.serverStatus !== this.server.status &&
!this.changesSaved)
return confirm('Do you want to discard changes?');
else
return true;
}
}
```
**Add a new File**
> can-deactivate-guard.service.ts
```typescript=
export interface CanComponentDeactivate {
canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
}
//interface, therefore needs to be implmeneted.
export class CanDeactivateGuard implements
CanDeacivate<CanComponentDeactivate>{
canDeactivate(component: CanComponentDeactivate,
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot,
nextState?: RouterStateSnapshot):
// nextState? - Optional argument
// Function's returned value is the below
Observable<boolean> | Promise<boolean> | boolean {
return component.canDeactivate();
}
}
```
> app-routing.module.ts
```typescript=
{ path: 'users', component: UsersComponent,
canDeactivate: [CanDeactivateGuard]}
```
and don't forget to provide service in `app.module.ts`
Get dynamic data to a Route
-
(Previeus chapter deals with static data)
* Resolver (service) - run some code before the Route is rendered
* Preload some data from the server before accesing/displaying route
* Alternative - Load instantly and present a spinner.
* Create a new service to retrieve data with a resolver instead of via params.
* Important when using asynchronis data
> server-resolver.service.ts
```typescript=
import { Resolve } from '@angular/router';
interface Server {
id: number;
name: string;
status: string;
}
@Injectable
export class ServerResolver implements Resolve<Server> {
constructor(private serversService: ServersService)
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
Observable<Server> | Promise<Server> | Server {
//'+' - cast to integer
return this.serversService.getServer(+route.params['id'])
}
}
```
> app-routing.module.ts
```typescript=
{ path: 'users', component: UsersComponent,
resolve: {server: ServerResolver} }
```
this will map the data it gives us back
> server.component.ts
```typescript=
ngOnInit() {
this.route.data.subscribe(
(data: Data) => {
this.server = data['server']
}
);
}
```
Hash Configuration
-
On real server, the URL is parsed by the web server first, not by angular.
Adding `#` to the URL - `localhost:4200/#/servers`
informs the server to parse only the first part of the URL `localhost:4200/`
and let Angular parse the rest `/servers`
> app-routing.module.ts
```typescript=
imports: [
RouterModule.forRoot(appRoutes, {useHash: true})
]
```
<!-- <hr> -->
### Avoid reloading the page when clicking on elements
1. Remove `href="#"` - this calls the web server
2. Add
```CSS
style="cursor: pointer;"
```
to view clickable when hovering over the element.
### New item / Edit item page
> recipe-edit.component.ts
```typescript=
export class RecipeEditComponent implements OnInit {
id: number;
editMode = false;
constructor(private route: ActivatedRoute) { }
ngOnInit() {
this.route.params
.subscribe(
(params: Params) => {
// retrieve id dynamically
this.id = +params['id'];
// if route has id parameter - then we are in Edit mode
// else: we are creating a New item
this.editMode = params['id'] != null;
}
);
}
}
```
> app-routing.module.ts
```typescript=
{ path: 'new', component: RecipeEditComponent },
{ path: ':id', component: RecipeDetailComponent },
```
* Must put `new` before `:id` to avoid error - Routing does first match prefix
(`localhost:4200/recipe/new` will fail because `id=new` not identified as valid id)
* Both `New` and `Edit` are handled within same component - can be tell apart by `editMode` property.