Angular 2/4 - 用户注册和登录示例和教程

几个月前,我发布了一个教程,介绍如何使用模拟后端在Angular 2中构建用户注册和登录功能,它包括我最近为悉尼律师事务所开发的安全Web应用程序的样板前端代码。 在这篇文章中,我将扩展它,添加一个建立在Node,Express和Mongo上的真正后端API,使其成为具有Angular 2/4(MEAN Stack 2/4)应用程序的完整MEAN Stack。

项目代码可在GitHub上获取。

如果您使用AngularJS / Angular 1构建的类似应用,您可以查看MEAN Stack 1.0 - User Registration and Login Example & Tutorial
如果您使用ASP.NET Core Web API开发,请查看ASP.NET Core + Angular 2/4 - User Registration and Login Tutorial & Example

Update History:

  • 25 May 2017 - Updated to Angular 4.1.0
  • 17 Mar 2017 - Updated to Angular 2.4.9
  • 22 Feb 2017 - Built with Angular 2.2.1

在本地运行Angular 2/4 - 用户注册和登录示例

注意: 若要在本地运行本示例,您需要安装NodeJS并运行MongoDB。请参阅Setup the MEAN Stack on Windows

该项目包含两个应用程序,一个用于客户端,另一个用于服务器api,以下是每个应用程序设置和运行的步骤:

运行MEAN Stack 服务端API

  1. 确认MongoDB已经安装并运行
  2. 打开命令行并定位到项目根文件夹下的/server文件夹下
  3. 运行npm install以安装在package.json文件中定义的所有必需的npm软件包
  4. 运行node server.js启动服务端,服务端默认地址:http://localhost:4000

运行Angular 2/4客户端

  1. 打开命令行窗口并导航到项目根目录下的/client文件夹
  2. 运行npm install以安装在package.json文件中定义的所有必需的npm软件包
  3. 运行npm start启动客户端,浏览器会自动打开http://localhost:3000

MEAN with Angular 2/4 项目结构

MEAN with Angular 2/4 客户端

路径: /client
我使用Angular 2 quickstart项目作为客户端应用程序的基础,他使用TypeScript编写并使用systemjs加载模块。如果你是Angular 2的新手,我建议你去看一下Angular 2 quickstart,因为他详细的介绍了项目工具和配置文件,本文不再赘述。

该项目和代码结构主要遵循官方Angular 2 style guide中的建议,我在其基础上做了一些调整。

Angular 2/4 Alert Component 模板

路径: /client/app/_directives/alert.component.html
alert component 模板包含在页面顶部显示alert信息的html

<div *ngIf="message" [ngClass]="{ 'alert': message, 'alert-success': message.type === 'success', 'alert-danger': message.type === 'error' }">{{message.text}}</div>

Angular 2/4 Alert Component

路径: /client/app/_directives/alert.component.ts
每当从警报服务接收到消息时,警报组件都会将警报消息传递给模板。 它通过订阅alertServicegetMessage()方法来返回一个Observable。

Angular 2/4 授权守卫

路径: /client/app/_guards/auth.guard.ts
授权守卫用于防止未经身份验证的用户访问受限路由,在本示例中,它被用于在app.routing.ts守卫home page 路由。有关angular 2 守卫的更多信息你可以查看thoughtram blog中的文章。

import {Injectable} from '@angular/core';
import {Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot} from '@angular/router';

@Injectable()
export class AuthGuard implements CanActivate {
    constructor(private router : Router) {}
    canActivate(route : ActivatedRouteSnapshot, state : RouterStateSnapshot) {
        if (localStorage.getItem('currentUser')) {
            // 已登录所以返回true
            return true;
        }
        // 未登录,所以跳转到登录页并且返回url
        this
            .router
            .navigate(['/login'], {
                queryParams: {
                    returnUrl: state.url
                }
            });
        return false;
    }
}

Angular 2/4 自定义 Http 服务

路径: /client/app/_helpers/custom-http.ts
自定义http服务扩展了默认的http服务以添加以下功能:

  • 它会自动将JWT令牌(如果登录)添加到所有请求的http授权头
  • 使用appConfig文件中的api url预先提交请求URL
  • 拦截来自api的401 unauthorized 响应并自动登出用户
