Angular 从0到1 (三)

日期: 2019-12-06 15:19 浏览次数 :

RESTful API在Web项目开支中不感到奇选择,本文针对Go语言怎样一步步实现RESTful JSON API进行传授, 此外也会波及到RESTful设计方面的话题。

In this post, we will not only cover how to use Go to create a RESTful JSON API, but we will also talk about good RESTful design.

作者:王芃 wpcfan@gmail.com

只怕我们后面有采纳过多姿多彩的API, 当大家相遇设计十分不好的API的时候,几乎感到崩溃非常。希望经过本文之后,能对统筹精美的RESTful API有叁个开首认知。

What is a JSON API?

JSON API 是数量人机联作标准,用以定义客商端如何获取与修正能源,以致服务器怎么样响应对应央浼。JSON API设计用来最小化须求的数据,以致顾客端与劳动器间传输的数据量。通过遵从合作的预约,能够拉长开垦效能,利用更广阔的工具,基于 JSON API 的客商端仍然为能够够丰裕利用缓存,以进级性能。

示例:

{
  "links": {
    "posts.author": {
      "href": "http://example.com/people/{posts.author}",
      "type": "people"
    },
    "posts.comments": {
      "href": "http://example.com/comments/{posts.comments}",
      "type": "comments"
    }
  },
  "posts": [{
    "id": "1",
    "title": "Rails is Omakase",
    "links": {
      "author": "9",
      "comments": [ "5", "12", "17", "20" ]
    }
  }]
}

第一节:初识Angular-CLI
其次节:登入组件的构建
其三节:创建三个待办事项应用
首节:演化!模块化你的应用
第五节:多客户版本的待办事项应用
第六节:使用第三方样式库及模块优化用
第七节:给组件带给生机
Rx--隐藏在Angular 2.x中利剑
Redux你的Angular 2应用
第八节:查缺补漏大合集(上)
第九节:查缺补漏大合集(下卡塔尔国

JSON API是什么?

起步八个RESTful服务

$ go run main.go

$ curl http://localhost:8080
Hello,"/"

package main

import (
    "fmt"
    "html"
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
    })

    log.Fatal(http.ListenAndServe(":8080", nil))

}

其三节:创立二个待办事项应用

那风度翩翩章大家会树立三个更头晕目眩的待办事项应用,当然大家的记名效用也还保存,这样的话我们的运用就有了多少个相对独立的效能模块。现在的web应用依据差异的法力跳转到区别的机能页面。但当下前端的趋向是开采三个SPA(Single Page Application 单页应用),所以其实大家相应把这种跳转叫视图切换:遵照分裂的门径展现分歧的构件。那我们怎么处理这种视图切换呢?幸运的是,我们没有必要寻觅第三方组件,Angular官方内建了温馨的路由模块。

JSON从前,非常多网站都由此XML进行数据调换。假使在行使过XML之后,再接触JSON, 无可置疑,你会以为世界多么美好。这里不深切JSON API的牵线,风野趣能够参照他事他说加以考察jsonapi。

追加路径分发成效

路径又称"终点"(endpoint),表示API的具体网址。在RESTful结构中,每一种网站代表后生可畏种财富(resource)。
其三方组件(Gorilla Mux package): “github.com/gorilla/mux”

package main

import (
    "fmt"
    "log"
    "net/http"
    "github.com/gorilla/mux"
)

func main() {
    router := mux.NewRouter().StrictSlash(true)
    router.HandleFunc("/", Index)
    router.HandleFunc("/todos", TodoIndex)
    router.HandleFunc("/todos/{todoId}", TodoShow)

    log.Fatal(http.ListenAndServe(":8080", router))
}

func Index(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Welcome!")
}

func TodoIndex(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Todo Index!")
}

func TodoShow(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    todoId := vars["todoId"]
    fmt.Fprintln(w, "Todo show:", todoId)
}

寻访测量检验:

$ curl http://localhost:8080/todo
404 page not found
$ curl http://localhost:8080/todos
Todo Index! ,"/todos"
$ curl http://localhost:8080/todos/{123}
TodoShow: ,"123"

建立routing的步骤

由于大家要以路由情势显得器件,建构路由前,让我们先把srcappapp.component.html中的<app-login></app-login>删掉。

  • 第一步:在src/index.html中内定基准路线,即在<head>中加入<base href="/">,那个是指向你的index.html所在的路线,浏览器也会借助那几个路子下载css,图像和js文件,所以请将以此讲话放在 head 的最上方。
  • 第二步:在src/app/app.module.ts中引入RouterModule:import { RouterModule } from '@angular/router';
  • 其三步:定义和布局路由数组,大家权且只为login来定义路由,依旧在src/app/app.module.ts中的imports中
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    RouterModule.forRoot([
      {
        path: 'login',
        component: LoginComponent
      }
    ])
  ],

用心到那么些方式和任何的举个例子BrowserModule、FormModule和HTTPModule展现情势好像不太相通,这里解释一下,forRoot其实是二个静态的工厂方法,它回到的依然是Module,上边包车型大巴是Angular API文书档案给出的RouterModule.forRoot的定义。

forRoot(routes: Routes, config?: ExtraOptions) : ModuleWithProviders

