Angular 4 快速入门

查看新版教程,请访问 Angular 6.x 快速入门

目录

  • 第一节 - Angular 简介
  • 第二节 - Angular 环境搭建
  • 第三节 - 插值表达式
  • 第四节 - 自定义组件
  • 第五节 - 常用指令简介
  • 第六节 - 事件绑定
  • 第七节 - 表单模块简介
  • 第八节 - Http 模块简介
  • 第九节 - 注入服务
  • 第十节 - 路由模块简介

第一节 Angular 简介

Angular 是什么

Angular 是由谷歌开发与维护一个开发跨平台应用程序的框架,同时适用于手机与桌面。

Angular 有什么特点

  • 基于 Angular 我们可以构建适用于所有平台的应用。比如:Web 应用、移动 Web 应用、移动应用和桌面应用等。
  • 通过 Web Worker和服务端渲染 (SSR),达到在如今Web平台上所能达到的最高渲染速度。
  • Angular 让你能够有效掌控可伸缩性。基于 RxJS、Immutable.js 和其它推送模型,能适应海量数据需求。

Angular 提供了哪些功能

  • 动态HTML
  • 强大的表单系统 (模板驱动和模型驱动)
  • 强大的视图引擎
  • 事件处理
  • 快速的页面渲染
  • 灵活的路由
  • HTTP 服务
  • 视图封装
  • AOT、Tree Shaking

Angular 与 AngularJS 有什么区别

  • 不再有ControllerScope
  • 更好的组件化及代码复用
  • 降低了学习曲线
  • 更好的移动端支持
  • 引入了 RxJSObservable
  • 引入了 Zone.js,提供更加智能的变化检测

第二节 - Angular 环境搭建

基础要求

Angular 开发环境配置方式

配置开发环境

本快速入门教程,选用第一种配置方式搭建 Angular 开发环境:

  • 使用 Git 克隆 quickstart 项目
git clone https://github.com/angular/quickstart ng4-quickstart
code ./ng4-quickstart
  • 安装项目所需依赖
npm i 
  • 验证环境是否搭建成功
npm start

第三节 - 插值表达式

在 Angular 中,我们可以使用 {{}} 插值语法实现数据绑定。

绑定普通文本

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

@Component({
  selector: 'my-app',
  template: `<h1>Hello {{name}}</h1>`,
})
export class AppComponent  {
  name = 'Angular'; 
}

绑定对象属性

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

@Component({
  selector: 'my-app',
  template: `
    <h2>大家好,我是{{name}}</h2>
    <p>我来自<strong>{{address.province}}</strong>省,
      <strong>{{address.city}}</strong>市
    </p>
  `,
})
export class AppComponent {
  name = 'Semlinker';
  address = {
    province: '福建',
    city: '厦门'
  }
}

值得一提的是,我们可以使用 Angular 内置的 json 管道,来显示对象信息:

@Component({
  selector: 'my-app',
  template: `
    ...
    <p>{{address | json}}</p>
  `,
})
export class AppComponent {
  name = 'Semlinker';
  address = {
    province: '福建',
    city: '厦门'
  }
}

第四节 - 自定义组件

在 Angular 中,我们可以通过 Component 装饰器和自定义组件类来创建自定义组件。

基础知识

定义组件的元信息

在 Angular 中,我们可以使用 Component 装饰器来定义组件的元信息:

@Component({
  selector: 'my-app', // 用于定义组件在HTML代码中匹配的标签
  template: `<h1>Hello {{name}}</h1>`, // 定义组件内嵌视图
})

定义组件类

export class AppComponent  {
  name = 'Angular'; 
}

定义数据接口

在 TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。

interface Person {
  name: string;
  age: number;
}

let semlinker: Person = {
  name: 'semlinker',
  age: 31
};

自定义组件示例

创建 UserComponent 组件

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

@Component({
    selector: 'sl-user',
    template: `
    <h2>大家好,我是{{name}}</h2>
    <p>我来自<strong>{{address.province}}</strong>省,
      <strong>{{address.city}}</strong>市
    </p>
    `
})
export class UserComponent {
    name = 'Semlinker';
    address = {
        province: '福建',
        city: '厦门'
    };
}

声明 UserComponent 组件