import {Injectable} from "@angular/core";
import {
    ConnectionBackend,
    XHRBackend,
    RequestOptions,
    Request,
    RequestOptionsArgs,
    Response,
    Http,
    Headers
} from "@angular/http";
import {appConfig} from '../app.config';

import {Observable} from "rxjs/Observable";
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/throw';
@Injectable()

export class CustomHttp extends Http {
    constructor(backend : ConnectionBackend, defaultOptions : RequestOptions) {
        super(backend, defaultOptions);
    }
    get(url : string, options?: RequestOptionsArgs) : Observable < Response > {
        return super
            .get(appConfig.apiUrl + url, this.addJwt(options))
            .catch(this.handleError);
    }

    post(url : string, body : string, options?: RequestOptionsArgs) : Observable < Response > {
        return super
            .post(appConfig.apiUrl + url, body, this.addJwt(options))
            .catch(this.handleError);
    }

    put(url : string, body : string, options?: RequestOptionsArgs) : Observable < Response > {
        return super
            .put(appConfig.apiUrl + url, body, this.addJwt(options))
            .catch(this.handleError);
    }

    delete(url : string, options?: RequestOptionsArgs) : Observable < Response > {
        return super
            .delete(appConfig.apiUrl + url, this.addJwt(options))
            .catch(this.handleError);
    }

    // 私有的 helper 方法
    private addJwt(options?: RequestOptionsArgs) : RequestOptionsArgs {
        // 确保请求options和headers不为null
        options = options || new RequestOptions();
        options.headers = options.headers || new Headers();
        // 添加jwt token的authorization header
        let currentUser = JSON.parse(localStorage.getItem('currentUser'));
        if (currentUser && currentUser.token) {
            options
                .headers
                .append('Authorization', 'Bearer ' + currentUser.token);
        }
        return options;
    }

    private handleError(error : any) {
        if (error.status === 401) {
            // 返回 401 unauthorized 所以登出用户
            window.location.href = '/login';
        }
        return Observable.throw(error._body);
    }

}
export function customHttpFactory(xhrBackend : XHRBackend, requestOptions : RequestOptions) : Http {
    return new CustomHttp(xhrBackend, requestOptions);
}

export let customHttpProvider = {
    provide: Http,
    useFactory: customHttpFactory,
    deps: [XHRBackend, RequestOptions]
};

Angular 2/4 User Model

路径: /client/app/_models/user.ts
这是一个定义用户属性的Model

export class User {
    _id: string;
    username: string;
    password: string;
    firstName: string;
    lastName: string;
}

Angular 2/4 Alert Service

路径: /client/app/_services/alert.service.ts
Alert Service使应用程序中的任何组件能够通过Alert组件在页面顶部显示警报消息。

它具有显示成功和错误消息的方法,以及一个getMessage()方法,返回一个Observable,该Alert组件订阅于每当需要显示消息的时候( the alert component to subscribe to notifications for whenever a message should be displayed.)。

import {Injectable} from '@angular/core';
import {Router, NavigationStart} from '@angular/router';
import {Observable} from 'rxjs';
import {Subject} from 'rxjs/Subject';

@Injectable()
export class AlertService {
    private subject = new Subject < any > ();
    private keepAfterNavigationChange = false;
    constructor(private router : Router) {
        //在路由变化时清除警告信息
        router
            .events
            .subscribe(event => {
                if (event instanceof NavigationStart) {
                    if (this.keepAfterNavigationChange) {
                        //只保持一个地址变化???
                        this.keepAfterNavigationChange = false;
                    } else {
                        //清除警告信息
                        this
                            .subject
                            .next();
                    }
                }
            })

    }

    success(message : string, keepAfterNavigationChange = false) {
        this.keepAfterNavigationChange = keepAfterNavigationChange;
        this
            .subject
            .next({type: 'success', text: message});
    }

    error(message : string, keepAfterNavigationChange = false) {
        this.keepAfterNavigationChange = keepAfterNavigationChange;
        this
            .subject
            .next({type: 'error', text: message});
    }

    getMessage() : Observable < any > {
        return this
            .subject
            .asObservable();
    }
}

Angular 2/4 Authentication Service