何以叫forRoot呢?因为这些路由定义是使用在动用根部的,你可能猜到了还应该有一个厂子方法叫forChild,后边大家会详细讲。接下来大家看一下forRoot选择的参数,参数看起来是二个数组,每一个数组元素是二个{path: 'xxx', component: XXXComponent}本条样子的指标。那几个数组就叫做路由定义(RouteConfig)数组,各种数组成分就叫路由定义,近期我们唯有三个路由定义。路由定义这几个目的包涵若干品质:

  • path:路由器会用它来相配路由中钦命的不二秘籍和浏览器地址栏中的当前路径,如 /login 。
  • component:导航到此路由时,路由器供给创建的机件,如 LoginComponent
  • redirectTo:重定向到有个别path,使用情状的话,比如在客户输入空头支票的路线时重定向到首页。
  • pathMatch:路线的字符相称计策
  • children:子路由数组
    运作一下,我们会意识出错了
    图片 1 image_1b0hgdsiu87n1lha1kcahl51ckb9.png-233.2kB
这个错误看上去应该是对于''没有找到匹配的route,这是由于我们只定义了一个'login',我们再试试在浏览器地址栏输入:`http://localhost:4200/login`。这次仍然出错,但错误信息变成了下面的样子,意思是我们没有找到一个outlet去加载LoginComponent。对的,这就引出了router
outlet的概念,如果要显示对应路由的组件,我们需要一个插头(outlet)来装载组件。
error_handler.js:48EXCEPTION: Uncaught (in promise): Error: Cannot find primary outlet to load 'LoginComponent'
Error: Cannot find primary outlet to load 'LoginComponent'
    at getOutlet (http://localhost:4200/main.bundle.js:66161:19)
    at ActivateRoutes.activateRoutes (http://localhost:4200/main.bundle.js:66088:30)
    at http://localhost:4200/main.bundle.js:66052:19
    at Array.forEach (native)
    at ActivateRoutes.activateChildRoutes (http://localhost:4200/main.bundle.js:66051:29)
    at ActivateRoutes.activate (http://localhost:4200/main.bundle.js:66046:14)
    at http://localhost:4200/main.bundle.js:65787:56
    at SafeSubscriber._next (http://localhost:4200/main.bundle.js:9000:21)
    at SafeSubscriber.__tryOrSetError (http://localhost:4200/main.bundle.js:42013:16)
    at SafeSubscriber.next (http://localhost:4200/main.bundle.js:41955:27)

上面大家把<router-outlet></router-outlet>写在srcappapp.component.html的最终,地址栏输入http://localhost:4200/login重复看看浏览器中的效果啊,大家的使用应该平常显示了。但即使输入http://localhost:4200时仍是有拾分出现的,我们须要增添二个路由定义来管理。输入http://localhost:4200时相对于根路径的path应该是空,即''。而小编辈那儿希望将客户还是指导到登入页面,那正是redirectTo: 'login'的作用。pathMatch: 'full'的意思是必得完全相符路线的需要,也便是说http://localhost:4200/1是不会协作到这一个法则的,必须严苛是http://localhost:4200

    RouterModule.forRoot([
      {
        path: '',
        redirectTo: 'login',
        pathMatch: 'full'
      },
      {
        path: 'login',
        component: LoginComponent
      }
    ])

当心路径配置的顺序是可怜关键的,Angular2使用“先相配优先”的尺码,也正是说假若叁个路径能够同不平日候包容多少个门路配置的规规矩矩的话,以率先个极其的准绳为准。

只是今后还会有一点小不爽,正是直接在app.modules.ts中定义路线并不是很好的秘籍,因为随着路线定义的深入骨髓,那生龙活虎部分最佳照旧用单独的文件来定义。今后大家新建三个文本srcappapp.routes.ts,将地方在app.modules.ts中定义的路线删除并在app.routes.ts中另行定义。

import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from './login/login.component';

export const routes: Routes = [
  {
    path: '',
    redirectTo: 'login',
    pathMatch: 'full'
  },
  {
    path: 'login',
    component: LoginComponent
  }
];

export const routing = RouterModule.forRoot(routes);

接下去大家在app.modules.ts中引入routing,import { routing } from './app.routes';,然后在imports数组里增加routing,今后我们的app.modules.ts看起来是上面那几个样子。

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

import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';
import { AuthService } from './core/auth.service';
import { routing } from './app.routes';

@NgModule({
  declarations: [
    AppComponent,
    LoginComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    routing
  ],
  providers: [
    {provide: 'auth',  useClass: AuthService}
    ],
  bootstrap: [AppComponent]
})
export class AppModule { }

基本的Web服务器

空泛数据模型

始建多个数据模型“Todo”、“Routes”。在其他语言中,使用类(class)实现。
在Go语言中,未有class,必需使用构造(struct卡塔尔。

Todo.go

package main

import "time"

type Todo struct {
      Id        int       `json:"id"`
      Name      string    `json:"name"`
      Completed bool      `json:"completed"`
      Due       time.Time `json:"due"`
}

type Todos []Todo

Routes.go

package main

import (
    "net/http"
    "github.com/gorilla/mux"
)

type Route struct {
    Name        string
    Method      string
    Pattern     string
    HandlerFunc http.HandlerFunc
}

type Routes []Route

让待办事项变得有意义

前天大家来规划一下根路线'',对应根路径大家想建设构造多个todo组件,那么我们接收ng g c todo来生成组件,然后在app.routes.ts中插手路由定义,对于根路线大家不再须要重定向到登入了,我们把它改写成重定向到todo。

export const routes: Routes = [
  {
    path: '',
    redirectTo: 'todo',
    pathMatch: 'full'
  },
  {
    path: 'todo',
    component: TodoComponent
  },
  {
    path: 'login',
    component: LoginComponent
  }
];

在浏览器中键入http://localhost:4200能够看到自动跳转到了todo路线,而且咱们的todo组件也展现出来了。

图片 2

image_1b0k2ba0d1qqraa51mj51hpdpeo9.png-81kB

大家意在的Todo页面应该有贰个输入待办事项的输入框和八个人展览示待办事项情况的列表。那么大家先来定义一下todo的布局,todo应该有二个id用来唯一标记,还应当有二个desc用来说述这些todo是干什么的,再有一个completed用来标记是还是不是早就做到。好了,大家来创立这么些todo模型吧,在todo文件夹下新建三个文书todo.model.ts

export class Todo {
  id: number;
  desc: string;
  completed: boolean;
}

接下来我们应该退换一下todo组件了,引进刚刚确立好的todo对象,並且创建贰个todos数组作为持有todo的集合,二个desc是当下加上的新的todo的剧情。当然大家还亟需叁个addTodo方法把新的todo加到todos数组中。这里大家权且写二个破绽百出的本子。

import { Component, OnInit } from '@angular/core';
import { Todo } from './todo.model';

@Component({
  selector: 'app-todo',
  templateUrl: './todo.component.html',
  styleUrls: ['./todo.component.css']
})
export class TodoComponent implements OnInit {
  todos: Todo[] = [];
  desc = '';
  constructor() { }

  ngOnInit() {
  }

  addTodo(){
    this.todos.push({id: 1, desc: this.desc, completed: false});
    this.desc = '';
  }
}

接下来我们改换一下srcapptodotodo.component.html

<div>
  <input type="text" [(ngModel)]="desc" (keyup.enter)="addTodo()">
  <ul>
    <li *ngFor="let todo of todos">{{ todo.desc }}</li>
  </ul>
</div>

如下边代码所示,大家树立了一个文本输入框,这几个输入框的值应该是新todo的叙说(desc),大家想在客户按了回车键后举办增多操作((keyup.enter)="addTodo())。由于todos是个数组,所以大家选用三个循环将数组内容展现出来(<li *ngFor="let todo of todos">{{ todo.desc }}</li>)。好了让我们赏识一下胜果吧

图片 3

image_1b0kgg9mnppf16pkip81b2hhbrm.png-90.1kB

风度翩翩旦我们还记得以前提到的作业逻辑应该献身单独的service中,大家还能够做的更加好一些。在todo文件夹内创造TodoService:ng g s todotodo。下边的例子中装有创立的todo都以id为1的,这显明是三个大bug,大家看一下怎么管理。不足为奇的不重复id创立形式有二种,二个是搞二个自增进数列,另四个是使用擅自生成风华正茂组不恐怕再度的字符系列,司空见惯的便是UUID了。大家来引进一个uuid的包:npm i --save angular2-uuid,由于那些包中已经包罗了用于typescript的定义文件,这里就奉行那二个下令就丰裕了。由于此时Todo对象的id风姿洒脱度是字符型了,请校正其宣称为id: string;
下一场改进service成上面包车型客车样本:

import { Injectable } from '@angular/core';
import {Todo} from './todo.model';
import { UUID } from 'angular2-uuid';

@Injectable()
export class TodoService {

  todos: Todo[] = [];

  constructor() { }

  addTodo(todoItem:string): Todo[] {
    let todo = {
      id: UUID.UUID(),
      desc: todoItem,
      completed: false
    };
    this.todos.push(todo);
    return this.todos;
  }
}

自然大家还要把组件中的代码改成接收service的

import { Component, OnInit } from '@angular/core';
import { Todo } from './todo.model';
import { TodoService } from './todo.service';

@Component({
  selector: 'app-todo',
  templateUrl: './todo.component.html',
  styleUrls: ['./todo.component.css'],
  providers:[TodoService]
})
export class TodoComponent implements OnInit {
  todos: Todo[] = [];
  desc = '';
  constructor(private service:TodoService) { }

  ngOnInit() {
  }

  addTodo(){
    this.todos = this.service.addTodo(this.desc);
    this.desc = '';
  }
}

为了能够清晰的来看大家的果实,我们为chrome浏览器装贰个插件,在chrome的地点栏中输入chrome://extensions,拉到最尾巴部分会看出三个“获取更加的多扩张程序”的链接,点击这么些链接然后寻觅“Augury”,安装即可。安装好后,按F12调出开垦者工具,里面现身一个叫“Augury”的tab。

图片 4

image_1b0kr7gpn17td7v1p4s1qucuu313.png-273.8kB

咱俩得以见到id此时被设置成了后生可畏串字符,这几个就是UUID了。

从根本上讲,RESTful服务首先是Web服务。 因而大家能够先看看Go语言中基本的Web服务器是哪些促成的。上面例子完毕了叁个轻易的Web服务器,对于其余必要,服务器都响应央求的U凯雷德L回去。

重构:Handlers & Router

Handlers.go

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "github.com/gorilla/mux"
)

func Index(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Welcome!")
}

func TodoIndex(w http.ResponseWriter, r *http.Request) {
    todos := Todos{
        Todo{Name: "Write presentation"},
        Todo{Name: "Host meetup"},
    }

    if err := json.NewEncoder(w).Encode(todos); err != nil {
        panic(err)
    }
}

func TodoShow(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    todoId := vars["todoId"]
    fmt.Fprintln(w, "Todo show:", todoId)
}

Router.go

package main

import (
    "net/http"
    "github.com/gorilla/mux"
)

func NewRouter() *mux.Router {
    router := mux.NewRouter().StrictSlash(true)
    for _, route := range routes {
        var handler http.Handler
        handler = route.HandlerFunc
        handler = Logger(handler, route.Name)

        router.
            Methods(route.Method).
            Path(route.Pattern).
            Name(route.Name).
            Handler(handler)

    }
    return router
}

建构模拟web服务和异步操作

实在的支出中大家的service是要和服务器api举办相互作用的,并不是前些天那样总结的操作数组。但难点来了,未来从未web服务啊,难道真要团结开拓一个啊?答案是足以做个假的,假作真时真亦假。大家在支付进度中日常会遇见那类难题,等待后端同学的进度是异常疼心的。所以Angular内建提供了三个方可飞速建构测量试验用web服务的格局:内存(in-memory卡塔尔(قطر‎ 服务器。

经常的话,你须求领悟自身对服务器的梦想是如何,期望它回到什么样的数码,有了这些数目吧,我们就足以慈爱超级快的树立二个内部存款和储蓄器服务器了。拿那一个例子来看,大家或者需求二个这么的目的

class Todo {
  id: string;
  desc: string;
  completed: boolean;
}

对应的JSON应该是那般的

{
  "data": [
    {
      "id": "f823b191-7799-438d-8d78-fcb1e468fc78",
      "desc": "blablabla",
      "completed": false
    },
    {
      "id": "c316a3bf-b053-71f9-18a3-0073c7ee3b76",
      "desc": "tetssts",
      "completed": false
    },
    {
      "id": "dd65a7c0-e24f-6c66-862e-0999ea504ca0",
      "desc": "getting up",
      "completed": false
    }
  ]
}

率先大家供给设置angular-in-memory-web-api,输入npm install --save angular-in-memory-web-api
然后在Todo文件夹下创立二个文件srcapptodotodo-data.ts

import { InMemoryDbService } from 'angular-in-memory-web-api';
import { Todo } from './todo.model';

export class InMemoryTodoDbService implements InMemoryDbService {
  createDb() {
    let todos: Todo[] = [
      {id: "f823b191-7799-438d-8d78-fcb1e468fc78", desc: 'Getting up', completed: true},
      {id: "c316a3bf-b053-71f9-18a3-0073c7ee3b76", desc: 'Go to school', completed: false}
    ];
    return {todos};
  }
}

能够看出,我们制造了二个贯彻InMemoryDbService的内部存款和储蓄器数据库,这些数据库其实相当于把数组传入进去。接下来大家要转移srcappapp.module.ts,参预类援用和呼应的模块表明:

import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryTodoDbService } from './todo/todo-data';

下一场在imports数组中紧挨着HttpModule加上InMemoryWebApiModule.forRoot(InMemoryTodoDbService),

今昔大家在service中试着调用大家的“假web服务”吧

import { Injectable } from '@angular/core';
import { Http, Headers } from '@angular/http';
import { UUID } from 'angular2-uuid';

import 'rxjs/add/operator/toPromise';

import { Todo } from './todo.model';

@Injectable()
export class TodoService {

  //定义你的假WebAPI地址,这个定义成什么都无所谓
  //只要确保是无法访问的地址就好
  private api_url = 'api/todos';
  private headers = new Headers({'Content-Type': 'application/json'});

  constructor(private http: Http) { }

  // POST /todos
  addTodo(desc:string): Promise<Todo> {
    let todo = {
      id: UUID.UUID(),
      desc: desc,
      completed: false
    };
    return this.http
            .post(this.api_url, JSON.stringify(todo), {headers: this.headers})
            .toPromise()
            .then(res => res.json().data as Todo)
            .catch(this.handleError);
  }

  private handleError(error: any): Promise<any> {
    console.error('An error occurred', error); 
    return Promise.reject(error.message || error);
  }
}

地点的代码大家看到定义了四个api_url = 'api/todos',你也许会问那一个是怎么来的?分两局地看,api/todos中前面包车型客车api定义成怎么着都能够,但后边那一个todos是有尊重的,咱们回来看一下srcapptodotodo-data.ts重临的return {todos},这一个实际是return {todos: todos}的简约表示方式,假使我们不想让这一个后半有的是todos,我们得以写成{nahnahnah: todos}。那样的话大家改写成api_url = 'blablabla/nahnahnah'也无所谓,因为这些内部存款和储蓄器Web服务的机理是掣肘Web采访,也正是说随意什么地方都得以,内部存款和储蓄器Web服务会拦截这一个地址并分析你的伏乞是或不是满足RESTful API的渴求

大约来讲RESTful API中GET央求用于查询,PUT用于更新,DELETE用于删除,POST用于增多。比如假如url是api/todos,那么

  • 查询全数待办事项:以GET方法访谈api/todos
  • 询问单个待办事项:以GET方法访谈api/todos/id,比方id是1,那么访问api/todos/1
  • 立异有个别待办事项:以PUT方法访问api/todos/id
  • 删除某些待办事项:以DELETE方法访谈api/todos/id
  • 日增多少个待办事项:以POST方法访谈api/todos

在service的布局函数中我们注入了Http,而angular的Http封装了绝大非常多大家必要的方法,比如例子中的扩充多少个todo,大家就调用this.http.post(url, body, options),下边代码中的.post(this.api_url, JSON.stringify(todo), {headers: this.headers})意义是:结构贰个POST类型的HTTP央求,其访谈的url是this.api_url,request的body是二个JSON(把todo对象调换来JSON),在参数配置中大家配备了request的header。

这些供给发出后归来的是三个Observable(可观望对象),我们把它调换到Promise然后管理res(Http Response)。Promise提供异步的管理,注意到then中的写法,那么些和大家古板一编写程写法超级小学一年级样,叫做lambda表明式,相当于是贰个无名氏函数,(input parameters) => expression=>前方的是函数的参数,前面包车型大巴是函数体。

还要一点要求重申的是:在用内部存款和储蓄器Web服务时,一定要小心res.json().data中的data属性必须要有,因为内存web服务坑爹的在回来的json中加了data对象,你真正要收获的json是在此个data里面。

下一步我们来纠正Todo组件的addTodo方法以便能够采取大家新的异步http方法

  addTodo(){
    this.service
      .addTodo(this.desc)
      .then(todo => {
        this.todos = [...this.todos, todo];
        this.desc = '';
      });
  }

那其间的前半有个别应该照旧好驾驭的:this.service.addTodo(this.desc)是调用service的呼应措施而已,但后半部分是怎么鬼?...本条貌似省略号的东东是ES7中安插提供的Object Spread操作符,它的作用是将对象或数组“战胜,拍平”。这么说也许还是不懂,举事例吗:

let arr = [1,2,3];
let arr2 = [...arr]; 
arr2.push(4); 

// arr2 变成了 [1,2,3,4]
// arr 保存原来的样子

let arr3 = [0, 1, 2];
let arr4 = [3, 4, 5];
arr3.push(...arr4);
// arr3变成了[0, 1, 2, 3, 4, 5]

let arr5 = [0, 1, 2];
let arr6 = [-1, ...arr5, 3];
// arr6 变成了[-1, 0, 1, 2, 3]

所以啊大家地方的this.todos = [...this.todos, todo];相当于为todos扩充一个新因素,和push很像,这怎么不用push呢?因为那样结构出来的指标是崭新的,并非引用的,在现世编制程序中多个斐然的自由化是毫不在经过中改动输入的参数。第二个原因是那样做会带来大家十分大的便利性和编制程序的意气风发致性。下边通过给大家的事例增加多少个职能,我们来一块回味一下。
先是改动srcapptodotodo.service.ts

//srcapptodotodo.service.ts
import { Injectable } from '@angular/core';
import { Http, Headers } from '@angular/http';
import { UUID } from 'angular2-uuid';

import 'rxjs/add/operator/toPromise';

import { Todo } from './todo.model';

@Injectable()
export class TodoService {

  private api_url = 'api/todos';
  private headers = new Headers({'Content-Type': 'application/json'});
  constructor(private http: Http) { }
  // POST /todos
  addTodo(desc:string): Promise<Todo> {
    let todo = {
      id: UUID.UUID(),
      desc: desc,
      completed: false
    };
    return this.http
            .post(this.api_url, JSON.stringify(todo), {headers: this.headers})
            .toPromise()
            .then(res => res.json().data as Todo)
            .catch(this.handleError);
  }
  // PUT /todos/:id
  toggleTodo(todo: Todo): Promise<Todo> {
    const url = `${this.api_url}/${todo.id}`;
    console.log(url);
    let updatedTodo = Object.assign({}, todo, {completed: !todo.completed});
    return this.http
            .put(url, JSON.stringify(updatedTodo), {headers: this.headers})
            .toPromise()
            .then(() => updatedTodo)
            .catch(this.handleError);
  }
  // DELETE /todos/:id
  deleteTodoById(id: string): Promise<void> {
    const url = `${this.api_url}/${id}`;
    return this.http
            .delete(url, {headers: this.headers})
            .toPromise()
            .then(() => null)
            .catch(this.handleError);
  }
  // GET /todos
  getTodos(): Promise<Todo[]>{
    return this.http.get(this.api_url)
              .toPromise()
              .then(res => res.json().data as Todo[])
              .catch(this.handleError);
  }
  private handleError(error: any): Promise<any> {
    console.error('An error occurred', error); 
    return Promise.reject(error.message || error);
  }
}

接下来更新srcapptodotodo.component.ts

import { Component, OnInit } from '@angular/core';
import { TodoService } from './todo.service';
import { Todo } from './todo.model';

@Component({
  selector: 'app-todo',
  templateUrl: './todo.component.html',
  styleUrls: ['./todo.component.css'],
  providers: [TodoService]
})
export class TodoComponent implements OnInit {
  todos : Todo[] = [];
  desc: string = '';

  constructor(private service: TodoService) {}
  ngOnInit() {
    this.getTodos();
  }
  addTodo(){
    this.service
      .addTodo(this.desc)
      .then(todo => {
        this.todos = [...this.todos, todo];
        this.desc = '';
      });
  }
  toggleTodo(todo: Todo) {
    const i = this.todos.indexOf(todo);
    this.service
      .toggleTodo(todo)
      .then(t => {
        this.todos = [
          ...this.todos.slice(0,i),
          t,
          ...this.todos.slice(i+1)
          ];
      });
  }
  removeTodo(todo: Todo) {
    const i = this.todos.indexOf(todo);
    this.service
      .deleteTodoById(todo.id)
      .then(()=> {
        this.todos = [
          ...this.todos.slice(0,i),
          ...this.todos.slice(i+1)
        ];
      });
  }
  getTodos(): void {
    this.service
      .getTodos()
      .then(todos => this.todos = [...todos]);
  }
}

履新模板文件srcapptodotodo.component.html

<section class="todoapp">
  <header class="header">
    <h1>Todos</h1>
    <input class="new-todo" placeholder="What needs to be done?" autofocus="" [(ngModel)]="desc" (keyup.enter)="addTodo()">
  </header>
  <section class="main" *ngIf="todos?.length > 0">
    <input class="toggle-all" type="checkbox">
    <ul class="todo-list">
      <li *ngFor="let todo of todos" [class.completed]="todo.completed">
        <div class="view">
          <input class="toggle" type="checkbox" (click)="toggleTodo(todo)" [checked]="todo.completed">
          <label (click)="toggleTodo(todo)">{{todo.desc}}</label>
          <button class="destroy" (click)="removeTodo(todo); $event.stopPropagation()"></button>
        </div>
      </li>
    </ul>
  </section>
  <footer class="footer" *ngIf="todos?.length > 0">

      <strong>{{todos?.length}}</strong> {{todos?.length == 1 ? 'item' : 'items'}} left

    <ul class="filters">
      <li><a href="">All</a></li>
      <li><a href="">Active</a></li>
      <li><a href="">Completed</a></li>
    </ul>
    <button class="clear-completed">Clear completed</button>
  </footer>
</section>

改良组件的css样式:srcapptodotodo.component.css

.todoapp {
    background: #fff;
    margin: 130px 0 40px 0;
    position: relative;
    box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
                0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.todoapp input::-webkit-input-placeholder {
    font-style: italic;
    font-weight: 300;
    color: #e6e6e6;
}
.todoapp input::-moz-placeholder {
    font-style: italic;
    font-weight: 300;
    color: #e6e6e6;
}
.todoapp input::input-placeholder {
    font-style: italic;
    font-weight: 300;
    color: #e6e6e6;
}
.todoapp h1 {
    position: absolute;
    top: -155px;
    width: 100%;
    font-size: 100px;
    font-weight: 100;
    text-align: center;
    color: rgba(175, 47, 47, 0.15);
    -webkit-text-rendering: optimizeLegibility;
    -moz-text-rendering: optimizeLegibility;
    text-rendering: optimizeLegibility;
}
.new-todo,
.edit {
    position: relative;
    margin: 0;
    width: 100%;
    font-size: 24px;
    font-family: inherit;
    font-weight: inherit;
    line-height: 1.4em;
    border: 0;
    color: inherit;
    padding: 6px;
    border: 1px solid #999;
    box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
    box-sizing: border-box;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}
.new-todo {
    padding: 16px 16px 16px 60px;
    border: none;
    background: rgba(0, 0, 0, 0.003);
    box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
}
.main {
    position: relative;
    z-index: 2;
    border-top: 1px solid #e6e6e6;
}
label[for='toggle-all'] {
    display: none;
}
.toggle-all {
    position: absolute;
    top: -55px;
    left: -12px;
    width: 60px;
    height: 34px;
    text-align: center;
    border: none; /* Mobile Safari */
}
.toggle-all:before {
    content: '❯';
    font-size: 22px;
    color: #e6e6e6;
    padding: 10px 27px 10px 27px;
}
.toggle-all:checked:before {
    color: #737373;
}
.todo-list {
    margin: 0;
    padding: 0;
    list-style: none;
}
.todo-list li {
    position: relative;
    font-size: 24px;
    border-bottom: 1px solid #ededed;
}
.todo-list li:last-child {
    border-bottom: none;
}
.todo-list li.editing {
    border-bottom: none;
    padding: 0;
}
.todo-list li.editing .edit {
    display: block;
    width: 506px;
    padding: 12px 16px;
    margin: 0 0 0 43px;
}
.todo-list li.editing .view {
    display: none;
}
.todo-list li .toggle {
    text-align: center;
    width: 40px;
    /* auto, since non-WebKit browsers doesn't support input styling */
    height: auto;
    position: absolute;
    top: 0;
    bottom: 0;
    margin: auto 0;
    border: none; /* Mobile Safari */
    -webkit-appearance: none;
    appearance: none;
}
.todo-list li .toggle:after {
    content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#ededed" stroke-width="3"/></svg>');
}
.todo-list li .toggle:checked:after {
    content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#bddad5" stroke-width="3"/><path fill="#5dc2af" d="M72 25L42 71 27 56l-4 4 20 20 34-52z"/></svg>');
}
.todo-list li label {
    word-break: break-all;
    padding: 15px 60px 15px 15px;
    margin-left: 45px;
    display: block;
    line-height: 1.2;
    transition: color 0.4s;
}
.todo-list li.completed label {
    color: #d9d9d9;
    text-decoration: line-through;
}
.todo-list li .destroy {
    display: none;
    position: absolute;
    top: 0;
    right: 10px;
    bottom: 0;
    width: 40px;
    height: 40px;
    margin: auto 0;
    font-size: 30px;
    color: #cc9a9a;
    margin-bottom: 11px;
    transition: color 0.2s ease-out;
}
.todo-list li .destroy:hover {
    color: #af5b5e;
}
.todo-list li .destroy:after {
    content: '×';
}
.todo-list li:hover .destroy {
    display: block;
}
.todo-list li .edit {
    display: none;
}
.todo-list li.editing:last-child {
    margin-bottom: -1px;
}
.footer {
    color: #777;
    padding: 10px 15px;
    height: 20px;
    text-align: center;
    border-top: 1px solid #e6e6e6;
}
.footer:before {
    content: '';
    position: absolute;
    right: 0;
    bottom: 0;
    left: 0;
    height: 50px;
    overflow: hidden;
    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
                0 8px 0 -3px #f6f6f6,
                0 9px 1px -3px rgba(0, 0, 0, 0.2),
                0 16px 0 -6px #f6f6f6,
                0 17px 2px -6px rgba(0, 0, 0, 0.2);
}
.todo-count {
    float: left;
    text-align: left;
}
.todo-count strong {
    font-weight: 300;
}
.filters {
    margin: 0;
    padding: 0;
    list-style: none;
    position: absolute;
    right: 0;
    left: 0;
}
.filters li {
    display: inline;
}
.filters li a {
    color: inherit;
    margin: 3px;
    padding: 3px 7px;
    text-decoration: none;
    border: 1px solid transparent;
    border-radius: 3px;
}
.filters li a:hover {
    border-color: rgba(175, 47, 47, 0.1);
}
.filters li a.selected {
    border-color: rgba(175, 47, 47, 0.2);
}
.clear-completed,
html .clear-completed:active {
    float: right;
    position: relative;
    line-height: 20px;
    text-decoration: none;
    cursor: pointer;
}
.clear-completed:hover {
    text-decoration: underline;
}
/*
    Hack to remove background from Mobile Safari.
    Can't use it globally since it destroys checkboxes in Firefox
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
    .toggle-all,
    .todo-list li .toggle {
        background: none;
    }
    .todo-list li .toggle {
        height: 40px;
    }
    .toggle-all {
        -webkit-transform: rotate(90deg);
        transform: rotate(90deg);
        -webkit-appearance: none;
        appearance: none;
    }
}
@media (max-width: 430px) {
    .footer {
        height: 50px;
    }
    .filters {
        bottom: 10px;
    }
}

更新srcstyles.css为如下

/* You can add global styles to this file, and also import other style files */
html, body {
    margin: 0;
    padding: 0;
}
button {
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
    font-size: 100%;
    vertical-align: baseline;
    font-family: inherit;
    font-weight: inherit;
    color: inherit;
    -webkit-appearance: none;
    appearance: none;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}
body {
    font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
    line-height: 1.4em;
    background: #f5f5f5;
    color: #4d4d4d;
    min-width: 230px;
    max-width: 550px;
    margin: 0 auto;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    font-weight: 300;
}
:focus {
    outline: 0;
}
.hidden {
    display: none;
}
.info {
    margin: 65px auto 0;
    color: #bfbfbf;
    font-size: 10px;
    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
    text-align: center;
}
.info p {
    line-height: 1;
}
.info a {
    color: inherit;
    text-decoration: none;
    font-weight: 400;
}
.info a:hover {
    text-decoration: underline;
}

近来大家看看成果吧,今后赏心悦目多了

图片 5

image_1b11jlmes1nithths9q1n8ijqg9.png-78.9kB

本节代码:https://github.com/wpcfan/awesome-tutorials/tree/chap03/angular2/ng2-tut

纸书出版了,比网络内容充裕充实了,接待大家订购!
京东链接:https://item.m.jd.com/product/12059091.html?from=singlemessage&isappinstalled=0

图片 6

Angular从零到风姿罗曼蒂克

第一节:初识Angular-CLI
其次节:登陆组件的构建
其三节:创设四个待办事项应用
第三节:演化!模块化你的利用
第五节:多客户版本的待办事项应用
第六节:使用第三方样式库及模块优化用
第七节:给组件带给活力
Rx--隐藏在Angular 2.x中利剑
Redux你的Angular 2应用
第八节:查缺补漏大合集(上卡塔尔(قطر‎
第九节:查缺补漏大合集(下卡塔尔国

package main

import (
  "fmt"
  "html"
  "log"
  "net/http"
)

func main() {
  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
  })

  log.Fatal(http.ListenAndServe(":8080", nil))
}

开发银行入口是或不是心旷神怡非常多!

Main.go

Main.go
package main

import (
    "log"
    "net/http"
)

func main() {
    router := NewRouter()
    log.Fatal(http.ListenAndServe(":8080", router))
}

web access:http://localhost:8080/todos

Todo Index! ,"/todos"
[
{
"id":0,
"name":"Write sth ....",
"completed":false,
"due":"0001-01-01T00:00:00
},
{
"id":1,
"name":"Host meetup ....",
"completed":false,
"due":"0001-01-01T00:00:00Z"
}
]

下面基本的web服务器使用Go标准库的三个基本函数HandleFunc和ListenAndServe。

加强效用:长久化

func TodoCreate(w http.ResponseWriter, r *http.Request) {
    var todo Todo
    //add Todo instance
}
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
  DefaultServeMux.HandleFunc(pattern, handler)
}

func ListenAndServe(addr string, handler Handler) error {
  server := &Server{Addr: addr, Handler: handler}
  return server.ListenAndServe()
}

增长效率:日志

2017/05/23 15:57:23 http: multiple response.WriteHeader calls
2017/05/23 15:57:23 GET /todos  TodoIndex   6.945807ms
2017/05/23 16:18:40 http: multiple response.WriteHeader calls
2017/05/23 16:18:40 GET /todos  TodoIndex   2.127435ms

运营方面包车型客车主导web服务,就足以平昔通过浏览器访谈

Things We Didn’t Do

1、版本调整
API版本迭代 & 跨版本能源访谈。常用做法是将版本号放在U翼虎L,较为轻松,比方:https://localhost:8080/v1/
另风流罗曼蒂克种做法是将版本号放在HTTP头音讯中。

2、授权验证:涉及到OAuth和JWT。
(1)OAuth 2.0,OAuth2 is an authentication framework,RFC 6749
OAuth2是风华正茂种授权框架,提供了风姿洒脱套详细的、可供施行的指令性应用方案。OAuth 2.0定义了多种授权格局。授权码形式(authorization code)、简化格局(implicit)、密码形式(resource owner password credentials)、客商端情势(client credentials)。

(2)JSON web tokens,JWT is an authentication protocol,RFC 7519
JWT是生机勃勃种安全左券。基本思路正是客户提供客户名和密码给认证服务器,服务器验证客商提交音讯消息的合法性;要是表明成功,会产生并赶回三个Token(令牌),客户能够接纳那么些token访问服务器上受保障的能源。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

header:定义算法(alg:ALGO路虎极光ITHM卡塔尔国和TOKEN TYPE(typ)

{
  "alg": "HS256",
  "typ": "JWT"
}

Data:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

3、 eTags:关于缓存、品质和顾客标记和追踪。

> go run basic_server.go

仿效文献

  1. 阮风流倜傥峰:RESTful API 设计指南
  2. CORY LANOU:Making a RESTful JSON API in Go,2014Nov
  3. InfoQ:使用ETags降低Web应用带宽和负载
  4. Stackoverflow:jwt vs oauth authentication
  5. OAuth 2 VS JSON Web Tokens:How to secure an API,20160605
  6. 阮一峰:理解OAuth 2.0,201405

增加路由

纵然如此标准库包罗有router, 可是本身意识许三人对它的办事原理以为很郁结。 作者在和谐的体系中利用过种种分化的第三方router库。 最值得生龙活虎提的是Gorilla Web ToolKit的mux router。

别的四个风靡的router是缘于朱利安 Schmidt的称之为httprouter的包。

package main

import (
  "fmt"
  "html"
  "log"
  "net/http"

  "github.com/gorilla/mux"
)

func main() {
  router := mux.NewRouter().StrictSlash(true)
  router.HandleFunc("/", Index)

  log.Fatal(http.ListenAndServe(":8080", router))
}

func Index(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
}

要运转方面的代码,首先选取go get获取mux router的源代码:

> go get github.com/gorilla/mux

上边代码成立了三个骨干的路由器,给诉求"/"授予Index微电脑,当顾客端必要

假设您足足用心,你会开采早先的主导web服务拜见: 'Hello, "/abc"', 不过在加多了路由之后,就一定要访问 原因比较轻便,因为大家只增添了对"/"的剖析,别的的路由都以没用路由,因而都是404。

创建一些中坚的路由

既是大家步入了路由,那么大家就能够再增加越多路由步入了。

假定我们要开创多个大旨的ToDo应用, 于是我们的代码就形成上面那样:

package main

import (
  "fmt"
  "log"
  "net/http"

  "github.com/gorilla/mux"
)

func main() {
  router := mux.NewRouter().StrictSlash(true)
  router.HandleFunc("/", Index)
  router.HandleFunc("/todos", TodoIndex)
  router.HandleFunc("/todos/{todoId}", TodoShow)

  log.Fatal(http.ListenAndServe(":8080", router))
}

func Index(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintln(w, "Welcome!")
}

func TodoIndex(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintln(w, "Todo Index!")
}

func TodoShow(w http.ResponseWriter, r *http.Request) {
  vars := mux.Vars(r)
  todoId := vars["todoId"]
  fmt.Fprintln(w, "Todo Show:", todoId)
}

在这里地大家增加了别的几个路由: todos和todos/{todoId}。

那便是RESTful API设计的上马。

请精心最终贰个路由我们给路由前边加多了二个变量叫做todoId。

如此那般就同意大家传递id给路由,而且能选择具体的记录来响应恳求。

着力模型

路由今后已经就绪,是时候成立Model了,能够用model发送和查找数据。在Go语言中,model能够使用布局体来达成,而其他语言中model平日都以接收类来促成。

package main

import (
  "time"
)

type Todo struct {
  Name   string
  Completed bool
  Due    time.Time
}

type Todos []Todo

上面大家定义了三个Todo构造体,用于表示待做项。 其余大家还定义了风流洒脱种类型Todos, 它象征待做列表,是一个数组,或然说是叁个分片。

稍后您就会看出这么会变得十分平价。

回去一些JSON

咱俩有了主导的模子,那么大家可以效仿一些敦厚的响应了。我们得以为TodoIndex模拟一些静态的多寡列表。

package main

import (
  "encoding/json"
  "fmt"
  "log"
  "net/http"

  "github.com/gorilla/mux"
)

// ...

func TodoIndex(w http.ResponseWriter, r *http.Request) {
  todos := Todos{
    Todo{Name: "Write presentation"},
    Todo{Name: "Host meetup"},
  }

  json.NewEncoder(w).Encode(todos)
}

// ...

现行咱们创立了二个静态的Todos分片来响应顾客端央浼。注意,借使您央求, 就能够获取下边包车型地铁响应: