1353 lines
39 KiB
HTML
1353 lines
39 KiB
HTML
<html lang="en"><head></head><body><form id="mainForm" method="post" action="http://plnkr.co/edit/?p=preview" target="_self"><input type="hidden" name="files[app/admin/admin-dashboard.component.ts]" value="import { Component, OnInit } from '@angular/core';
|
|
import { ActivatedRoute } from '@angular/router';
|
|
import { Observable } from 'rxjs/Observable';
|
|
|
|
import { SelectivePreloadingStrategy } from '../selective-preloading-strategy';
|
|
|
|
import 'rxjs/add/operator/map';
|
|
|
|
@Component({
|
|
template: `
|
|
<p>Dashboard</p>
|
|
|
|
<p>Session ID: {{ sessionId | async }}</p>
|
|
<a id="anchor"></a>
|
|
<p>Token: {{ token | async }}</p>
|
|
|
|
Preloaded Modules
|
|
<ul>
|
|
<li *ngFor="let module of modules">{{ module }}</li>
|
|
</ul>
|
|
`
|
|
})
|
|
export class AdminDashboardComponent implements OnInit {
|
|
sessionId: Observable<string>;
|
|
token: Observable<string>;
|
|
modules: string[];
|
|
|
|
constructor(
|
|
private route: ActivatedRoute,
|
|
private preloadStrategy: SelectivePreloadingStrategy
|
|
) {
|
|
this.modules = preloadStrategy.preloadedModules;
|
|
}
|
|
|
|
ngOnInit() {
|
|
// Capture the session ID if available
|
|
this.sessionId = this.route
|
|
.queryParams
|
|
.map(params => params['session_id'] || 'None');
|
|
|
|
// Capture the fragment if available
|
|
this.token = this.route
|
|
.fragment
|
|
.map(fragment => fragment || 'None');
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/admin/admin-routing.module.ts]" value="import { NgModule } from '@angular/core';
|
|
import { RouterModule, Routes } from '@angular/router';
|
|
|
|
import { AdminComponent } from './admin.component';
|
|
import { AdminDashboardComponent } from './admin-dashboard.component';
|
|
import { ManageCrisesComponent } from './manage-crises.component';
|
|
import { ManageHeroesComponent } from './manage-heroes.component';
|
|
|
|
import { AuthGuard } from '../auth-guard.service';
|
|
|
|
const adminRoutes: Routes = [
|
|
{
|
|
path: '',
|
|
component: AdminComponent,
|
|
canActivate: [AuthGuard],
|
|
children: [
|
|
{
|
|
path: '',
|
|
canActivateChild: [AuthGuard],
|
|
children: [
|
|
{ path: 'crises', component: ManageCrisesComponent },
|
|
{ path: 'heroes', component: ManageHeroesComponent },
|
|
{ path: '', component: AdminDashboardComponent }
|
|
]
|
|
}
|
|
]
|
|
}
|
|
];
|
|
|
|
@NgModule({
|
|
imports: [
|
|
RouterModule.forChild(adminRoutes)
|
|
],
|
|
exports: [
|
|
RouterModule
|
|
]
|
|
})
|
|
export class AdminRoutingModule {}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/admin/admin.component.ts]" value="import { Component } from '@angular/core';
|
|
|
|
@Component({
|
|
template: `
|
|
<h3>ADMIN</h3>
|
|
<nav>
|
|
<a routerLink="./" routerLinkActive="active"
|
|
[routerLinkActiveOptions]="{ exact: true }">Dashboard</a>
|
|
<a routerLink="./crises" routerLinkActive="active">Manage Crises</a>
|
|
<a routerLink="./heroes" routerLinkActive="active">Manage Heroes</a>
|
|
</nav>
|
|
<router-outlet></router-outlet>
|
|
`
|
|
})
|
|
export class AdminComponent {
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/admin/admin.module.ts]" value="import { NgModule } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
|
|
import { AdminComponent } from './admin.component';
|
|
import { AdminDashboardComponent } from './admin-dashboard.component';
|
|
import { ManageCrisesComponent } from './manage-crises.component';
|
|
import { ManageHeroesComponent } from './manage-heroes.component';
|
|
|
|
import { AdminRoutingModule } from './admin-routing.module';
|
|
|
|
@NgModule({
|
|
imports: [
|
|
CommonModule,
|
|
AdminRoutingModule
|
|
],
|
|
declarations: [
|
|
AdminComponent,
|
|
AdminDashboardComponent,
|
|
ManageCrisesComponent,
|
|
ManageHeroesComponent
|
|
]
|
|
})
|
|
export class AdminModule {}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/admin/manage-crises.component.ts]" value="import { Component } from '@angular/core';
|
|
|
|
@Component({
|
|
template: `
|
|
<p>Manage your crises here</p>
|
|
`
|
|
})
|
|
export class ManageCrisesComponent { }
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/admin/manage-heroes.component.ts]" value="import { Component } from '@angular/core';
|
|
|
|
@Component({
|
|
template: `
|
|
<p>Manage your heroes here</p>
|
|
`
|
|
})
|
|
export class ManageHeroesComponent { }
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/animations.ts]" value="import { animate, AnimationEntryMetadata, state, style, transition, trigger } from '@angular/core';
|
|
|
|
// Component transition animations
|
|
export const slideInDownAnimation: AnimationEntryMetadata =
|
|
trigger('routeAnimation', [
|
|
state('*',
|
|
style({
|
|
opacity: 1,
|
|
transform: 'translateX(0)'
|
|
})
|
|
),
|
|
transition(':enter', [
|
|
style({
|
|
opacity: 0,
|
|
transform: 'translateX(-100%)'
|
|
}),
|
|
animate('0.2s ease-in')
|
|
]),
|
|
transition(':leave', [
|
|
animate('0.5s ease-out', style({
|
|
opacity: 0,
|
|
transform: 'translateY(100%)'
|
|
}))
|
|
])
|
|
]);
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/app-routing.module.ts]" value="import { NgModule } from '@angular/core';
|
|
import { RouterModule, Routes } from '@angular/router';
|
|
|
|
import { ComposeMessageComponent } from './compose-message.component';
|
|
import { PageNotFoundComponent } from './not-found.component';
|
|
|
|
import { CanDeactivateGuard } from './can-deactivate-guard.service';
|
|
import { AuthGuard } from './auth-guard.service';
|
|
import { SelectivePreloadingStrategy } from './selective-preloading-strategy';
|
|
|
|
const appRoutes: Routes = [
|
|
{
|
|
path: 'compose',
|
|
component: ComposeMessageComponent,
|
|
outlet: 'popup'
|
|
},
|
|
{
|
|
path: 'admin',
|
|
loadChildren: 'app/admin/admin.module#AdminModule',
|
|
canLoad: [AuthGuard]
|
|
},
|
|
{
|
|
path: 'crisis-center',
|
|
loadChildren: 'app/crisis-center/crisis-center.module#CrisisCenterModule',
|
|
data: { preload: true }
|
|
},
|
|
{ path: '', redirectTo: '/heroes', pathMatch: 'full' },
|
|
{ path: '**', component: PageNotFoundComponent }
|
|
];
|
|
|
|
@NgModule({
|
|
imports: [
|
|
RouterModule.forRoot(
|
|
appRoutes,
|
|
{ preloadingStrategy: SelectivePreloadingStrategy }
|
|
)
|
|
],
|
|
exports: [
|
|
RouterModule
|
|
],
|
|
providers: [
|
|
CanDeactivateGuard,
|
|
SelectivePreloadingStrategy
|
|
]
|
|
})
|
|
export class AppRoutingModule { }
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/app.component.ts]" value="import { Component } from '@angular/core';
|
|
|
|
@Component({
|
|
selector: 'my-app',
|
|
template: `
|
|
<h1 class="title">Angular Router</h1>
|
|
<nav>
|
|
<a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
|
|
<a routerLink="/heroes" routerLinkActive="active">Heroes</a>
|
|
<a routerLink="/admin" routerLinkActive="active">Admin</a>
|
|
<a routerLink="/login" routerLinkActive="active">Login</a>
|
|
<a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a>
|
|
</nav>
|
|
<router-outlet></router-outlet>
|
|
<router-outlet name="popup"></router-outlet>
|
|
`
|
|
})
|
|
export class AppComponent {
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/app.module.ts]" value="import { NgModule } from '@angular/core';
|
|
import { BrowserModule } from '@angular/platform-browser';
|
|
import { FormsModule } from '@angular/forms';
|
|
import { Router } from '@angular/router';
|
|
|
|
import { AppComponent } from './app.component';
|
|
import { AppRoutingModule } from './app-routing.module';
|
|
|
|
import { HeroesModule } from './heroes/heroes.module';
|
|
import { ComposeMessageComponent } from './compose-message.component';
|
|
import { LoginRoutingModule } from './login-routing.module';
|
|
import { LoginComponent } from './login.component';
|
|
import { PageNotFoundComponent } from './not-found.component';
|
|
|
|
import { DialogService } from './dialog.service';
|
|
|
|
@NgModule({
|
|
imports: [
|
|
BrowserModule,
|
|
FormsModule,
|
|
HeroesModule,
|
|
LoginRoutingModule,
|
|
AppRoutingModule
|
|
],
|
|
declarations: [
|
|
AppComponent,
|
|
ComposeMessageComponent,
|
|
LoginComponent,
|
|
PageNotFoundComponent
|
|
],
|
|
providers: [
|
|
DialogService
|
|
],
|
|
bootstrap: [ AppComponent ]
|
|
})
|
|
export class AppModule {
|
|
// Diagnostic only: inspect router configuration
|
|
constructor(router: Router) {
|
|
console.log('Routes: ', JSON.stringify(router.config, undefined, 2));
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/auth-guard.service.ts]" value="import { Injectable } from '@angular/core';
|
|
import {
|
|
CanActivate, Router,
|
|
ActivatedRouteSnapshot,
|
|
RouterStateSnapshot,
|
|
CanActivateChild,
|
|
NavigationExtras,
|
|
CanLoad, Route
|
|
} from '@angular/router';
|
|
import { AuthService } from './auth.service';
|
|
|
|
@Injectable()
|
|
export class AuthGuard implements CanActivate, CanActivateChild, CanLoad {
|
|
constructor(private authService: AuthService, private router: Router) {}
|
|
|
|
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
|
|
let url: string = state.url;
|
|
|
|
return this.checkLogin(url);
|
|
}
|
|
|
|
canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
|
|
return this.canActivate(route, state);
|
|
}
|
|
|
|
canLoad(route: Route): boolean {
|
|
let url = `/${route.path}`;
|
|
|
|
return this.checkLogin(url);
|
|
}
|
|
|
|
checkLogin(url: string): boolean {
|
|
if (this.authService.isLoggedIn) { return true; }
|
|
|
|
// Store the attempted URL for redirecting
|
|
this.authService.redirectUrl = url;
|
|
|
|
// Create a dummy session id
|
|
let sessionId = 123456789;
|
|
|
|
// Set our navigation extras object
|
|
// that contains our global query params and fragment
|
|
let navigationExtras: NavigationExtras = {
|
|
queryParams: { 'session_id': sessionId },
|
|
fragment: 'anchor'
|
|
};
|
|
|
|
// Navigate to the login page with extras
|
|
this.router.navigate(['/login'], navigationExtras);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/auth.service.ts]" value="import { Injectable } from '@angular/core';
|
|
|
|
import { Observable } from 'rxjs/Observable';
|
|
import 'rxjs/add/observable/of';
|
|
import 'rxjs/add/operator/do';
|
|
import 'rxjs/add/operator/delay';
|
|
|
|
@Injectable()
|
|
export class AuthService {
|
|
isLoggedIn: boolean = false;
|
|
|
|
// store the URL so we can redirect after logging in
|
|
redirectUrl: string;
|
|
|
|
login(): Observable<boolean> {
|
|
return Observable.of(true).delay(1000).do(val => this.isLoggedIn = true);
|
|
}
|
|
|
|
logout(): void {
|
|
this.isLoggedIn = false;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/can-deactivate-guard.service.ts]" value="import { Injectable } from '@angular/core';
|
|
import { CanDeactivate } from '@angular/router';
|
|
import { Observable } from 'rxjs/Observable';
|
|
|
|
export interface CanComponentDeactivate {
|
|
canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
|
|
}
|
|
|
|
@Injectable()
|
|
export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> {
|
|
canDeactivate(component: CanComponentDeactivate) {
|
|
return component.canDeactivate ? component.canDeactivate() : true;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/compose-message.component.ts]" value="import { Component, HostBinding } from '@angular/core';
|
|
import { Router } from '@angular/router';
|
|
|
|
import { slideInDownAnimation } from './animations';
|
|
|
|
@Component({
|
|
moduleId: module.id,
|
|
templateUrl: './compose-message.component.html',
|
|
styles: [ ':host { position: relative; bottom: 10%; }' ],
|
|
animations: [ slideInDownAnimation ]
|
|
})
|
|
export class ComposeMessageComponent {
|
|
@HostBinding('@routeAnimation') routeAnimation = true;
|
|
@HostBinding('style.display') display = 'block';
|
|
@HostBinding('style.position') position = 'absolute';
|
|
|
|
details: string;
|
|
sending: boolean = false;
|
|
|
|
constructor(private router: Router) {}
|
|
|
|
send() {
|
|
this.sending = true;
|
|
this.details = 'Sending Message...';
|
|
|
|
setTimeout(() => {
|
|
this.sending = false;
|
|
this.closePopup();
|
|
}, 1000);
|
|
}
|
|
|
|
cancel() {
|
|
this.closePopup();
|
|
}
|
|
|
|
closePopup() {
|
|
// Providing a `null` value to the named outlet
|
|
// clears the contents of the named outlet
|
|
this.router.navigate([{ outlets: { popup: null }}]);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/crisis-center/crisis-center-home.component.ts]" value="import { Component } from '@angular/core';
|
|
|
|
@Component({
|
|
template: `
|
|
<p>Welcome to the Crisis Center</p>
|
|
`
|
|
})
|
|
export class CrisisCenterHomeComponent { }
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/crisis-center/crisis-center-routing.module.ts]" value="import { NgModule } from '@angular/core';
|
|
import { RouterModule, Routes } from '@angular/router';
|
|
|
|
import { CrisisCenterHomeComponent } from './crisis-center-home.component';
|
|
import { CrisisListComponent } from './crisis-list.component';
|
|
import { CrisisCenterComponent } from './crisis-center.component';
|
|
import { CrisisDetailComponent } from './crisis-detail.component';
|
|
|
|
import { CanDeactivateGuard } from '../can-deactivate-guard.service';
|
|
import { CrisisDetailResolver } from './crisis-detail-resolver.service';
|
|
|
|
const crisisCenterRoutes: Routes = [
|
|
{
|
|
path: '',
|
|
component: CrisisCenterComponent,
|
|
children: [
|
|
{
|
|
path: '',
|
|
component: CrisisListComponent,
|
|
children: [
|
|
{
|
|
path: ':id',
|
|
component: CrisisDetailComponent,
|
|
canDeactivate: [CanDeactivateGuard],
|
|
resolve: {
|
|
crisis: CrisisDetailResolver
|
|
}
|
|
},
|
|
{
|
|
path: '',
|
|
component: CrisisCenterHomeComponent
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
];
|
|
|
|
@NgModule({
|
|
imports: [
|
|
RouterModule.forChild(crisisCenterRoutes)
|
|
],
|
|
exports: [
|
|
RouterModule
|
|
],
|
|
providers: [
|
|
CrisisDetailResolver
|
|
]
|
|
})
|
|
export class CrisisCenterRoutingModule { }
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/crisis-center/crisis-center.component.ts]" value="import { Component } from '@angular/core';
|
|
|
|
@Component({
|
|
template: `
|
|
<h2>CRISIS CENTER</h2>
|
|
<router-outlet></router-outlet>
|
|
`
|
|
})
|
|
export class CrisisCenterComponent { }
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/crisis-center/crisis-center.module.ts]" value="import { NgModule } from '@angular/core';
|
|
import { FormsModule } from '@angular/forms';
|
|
import { CommonModule } from '@angular/common';
|
|
|
|
import { CrisisService } from './crisis.service';
|
|
|
|
import { CrisisCenterComponent } from './crisis-center.component';
|
|
import { CrisisListComponent } from './crisis-list.component';
|
|
import { CrisisCenterHomeComponent } from './crisis-center-home.component';
|
|
import { CrisisDetailComponent } from './crisis-detail.component';
|
|
|
|
import { CrisisCenterRoutingModule } from './crisis-center-routing.module';
|
|
|
|
@NgModule({
|
|
imports: [
|
|
CommonModule,
|
|
FormsModule,
|
|
CrisisCenterRoutingModule
|
|
],
|
|
declarations: [
|
|
CrisisCenterComponent,
|
|
CrisisListComponent,
|
|
CrisisCenterHomeComponent,
|
|
CrisisDetailComponent
|
|
],
|
|
providers: [
|
|
CrisisService
|
|
]
|
|
})
|
|
export class CrisisCenterModule {}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/crisis-center/crisis-detail-resolver.service.ts]" value="import { Injectable } from '@angular/core';
|
|
import { Router, Resolve, RouterStateSnapshot,
|
|
ActivatedRouteSnapshot } from '@angular/router';
|
|
|
|
import { Crisis, CrisisService } from './crisis.service';
|
|
|
|
@Injectable()
|
|
export class CrisisDetailResolver implements Resolve<Crisis> {
|
|
constructor(private cs: CrisisService, private router: Router) {}
|
|
|
|
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<Crisis> {
|
|
let id = route.params['id'];
|
|
|
|
return this.cs.getCrisis(id).then(crisis => {
|
|
if (crisis) {
|
|
return crisis;
|
|
} else { // id not found
|
|
this.router.navigate(['/crisis-center']);
|
|
return null;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/crisis-center/crisis-detail.component.ts]" value="import { Component, OnInit, HostBinding } from '@angular/core';
|
|
import { ActivatedRoute, Router } from '@angular/router';
|
|
|
|
import { slideInDownAnimation } from '../animations';
|
|
import { Crisis } from './crisis.service';
|
|
import { DialogService } from '../dialog.service';
|
|
|
|
@Component({
|
|
template: `
|
|
<div *ngIf="crisis">
|
|
<h3>"{{ editName }}"</h3>
|
|
<div>
|
|
<label>Id: </label>{{ crisis.id }}</div>
|
|
<div>
|
|
<label>Name: </label>
|
|
<input [(ngModel)]="editName" placeholder="name"/>
|
|
</div>
|
|
<p>
|
|
<button (click)="save()">Save</button>
|
|
<button (click)="cancel()">Cancel</button>
|
|
</p>
|
|
</div>
|
|
`,
|
|
styles: ['input {width: 20em}'],
|
|
animations: [ slideInDownAnimation ]
|
|
})
|
|
export class CrisisDetailComponent implements OnInit {
|
|
@HostBinding('@routeAnimation') routeAnimation = true;
|
|
@HostBinding('style.display') display = 'block';
|
|
@HostBinding('style.position') position = 'absolute';
|
|
|
|
crisis: Crisis;
|
|
editName: string;
|
|
|
|
constructor(
|
|
private route: ActivatedRoute,
|
|
private router: Router,
|
|
public dialogService: DialogService
|
|
) {}
|
|
|
|
ngOnInit() {
|
|
this.route.data
|
|
.subscribe((data: { crisis: Crisis }) => {
|
|
this.editName = data.crisis.name;
|
|
this.crisis = data.crisis;
|
|
});
|
|
}
|
|
|
|
cancel() {
|
|
this.gotoCrises();
|
|
}
|
|
|
|
save() {
|
|
this.crisis.name = this.editName;
|
|
this.gotoCrises();
|
|
}
|
|
|
|
canDeactivate(): Promise<boolean> | boolean {
|
|
// Allow synchronous navigation (`true`) if no crisis or the crisis is unchanged
|
|
if (!this.crisis || this.crisis.name === this.editName) {
|
|
return true;
|
|
}
|
|
// Otherwise ask the user with the dialog service and return its
|
|
// promise which resolves to true or false when the user decides
|
|
return this.dialogService.confirm('Discard changes?');
|
|
}
|
|
|
|
gotoCrises() {
|
|
let crisisId = this.crisis ? this.crisis.id : null;
|
|
// Pass along the crisis id if available
|
|
// so that the CrisisListComponent can select that crisis.
|
|
// Add a totally useless `foo` parameter for kicks.
|
|
// Relative navigation back to the crises
|
|
this.router.navigate(['../', { id: crisisId, foo: 'foo' }], { relativeTo: this.route });
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/crisis-center/crisis-list.component.ts]" value="import 'rxjs/add/operator/switchMap';
|
|
import { Component, OnInit } from '@angular/core';
|
|
import { ActivatedRoute, Router, Params } from '@angular/router';
|
|
|
|
import { Observable } from 'rxjs/Observable';
|
|
|
|
import { Crisis, CrisisService } from './crisis.service';
|
|
|
|
@Component({
|
|
template: `
|
|
<ul class="items">
|
|
<li *ngFor="let crisis of crises | async"
|
|
(click)="onSelect(crisis)"
|
|
[class.selected]="isSelected(crisis)">
|
|
<span class="badge">{{ crisis.id }}</span>
|
|
{{ crisis.name }}
|
|
</li>
|
|
</ul>
|
|
|
|
<router-outlet></router-outlet>
|
|
`
|
|
})
|
|
export class CrisisListComponent implements OnInit {
|
|
crises: Observable<Crisis[]>;
|
|
selectedId: number;
|
|
|
|
constructor(
|
|
private service: CrisisService,
|
|
private route: ActivatedRoute,
|
|
private router: Router
|
|
) {}
|
|
|
|
isSelected(crisis: Crisis) {
|
|
return crisis.id === this.selectedId;
|
|
}
|
|
|
|
ngOnInit() {
|
|
this.crises = this.route.params
|
|
.switchMap((params: Params) => {
|
|
this.selectedId = +params['id'];
|
|
return this.service.getCrises();
|
|
});
|
|
}
|
|
|
|
onSelect(crisis: Crisis) {
|
|
this.selectedId = crisis.id;
|
|
|
|
// Navigate with relative link
|
|
this.router.navigate([crisis.id], { relativeTo: this.route });
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/crisis-center/crisis.service.ts]" value="export class Crisis {
|
|
constructor(public id: number, public name: string) { }
|
|
}
|
|
|
|
const CRISES = [
|
|
new Crisis(1, 'Dragon Burning Cities'),
|
|
new Crisis(2, 'Sky Rains Great White Sharks'),
|
|
new Crisis(3, 'Giant Asteroid Heading For Earth'),
|
|
new Crisis(4, 'Procrastinators Meeting Delayed Again'),
|
|
];
|
|
|
|
let crisesPromise = Promise.resolve(CRISES);
|
|
|
|
import { Injectable } from '@angular/core';
|
|
|
|
@Injectable()
|
|
export class CrisisService {
|
|
|
|
static nextCrisisId = 100;
|
|
|
|
getCrises() { return crisesPromise; }
|
|
|
|
getCrisis(id: number | string) {
|
|
return crisesPromise
|
|
.then(crises => crises.find(crisis => crisis.id === +id));
|
|
}
|
|
|
|
addCrisis(name: string) {
|
|
name = name.trim();
|
|
if (name) {
|
|
let crisis = new Crisis(CrisisService.nextCrisisId++, name);
|
|
crisesPromise.then(crises => crises.push(crisis));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/dialog.service.ts]" value="import { Injectable } from '@angular/core';
|
|
/**
|
|
* Async modal dialog service
|
|
* DialogService makes this app easier to test by faking this service.
|
|
* TODO: better modal implementation that doesn't use window.confirm
|
|
*/
|
|
@Injectable()
|
|
export class DialogService {
|
|
/**
|
|
* Ask user to confirm an action. `message` explains the action and choices.
|
|
* Returns promise resolving to `true`=confirm or `false`=cancel
|
|
*/
|
|
confirm(message?: string) {
|
|
return new Promise<boolean>(resolve => {
|
|
return resolve(window.confirm(message || 'Is it OK?'));
|
|
});
|
|
};
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/heroes/hero-detail.component.ts]" value="import 'rxjs/add/operator/switchMap';
|
|
import { Component, OnInit, HostBinding } from '@angular/core';
|
|
import { Router, ActivatedRoute, Params } from '@angular/router';
|
|
|
|
import { slideInDownAnimation } from '../animations';
|
|
|
|
import { Hero, HeroService } from './hero.service';
|
|
|
|
@Component({
|
|
template: `
|
|
<h2>HEROES</h2>
|
|
<div *ngIf="hero">
|
|
<h3>"{{ hero.name }}"</h3>
|
|
<div>
|
|
<label>Id: </label>{{ hero.id }}</div>
|
|
<div>
|
|
<label>Name: </label>
|
|
<input [(ngModel)]="hero.name" placeholder="name"/>
|
|
</div>
|
|
<p>
|
|
<button (click)="gotoHeroes()">Back</button>
|
|
</p>
|
|
</div>
|
|
`,
|
|
animations: [ slideInDownAnimation ]
|
|
})
|
|
export class HeroDetailComponent implements OnInit {
|
|
@HostBinding('@routeAnimation') routeAnimation = true;
|
|
@HostBinding('style.display') display = 'block';
|
|
@HostBinding('style.position') position = 'absolute';
|
|
|
|
hero: Hero;
|
|
|
|
constructor(
|
|
private route: ActivatedRoute,
|
|
private router: Router,
|
|
private service: HeroService
|
|
) {}
|
|
|
|
ngOnInit() {
|
|
this.route.params
|
|
// (+) converts string 'id' to a number
|
|
.switchMap((params: Params) => this.service.getHero(+params['id']))
|
|
.subscribe((hero: Hero) => this.hero = hero);
|
|
}
|
|
|
|
gotoHeroes() {
|
|
let heroId = this.hero ? this.hero.id : null;
|
|
// Pass along the hero id if available
|
|
// so that the HeroList component can select that hero.
|
|
// Include a junk 'foo' property for fun.
|
|
this.router.navigate(['/heroes', { id: heroId, foo: 'foo' }]);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/heroes/hero-list.component.ts]" value="// TODO SOMEDAY: Feature Componetized like CrisisCenter
|
|
import 'rxjs/add/operator/switchMap';
|
|
import { Observable } from 'rxjs/Observable';
|
|
import { Component, OnInit } from '@angular/core';
|
|
import { Router, ActivatedRoute, Params } from '@angular/router';
|
|
|
|
import { Hero, HeroService } from './hero.service';
|
|
|
|
@Component({
|
|
template: `
|
|
<h2>HEROES</h2>
|
|
<ul class="items">
|
|
<li *ngFor="let hero of heroes | async"
|
|
[class.selected]="isSelected(hero)"
|
|
(click)="onSelect(hero)">
|
|
<span class="badge">{{ hero.id }}</span> {{ hero.name }}
|
|
</li>
|
|
</ul>
|
|
|
|
<button routerLink="/sidekicks">Go to sidekicks</button>
|
|
`
|
|
})
|
|
export class HeroListComponent implements OnInit {
|
|
heroes: Observable<Hero[]>;
|
|
|
|
private selectedId: number;
|
|
|
|
constructor(
|
|
private service: HeroService,
|
|
private route: ActivatedRoute,
|
|
private router: Router
|
|
) {}
|
|
|
|
ngOnInit() {
|
|
this.heroes = this.route.params
|
|
.switchMap((params: Params) => {
|
|
this.selectedId = +params['id'];
|
|
return this.service.getHeroes();
|
|
});
|
|
}
|
|
|
|
isSelected(hero: Hero) { return hero.id === this.selectedId; }
|
|
|
|
onSelect(hero: Hero) {
|
|
this.router.navigate(['/hero', hero.id]);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/heroes/hero.service.ts]" value="import { Injectable } from '@angular/core';
|
|
|
|
export class Hero {
|
|
constructor(public id: number, public name: string) { }
|
|
}
|
|
|
|
let HEROES = [
|
|
new Hero(11, 'Mr. Nice'),
|
|
new Hero(12, 'Narco'),
|
|
new Hero(13, 'Bombasto'),
|
|
new Hero(14, 'Celeritas'),
|
|
new Hero(15, 'Magneta'),
|
|
new Hero(16, 'RubberMan')
|
|
];
|
|
|
|
let heroesPromise = Promise.resolve(HEROES);
|
|
|
|
@Injectable()
|
|
export class HeroService {
|
|
getHeroes() { return heroesPromise; }
|
|
|
|
getHero(id: number | string) {
|
|
return heroesPromise
|
|
.then(heroes => heroes.find(hero => hero.id === +id));
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/heroes/heroes-routing.module.ts]" value="import { NgModule } from '@angular/core';
|
|
import { RouterModule, Routes } from '@angular/router';
|
|
|
|
import { HeroListComponent } from './hero-list.component';
|
|
import { HeroDetailComponent } from './hero-detail.component';
|
|
|
|
const heroesRoutes: Routes = [
|
|
{ path: 'heroes', component: HeroListComponent },
|
|
{ path: 'hero/:id', component: HeroDetailComponent }
|
|
];
|
|
|
|
@NgModule({
|
|
imports: [
|
|
RouterModule.forChild(heroesRoutes)
|
|
],
|
|
exports: [
|
|
RouterModule
|
|
]
|
|
})
|
|
export class HeroRoutingModule { }
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/heroes/heroes.module.ts]" value="import { NgModule } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
import { FormsModule } from '@angular/forms';
|
|
|
|
import { HeroListComponent } from './hero-list.component';
|
|
import { HeroDetailComponent } from './hero-detail.component';
|
|
|
|
import { HeroService } from './hero.service';
|
|
|
|
import { HeroRoutingModule } from './heroes-routing.module';
|
|
|
|
@NgModule({
|
|
imports: [
|
|
CommonModule,
|
|
FormsModule,
|
|
HeroRoutingModule
|
|
],
|
|
declarations: [
|
|
HeroListComponent,
|
|
HeroDetailComponent
|
|
],
|
|
providers: [ HeroService ]
|
|
})
|
|
export class HeroesModule {}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/login-routing.module.ts]" value="import { NgModule } from '@angular/core';
|
|
import { RouterModule, Routes } from '@angular/router';
|
|
import { AuthGuard } from './auth-guard.service';
|
|
import { AuthService } from './auth.service';
|
|
import { LoginComponent } from './login.component';
|
|
|
|
const loginRoutes: Routes = [
|
|
{ path: 'login', component: LoginComponent }
|
|
];
|
|
|
|
@NgModule({
|
|
imports: [
|
|
RouterModule.forChild(loginRoutes)
|
|
],
|
|
exports: [
|
|
RouterModule
|
|
],
|
|
providers: [
|
|
AuthGuard,
|
|
AuthService
|
|
]
|
|
})
|
|
export class LoginRoutingModule {}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/login.component.ts]" value="import { Component } from '@angular/core';
|
|
import { Router,
|
|
NavigationExtras } from '@angular/router';
|
|
import { AuthService } from './auth.service';
|
|
|
|
@Component({
|
|
template: `
|
|
<h2>LOGIN</h2>
|
|
<p>{{message}}</p>
|
|
<p>
|
|
<button (click)="login()" *ngIf="!authService.isLoggedIn">Login</button>
|
|
<button (click)="logout()" *ngIf="authService.isLoggedIn">Logout</button>
|
|
</p>`
|
|
})
|
|
export class LoginComponent {
|
|
message: string;
|
|
|
|
constructor(public authService: AuthService, public router: Router) {
|
|
this.setMessage();
|
|
}
|
|
|
|
setMessage() {
|
|
this.message = 'Logged ' + (this.authService.isLoggedIn ? 'in' : 'out');
|
|
}
|
|
|
|
login() {
|
|
this.message = 'Trying to log in ...';
|
|
|
|
this.authService.login().subscribe(() => {
|
|
this.setMessage();
|
|
if (this.authService.isLoggedIn) {
|
|
// Get the redirect URL from our auth service
|
|
// If no redirect has been set, use the default
|
|
let redirect = this.authService.redirectUrl ? this.authService.redirectUrl : '/admin';
|
|
|
|
// Set our navigation extras object
|
|
// that passes on our global query params and fragment
|
|
let navigationExtras: NavigationExtras = {
|
|
preserveQueryParams: true,
|
|
preserveFragment: true
|
|
};
|
|
|
|
// Redirect the user
|
|
this.router.navigate([redirect], navigationExtras);
|
|
}
|
|
});
|
|
}
|
|
|
|
logout() {
|
|
this.authService.logout();
|
|
this.setMessage();
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/not-found.component.ts]" value="import { Component } from '@angular/core';
|
|
|
|
@Component({
|
|
template: '<h2>Page not found</h2>'
|
|
})
|
|
export class PageNotFoundComponent {}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/selective-preloading-strategy.ts]" value="import 'rxjs/add/observable/of';
|
|
import { Injectable } from '@angular/core';
|
|
import { PreloadingStrategy, Route } from '@angular/router';
|
|
import { Observable } from 'rxjs/Observable';
|
|
|
|
@Injectable()
|
|
export class SelectivePreloadingStrategy implements PreloadingStrategy {
|
|
preloadedModules: string[] = [];
|
|
|
|
preload(route: Route, load: () => Observable<any>): Observable<any> {
|
|
if (route.data && route.data['preload']) {
|
|
// add the route path to our preloaded module array
|
|
this.preloadedModules.push(route.path);
|
|
|
|
// log the route path to the console
|
|
console.log('Preloaded: ' + route.path);
|
|
|
|
return load();
|
|
} else {
|
|
return Observable.of(null);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[main.ts]" value="import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
|
|
|
import { AppModule } from './app/app.module';
|
|
|
|
platformBrowserDynamic().bootstrapModule(AppModule);
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[styles.css]" value="/* Master Styles */
|
|
h1 {
|
|
color: #369;
|
|
font-family: Arial, Helvetica, sans-serif;
|
|
font-size: 250%;
|
|
}
|
|
h2, h3 {
|
|
color: #444;
|
|
font-family: Arial, Helvetica, sans-serif;
|
|
font-weight: lighter;
|
|
}
|
|
body {
|
|
margin: 2em;
|
|
}
|
|
body, input[text], button {
|
|
color: #888;
|
|
font-family: Cambria, Georgia;
|
|
}
|
|
a {
|
|
cursor: pointer;
|
|
cursor: hand;
|
|
}
|
|
button {
|
|
font-family: Arial;
|
|
background-color: #eee;
|
|
border: none;
|
|
padding: 5px 10px;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
cursor: hand;
|
|
}
|
|
button:hover {
|
|
background-color: #cfd8dc;
|
|
}
|
|
button:disabled {
|
|
background-color: #eee;
|
|
color: #aaa;
|
|
cursor: auto;
|
|
}
|
|
|
|
/* Navigation link styles */
|
|
nav a {
|
|
padding: 5px 10px;
|
|
text-decoration: none;
|
|
margin-right: 10px;
|
|
margin-top: 10px;
|
|
display: inline-block;
|
|
background-color: #eee;
|
|
border-radius: 4px;
|
|
}
|
|
nav a:visited, a:link {
|
|
color: #607D8B;
|
|
}
|
|
nav a:hover {
|
|
color: #039be5;
|
|
background-color: #CFD8DC;
|
|
}
|
|
nav a.active {
|
|
color: #039be5;
|
|
}
|
|
|
|
/* items class */
|
|
.items {
|
|
margin: 0 0 2em 0;
|
|
list-style-type: none;
|
|
padding: 0;
|
|
width: 24em;
|
|
}
|
|
.items li {
|
|
cursor: pointer;
|
|
position: relative;
|
|
left: 0;
|
|
background-color: #EEE;
|
|
margin: .5em;
|
|
padding: .3em 0;
|
|
height: 1.6em;
|
|
border-radius: 4px;
|
|
}
|
|
.items li:hover {
|
|
color: #607D8B;
|
|
background-color: #DDD;
|
|
left: .1em;
|
|
}
|
|
.items li.selected {
|
|
background-color: #CFD8DC;
|
|
color: white;
|
|
}
|
|
.items li.selected:hover {
|
|
background-color: #BBD8DC;
|
|
}
|
|
.items .text {
|
|
position: relative;
|
|
top: -3px;
|
|
}
|
|
.items .badge {
|
|
display: inline-block;
|
|
font-size: small;
|
|
color: white;
|
|
padding: 0.8em 0.7em 0 0.7em;
|
|
background-color: #607D8B;
|
|
line-height: 1em;
|
|
position: relative;
|
|
left: -1px;
|
|
top: -4px;
|
|
height: 1.8em;
|
|
margin-right: .8em;
|
|
border-radius: 4px 0 0 4px;
|
|
}
|
|
/* everywhere else */
|
|
* {
|
|
font-family: Arial, Helvetica, sans-serif;
|
|
}
|
|
|
|
|
|
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
*/"><input type="hidden" name="files[app/compose-message.component.html]" value="<h3>Contact Crisis Center</h3>
|
|
<div *ngIf="details">
|
|
{{ details }}
|
|
</div>
|
|
<div>
|
|
<div>
|
|
<label>Message: </label>
|
|
</div>
|
|
<div>
|
|
<textarea [(ngModel)]="message" rows="10" cols="35" [disabled]="sending"></textarea>
|
|
</div>
|
|
</div>
|
|
<p *ngIf="!sending">
|
|
<button (click)="send()">Send</button>
|
|
<button (click)="cancel()">Cancel</button>
|
|
</p>
|
|
|
|
|
|
<!--
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
-->"><input type="hidden" name="files[index.html]" value="<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<!-- Set the base href -->
|
|
<script>document.write('<base href="' + document.location + '" />');</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">
|
|
|
|
<!-- Polyfills -->
|
|
<script src="https://unpkg.com/core-js/client/shim.min.js"></script>
|
|
|
|
<script src="https://unpkg.com/zone.js@0.7.4?main=browser"></script>
|
|
<script src="https://unpkg.com/systemjs@0.19.39/dist/system.src.js"></script>
|
|
|
|
<script src="https://cdn.rawgit.com/angular/angular.io/b3c65a9/public/docs/_examples/_boilerplate/systemjs.config.web.js"></script>
|
|
<script>
|
|
System.import('main.js')
|
|
.catch(function(err){ console.error(err); });
|
|
</script>
|
|
</head>
|
|
|
|
<body>
|
|
<my-app>loading...</my-app>
|
|
</body>
|
|
|
|
</html>
|
|
|
|
|
|
<!--
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
Use of this source code is governed by an MIT-style license that
|
|
can be found in the LICENSE file at http://angular.io/license
|
|
-->"><input type="hidden" name="tags[0]" value="angular"><input type="hidden" name="tags[1]" value="example"><input type="hidden" name="tags[2]" value="router"><input type="hidden" name="private" value="true"><input type="hidden" name="description" value="Angular Example - Router"></form><script>document.getElementById("mainForm").submit();</script></body></html> |