路径: /client/app/_services/authentication.service.ts
Authentication Service用于登录和注销应用程序,登陆后用户将凭据发送到api并查看JWT令牌的响应,如果有令牌则意味着认证成功,添加包含令牌的用户详细信息到local storage。
将用户详细信息储存到local storage,以便用户在刷新浏览器以及在浏览器会话之间保持登录状态,直到用户注销。如果您不希望用户在刷新或会话之间保存登录,则可将用户详细信息存储在session storage或者Authentication Service的属性之类的较不持久的位置来轻松更改此行为。

import {Injectable} from '@angular/core';
import {Http, Headers, Response} from '@angular/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';

@Injectable()
export class AuthenticationService {
    constructor(private http : Http) {}
    login(username : string, password : string) {
        return this
            .http
            .post('users/authenticate', {
                username: username,
                password: password
            })
            .map((response : Response) => {
                // 如果返回jwt token则登录成功
                let user = response.json();
                if (user && user.token) {
                    //在local storage中存储用户详细信息和jwt token来在页面刷新之间保持用户登录
                    localStorage.setItem('currentUser', JSON.stringify(user));
                }
                return user;
            })
    }
    logout() {
        // 从local storage中删除user来登出
        localStorage.removeItem('currentUser');
    }
}

Angular 2/4 User Service

路径: /client/app/_services/user.service.ts
User Service包含一组通过api管理用户的CRUD方法。

import {Injectable} from '@angular/core';
import {Http, Headers, RequestOptions, Response} from '@angular/http';

import {User} from '../_models/index';

@Injectable()
export class UserService {
    constructor(private http : Http) {}
    getAll() {
        return this
            .http
            .get('/users')
            .map((response : Response) => response.json());
    }
    getById(_id : string) {
        return this
            .http
            .get('/users/' + _id)
            .map((response : Response) => response.json());
    }
    create(user : User) {
        return this
            .http
            .post('/users/register', user);
    }
    update(user : User) {
        return this
            .http
            .put('/users/' + user._id, user);
    }
    delete(_id : string) {
        return this
            .http
            .delete('/users/' + _id);
    }

}

Angular 2/4 Home Component 模板

路径: /client/app/home/home.component.html
Home Component 模板包含用于显示简单的欢迎消息,用户列表和注销链接的html和Angular 2模板语法。

<div class="col-md-6 col-md-offset-3">
    <h1>Hi {{currentUser.firstName}}!</h1>
    <p>You're logged in with the MEAN Stack & Angular 2!!</p>
    <h3>All registered users:</h3>
    <ul>
        <li *ngFor="let user of users">
            {{user.username}} ({{user.firstName}} {{user.lastName}})
            - <a (click)="deleteUser(user._id)">Delete</a>
        </li>
    </ul>
    <p><a [routerLink]="['/login']">Logout</a></p>
</div>

Angular 2/4 Home Component

路径: /client/app/home/home.component.ts
Home Component从local storage 中获取当前用户,并从User Service中获取所有用户,填充到模板中。

import {Component, OnInit} from '@angular/core';

import {User} from '../_models/index';
import {UserService} from '../_services/index';
@Component({moduleId: module.id, templateUrl: 'home.component.html'})

export class HomeComponent implements OnInit {
    currentUser : User;
    users : User[];
    constructor(private userService : UserService) {
        this.currentUser = JSON.parse(localStorage.getItem('currentUser'));
    }
    ngOnInit() {
        this.loadAllUsers();
    }
    deleteUser(_id : string) {
        this
            .userService
            .delete(_id)
            .subscribe(() => {
                this.loadAllUsers()
            });
    }
    private loadAllUsers() {
        this
            .userService
            .getAll()
            .subscribe(users => {
                this.users = users
            });
    }
}

Angular 2/4 Login Component 模板

路径: /client/app/login/login.component.html
登录组件模板包含一个具有用户名和密码字段的登录表单,当单机提交按钮时,它会显示无效字段的验证信息。只要表单有效,

<div class="col-md-6 col-md-offset-3">
  <h2>Login</h2>
  <form name="form" (ngSubmit)="f.form.valid && login()" #f="ngForm" novalidate>
    <div class="form-group" [ngClass]="{ 'has-error': f.submitted && !username.valid }">
      <label for="username">Username</label>
      <input type="text" class="form-control" name="username" [(ngModel)]="model.username" #username="ngModel" required />
      <div *ngIf="f.submitted && !username.valid" class="help-block">Username is required</div>
    </div>
    <div class="form-group" [ngClass]="{ 'has-error': f.submitted && !password.valid }">
      <label for="password">Password</label>
      <input type="password" class="form-control" name="password" [(ngModel)]="model.password" #password="ngModel" required />
      <div *ngIf="f.submitted && !password.valid" class="help-block">Password is required</div>
    </div>
    <div class="form-group">
      <button [disabled]="loading" class="btn btn-primary">Login</button>
      <img *ngIf="loading" src="data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA=="
      />
      <a [routerLink]="['/register']" class="btn btn-link">Register</a>
    </div>
  </form>
</div>

Angular 2/4 Login Component

路径: /client/app/login/login.component.ts
登录组件使用身份验证服务来登录和注销应用程序,它在初始化(ngOnInit)时自动登出用户,因此登录页面也可用于注销。

import {Component, OnInit} from '@angular/core';
import {Router, ActivatedRoute} from '@angular/router';

import {AlertService, AuthenticationService} from '../_services/index';

@Component({moduleId: module.id, templateUrl: 'login.component.html'})

export class LoginComponent implements OnInit {
    model : any = {};
    loading = false;
    returnUrl : string;

    constructor(private route : ActivatedRoute, private router : Router, private authenticationService : AuthenticationService, private alertService : AlertService) {}
    ngOnInit() {
        //重置登录状态
        this
            .authenticationService
            .logout();
        //从路由参数中获取返回的url,或者默认为“/”
        this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
    }

    login() {
        this.loading = true;
        this
            .authenticationService
            .login(this.model.username, this.model.password)
            .subscribe(data => {
                this
                    .router
                    .navigate([this.returnUrl]);
            }, error => {
                this
                    .alertService
                    .error(error);
                this.loading = false;
            })
    }
}

Angular 2/4 Register Component 模板

路径: /client/app/register/register.component.html
注册组件模板包含一个简单的注册表单,其中包含名字,姓氏,用户名和密码等字段。当单击提交按钮时,它会显示无效字段的验证消息。如果表单有限,则调用register()方法来提交。

<div class="col-md-6 col-md-offset-3">
  <h2>Register</h2>
  <form name="form" (ngSubmit)="f.form.valid && register()" #f="ngForm" novalidate>
    <div class="form-group" [ngClass]="{ 'has-error': f.submitted && !username.valid }">
      <label for="firstName">First Name</label>
      <input type="text" class="form-control" name="firstName" [(ngModel)]="model.firstName" #firstName="ngModel" required />
      <div *ngIf="f.submitted && !firstName.valid" class="help-block">First Name is required</div>
    </div>
    <div class="form-group" [ngClass]="{ 'has-error': f.submitted && !username.valid }">
      <label for="lastName">Last Name</label>
      <input type="text" class="form-control" name="lastName" [(ngModel)]="model.lastName" #lastName="ngModel" required />
      <div *ngIf="f.submitted && !lastName.valid" class="help-block">Last Name is required</div>
    </div>
    <div class="form-group" [ngClass]="{ 'has-error': f.submitted && !username.valid }">
      <label for="username">Username</label>
      <input type="text" class="form-control" name="username" [(ngModel)]="model.username" #username="ngModel" required />
      <div *ngIf="f.submitted && !username.valid" class="help-block">Username is required</div>
    </div>
    <div class="form-group" [ngClass]="{ 'has-error': f.submitted && !password.valid }">
      <label for="password">Password</label>
      <input type="password" class="form-control" name="password" [(ngModel)]="model.password" #password="ngModel" required />
      <div *ngIf="f.submitted && !password.valid" class="help-block">Password is required</div>
    </div>
    <div class="form-group">
      <button [disabled]="loading" class="btn btn-primary">Register</button>
      <img *ngIf="loading" src="data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA=="
      />
      <a [routerLink]="['/login']" class="btn btn-link">Cancel</a>
    </div>
  </form>
</div>

Angular 2/4 Register Component

路径: /client/app/register/register.component.ts
Register Component只有一个register()方法,当注册表单被用户提交后通过用户服务来创建新的用户。

import {Component} from '@angular/core';
import {Router} from '@angular/router';

import {AlertService, UserService} from '../_services/index';