// ...
import { UserComponent } from './user.component';
@NgModule({
  imports:      [ BrowserModule ],
  declarations: [ AppComponent, UserComponent],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }

使用 UserComponent 组件

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

@Component({
  selector: 'my-app',
  template: `
    <sl-user></sl-user>
  `,
})
export class AppComponent {}

使用构造函数初始化数据

@Component({...})
export class UserComponent {
    name: string;
    address: any;

    constructor() {
        this.name = 'Semlinker';
        this.address = {
            province: '福建',
            city: '厦门'
        }
    }
}

接口使用示例

定义 Address 接口

interface Address {
    province: string;
    city: string;
}

使用 Address 接口

export class UserComponent {
    name: string;
    address: Address;
    // ...
}

第五节 - 常用指令简介

在 Angular 实际项目中,最常用的指令是 ngIfngFor 指令。

基础知识

ngIf 指令简介

该指令用于根据表达式的值,动态控制模板内容的显示与隐藏。它与 AngularJS 1.x 中的 ng-if 指令的功能是等价的。

ngIf 指令语法

<div *ngIf="condition">...</div>

ngFor 指令简介

该指令用于基于可迭代对象中的每一项创建相应的模板。它与 AngularJS 1.x 中的 ng-repeat 指令的功能是等价的。

ngFor 指令语法

<li *ngFor="let item of items;">...</li>

ngIf 与 ngFor 指令使用示例

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

interface Address {
    province: string;
    city: string;
}

@Component({
    selector: 'sl-user',
    template: `
    <h2>大家好,我是{{name}}</h2>
    <p>我来自<strong>{{address.province}}</strong>省,
      <strong>{{address.city}}</strong>市
    </p>
    <div *ngIf="showSkills">
        <h3>我的技能</h3>
        <ul>
            <li *ngFor="let skill of skills">
                {{skill}}
            </li>
        </ul>
    </div>
    `
})
export class UserComponent {
    name: string;
    address: Address;
    showSkills: boolean;
    skills: string[];

    constructor() {
        this.name = 'Semlinker';
        this.address = {
            province: '福建',
            city: '厦门'
        };
        this.showSkills = true;
        this.skills = ['AngularJS 1.x', 'Angular 2.x', 'Angular 4.x'];
    }
}

第六节 - 事件绑定

在 Angular 中,我们可以通过 (eventName) 的语法,实现事件绑定。

基础知识

事件绑定语法

<date-picker (dateChanged)="statement()"></date-picker>

等价于

<date-picker on-dateChanged="statement()"></date-picker>

介绍完事件绑定的语法,接下来我们来为第五节中的 UserComponent 组件,开发一个功能,即可以让用户动态控制技能信息的显示与隐藏。

事件绑定示例

@Component({
    selector: 'sl-user',
    template: `
    ...
    <button (click)="toggleSkills()">
        {{ showSkills ? "隐藏技能" : "显示技能" }}
    </button>
    ...
    `
})
export class UserComponent {
    // ...
    toggleSkills() {
        this.showSkills = !this.showSkills;
    }
}

第七节 - 表单模块简介

Angular 中有两种表单:

  • Template Driven Forms - 模板驱动式表单 (类似于 AngularJS 1.x 中的表单 )
  • Reactive Forms - 响应式表单

本小节主要介绍模板驱动式的表单,接下来我们来演示如何通过表单来为我们的之前创建的 UserComponent 组件,增加让用户自定义技能的功能。

基础知识

导入表单模块

import { FormsModule } from '@angular/forms';
// ...
@NgModule({
  imports: [BrowserModule, FormsModule],
  declarations: [AppComponent, UserComponent],
  bootstrap: [AppComponent]
})
export class AppModule { }

模板变量语法

<video #player></video> 
<button (click)="player.pause()">Pause</button>

等价于

<video ref-player></video>

表单使用示例

@Component({
    selector: 'sl-user',
    template: `
    ...
    <div *ngIf="showSkills">
        <h3>我的技能</h3>
        ...
        <form (submit)="addSkill(skill.value)">
            <label>添加技能</label>
            <input type="text" #skill>
        </form>
    </div>
    `
})
export class UserComponent {
   // ...
    addSkill(skill: string) {
        let skillStr = skill.trim();
        if (this.skills.indexOf(skillStr) === -1) {
            this.skills.push(skillStr);
        }
    }
}

第八节 - Http 模块简介

基础知识

导入 Http 模块

// ... 
import { HttpModule } from '@angular/http';

@NgModule({
  imports: [BrowserModule, FormsModule, HttpModule],
  declarations: [AppComponent, UserComponent],
  bootstrap: [AppComponent]
})
export class AppModule { }

使用 Http 服务步骤

(1) 从 @angular/http 模块中导入 Http 类

(2) 导入 RxJS 中的 map 操作符

(3) 使用 DI 方式注入 http 服务

(4) 调用 http 服务的 get() 方法,设置请求地址并发送 HTTP 请求

(5) 调用 Response 对象的 json() 方法,把响应体转成 JSON 对象

(6) 把请求的结果,赋值给对应的属性

Http 服务使用示例

使用 Http 服务

import { Component, OnInit } from '@angular/core';
import { Http } from '@angular/http'; // (1)
import 'rxjs/add/operator/map'; // (2)

interface Member {
    id: string;
    login: string;
    avatar_url: string;
}

@Component({
    selector: 'sl-members',
    template: `
    <h3>Angular Orgs Members</h3>
    <ul *ngIf="members">
      <li *ngFor="let member of members;">
        <p>
          <img [src]="member.avatar_url" width="48" height="48"/>
          ID:<span>{{member.id}}</span>
          Name: <span>{{member.login}}</span>
        </p>
      </li>
    </ul>
    `
})
export class MembersComponent implements OnInit {
  members: Member[];

  constructor(private http: Http) { } // (3)

  ngOnInit() {
    this.http.get(`https://api.github.com/orgs/angular/members?page=1&per_page=5`) // (4)
        .map(res => res.json()) // (5)
        .subscribe(data => {
           if (data) this.members = data; // (6)
        });
    }
}

声明 MembersComponent 组件

// ...
import { MembersComponent } from './members.component';

@NgModule({
  imports: [BrowserModule, FormsModule, HttpModule],
  declarations: [AppComponent, UserComponent, MembersComponent],
  bootstrap: [AppComponent]
})
export class AppModule { }

使用 MembersComponent 组件

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

@Component({
  selector: 'my-app',
  template: `
    <sl-members></sl-members>
  `,
})
export class AppComponent {}

第九节 - 注入服务

基础知识

组件中注入服务步骤

(1) 配置已创建的服务,如:

@NgModule({
  // ...
  providers: [MemberService]
})
export class AppModule { }

(2) 导入已创建的服务,如:

import { MemberService } from '../member.service';

(3) 使用构造注入方式,注入服务:

export class MembersComponent implements OnInit {
   // ...
   constructor(private memberService: MemberService) { }
}

服务使用示例

创建 MemberService 服务

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';

@Injectable()
export class MemberService {
    constructor(private http: Http) { }

    getMembers() {
        return this.http
            .get(`https://api.github.com/orgs/angular/members?page=1&per_page=5`)
            .map(res => res.json())
    }
}

配置 MemberService 服务

import { MemberService } from "./member.service";

@NgModule({
  // ...
  providers:[MemberService],
  bootstrap: [AppComponent]
})
export class AppModule { }

使用 MemberService 服务

// ...
import { MemberService } from "./member.service";

@Component({...})
export class MembersComponent implements OnInit {
    members: Member[];

    constructor(private memberService: MemberService) { }

    ngOnInit() {
        this.memberService.getMembers()
            .subscribe(data => {
                if (data) this.members = data;
            });
    }
}

第十节 - 路由模块简介

基础知识

导入路由模块

// ...
import { RouterModule } from '@angular/router';

@NgModule({
  imports: [BrowserModule, FormsModule, HttpModule, RouterModule],
  declarations: [AppComponent, UserComponent, MembersComponent],
  bootstrap: [AppComponent]
})
export class AppModule { }

配置路由信息

import { Routes, RouterModule } from '@angular/router';
import { UserComponent } from './user.component';

export const ROUTES: Routes = [
  { path: 'user', component: UserComponent }
];

@NgModule({
  imports: [
    BrowserModule,
    RouterModule.forRoot(ROUTES)
  ],
  // ...
})
export class AppModule {}

routerLink 指令

为了让我们链接到已设置的路由,我们需要使用 routerLink 指令,具体示例如下:

<nav>
  <a routerLink="/">首页</a>
  <a routerLink="/user">我的</a>
</nav>

当我们点击以上的任意链接时,页面不会被重新加载。反之,我们的路径将在 URL 地址栏中显示,随后进行后续视图更新,以匹配 routerLink 中设置的值。

router-outlet 指令

该指令用于告诉 Angular 在哪里加载组件,当 Angular 路由匹配到响应路径,并成功找到需要加载的组件时,它将动态创建对应的组件,并将其作为兄弟元素,插入到 router-outlet 元素中。具体示例如下:

@Component({
  selector: 'app-root',
  template: `
    <div class="app">
      <h3>Our app</h3>
      <router-outlet></router-outlet>
    </div>
  `
})
export class AppComponent {}

路由使用示例

配置路由信息

export const ROUTES: Routes = [
  { path: '', pathMatch: 'full', redirectTo: 'user' },
  { path: 'user', component: UserComponent },
  { path: 'members', component: MembersComponent }
];

@NgModule({
  imports: [BrowserModule, FormsModule, HttpModule,
    RouterModule.forRoot(ROUTES)],
  // ...
})
export class AppModule { }

配置路由导航

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

@Component({
  selector: 'my-app',
  template: `
    <div class="app">
      <h1>欢迎来到Angular的世界</h1>
      <nav>
        <a routerLink="/user">我的</a>
        <a routerLink="/members">Angular成员</a>
      </nav>
      <router-outlet></router-outlet>
    </div>
  `,
})
export class AppComponent { }

完整示例

AppModule

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

import { AppComponent } from './app.component';
import { UserComponent } from './user.component';
import { MembersComponent } from './members.component';
import { MemberService } from "./member.service";

export const ROUTES: Routes = [
  { path: '', pathMatch: 'full', redirectTo: 'user' },
  { path: 'user', component: UserComponent },
  { path: 'members', component: MembersComponent }
];

@NgModule({
  imports: [BrowserModule, FormsModule, HttpModule,
    RouterModule.forRoot(ROUTES)],
  declarations: [AppComponent, UserComponent, MembersComponent],
  providers: [MemberService],
  bootstrap: [AppComponent]
})
export class AppModule { }

AppComponent

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

@Component({
  selector: 'my-app',
  template: `
    <div class="app">
      <h1>欢迎来到Angular的世界</h1>
      <nav>
        <a routerLink="/user">我的</a>
        <a routerLink="/members">Angular成员</a>
      </nav>
      <router-outlet></router-outlet>
    </div>
  `,
})
export class AppComponent { }

UserComponent

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


interface Address {
    province: string;
    city: string;
}

@Component({
    selector: 'sl-user',
    template: `
    <h2>大家好,我是{{name}}</h2>
    <p>我来自<strong>{{address.province}}</strong>省,
      <strong>{{address.city}}</strong>市
    </p>
    <button (click)="toggleSkills()">
        {{ showSkills ? "隐藏技能" : "显示技能" }}
    </button>
    <div *ngIf="showSkills">
        <h3>我的技能</h3>
        <ul>
            <li *ngFor="let skill of skills">
                {{skill}}
            </li>
        </ul>
        <form (submit)="addSkill(skill.value)">
            <label>添加技能</label>
            <input type="text" #skill>
        </form>
    </div>
    `
})
export class UserComponent {
    name: string;
    address: Address;
    showSkills: boolean;
    skills: string[];

    constructor() {
        this.name = 'Semlinker';
        this.address = {
            province: '福建',
            city: '厦门'
        };
        this.showSkills = true;
        this.skills = ['AngularJS 1.x', 'Angular 2.x', 'Angular 4.x'];
    }

    toggleSkills() {
        this.showSkills = !this.showSkills;
    }

    addSkill(skill: string) {
        let skillStr = skill.trim();
        if (this.skills.indexOf(skillStr) === -1) {
            this.skills.push(skillStr);
        }
    }
}

MembersComponent

import { Component, OnInit } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';

import { MemberService } from "./member.service";

interface Member {
    id: string;
    login: string;
    avatar_url: string;
}

@Component({
    selector: 'sl-members',
    template: `
    <h3>Angular Orgs Members</h3>
    <ul *ngIf="members">
      <li *ngFor="let member of members;">
        <p>
          <img [src]="member.avatar_url" width="48" height="48"/>
          ID:<span>{{member.id}}</span>
          Name: <span>{{member.login}}</span>
        </p>
      </li>
    </ul>
    `
})
export class MembersComponent implements OnInit {
    members: Member[];

    constructor(private memberService: MemberService) { }

    ngOnInit() {
        this.memberService.getMembers()
            .subscribe(data => {
                if (data) this.members = data;
            });
    }
}

MemberService

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';

@Injectable()
export class MemberService {
    constructor(private http: Http) { }

    getMembers() {
        return this.http
            .get(`https://api.github.com/orgs/angular/members?page=1&per_page=5`)
            .map(res => res.json())
    }
}

我有话说

除了本系列教程外,还有其它学习资源么?

本系列教程的主要目的是让初学者对 Angular 的相关基础知识,有一定的了解。除了本系列教程外,初学者还可以参考以下教程:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,566评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,275评论 25 707
  • 路由是 Angular 应用程序的核心,它加载与所请求路由相关联的组件,以及获取特定路由的相关数据。这允许我们通过...
    semlinker阅读 12,164评论 4 16
  • 你一定来过 是千百个梦里的一回 余晖残影中的一瞥 不求你记得我,但我知道你来过 来过,是匆匆的脚步 到千里之外去,...
    跟住阅读 222评论 0 2
  • 如果人生是一所大学,那么我们需要学习各种课程。 找工作,恋爱,赚钱等等,是很多人必修的课程。英语,写作,健身,投资...
    银河星海阅读 775评论 3 4