@Component({moduleId: module.id, templateUrl: 'register.component.html'})

export class RegisterComponent {
    model : any = {};
    loading = false;
    constructor(private router : Router, private userService : UserService, private alertService : AlertService) {}
    register() {
        this.loading = true;
        this
            .userService
            .create(this.model)
            .subscribe(data => {
                this
                    .alertService
                    .success('Registration successful', true);
                this
                    .router
                    .navigate(['/login']);
            }, error => {
                this
                    .alertService
                    .error(error);
                this.loading = false;
            });
    }
}

Angular 2/4 App Component 模板

路径: /client/app/app.component.html
App Component模板是应用程序的根组件模板,它包括根据当前路由显示每个视图内容的路由插座指令,以及用于从系统中任何位置显示alert信息的alert指令。

<!-- main app container -->
<div class="jumbotron">
  <div class="container">
    <div class="col-sm-8 col-sm-offset-2">
      <alert></alert>
      <router-outlet></router-outlet>
    </div>
  </div>
</div>

<!-- credits -->
<div class="text-center">
  <p>
    <a href="http://jasonwatmore.com/post/2017/02/22/mean-with-angular-2-user-registration-and-login-example-tutorial" target="_top">MEAN Stack with Angular 2 - User Registration and Login Example & Tutorial</a>
  </p>
  <p>
    <a href="http://jasonwatmore.com" target="_top">JasonWatmore.com</a>
  </p>
</div>

Angular 2/4 App Component

路径: /client/app/app.component.ts
App Component是应用程序的根组件,它定义了根标签<app></app>和它的selector属性。

moduleId属性用于为templateUrl设置为允许相对路径。

import { Component } from '@angular/core';
 
@Component({
    moduleId: module.id,
    selector: 'app',
    templateUrl: 'app.component.html'
})
 
export class AppComponent { }

Angular 2/4 App Config

路径: /client/app/app.config.ts
App Config文件用于存储应用配置变量(如api url),把他们单独的放在一起可以使任何组件都可以轻易地引入它。在本例中,它被用于User Service 和Authentication Service。

export const appConfig = {
    apiUrl: 'http://localhost:4000'
};

Angular 2/4 App Module

路径: /client/app/app.module.ts
App Module定义了应用程序的根模块以及有关模块的元数据。有关Angular 2的更多信息,请查看官方文档

import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {FormsModule} from '@angular/forms';
import {HttpModule} from '@angular/http';

import {AppComponent} from './app.component';
import {routing} from './app.routing';

import {customHttpProvider} from './_helpers/index';
import {AlertComponent} from './_directives/index';
import {AuthGuard} from './_guards/index';
import {AlertService, AuthenticationService, UserService} from './_services/index';
import {HomeComponent} from './home/index';
import {LoginComponent} from './login/index';
import {RegisterComponent} from './register/index';

@NgModule({
  imports: [
    BrowserModule, FormsModule, HttpModule, routing
  ],
  declarations: [
    AppComponent, AlertComponent, HomeComponent, LoginComponent, RegisterComponent
  ],
  providers: [
    customHttpProvider, AuthGuard, AlertService, AuthenticationService, UserService
  ],
  bootstrap: [AppComponent]
})

export class AppModule {}

Angular 2/4 App Routing

路径: /client/app/app.routing.ts
应用程序路由文件用来定义应用程序的路由,每个路由包含路径和关联组件,通过将AuthGuard传到路由的canActivate属性来保护主路由。

import { Routes, RouterModule } from '@angular/router';
 
import { HomeComponent } from './home/index';
import { LoginComponent } from './login/index';
import { RegisterComponent } from './register/index';
import { AuthGuard } from './_guards/index';
 
const appRoutes: Routes = [
    { path: '', component: HomeComponent, canActivate: [AuthGuard] },
    { path: 'login', component: LoginComponent },
    { path: 'register', component: RegisterComponent },
 
    // otherwise redirect to home
    { path: '**', redirectTo: '' }
];
 
export const routing = RouterModule.forRoot(appRoutes);

Angular 2/4 Main (Bootstrap) File

路径: /client/app/main.ts
主文件是通过Angular启动和引导应用程序使用的入口点。
```js
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app.module';

platformBrowserDynamic().bootstrapModule(AppModule);

Comments
Write a Comment