angular学习笔记 - 知识点

介绍

angularJs是第一版,angular是第二版以后的统称。
angular不兼容angularJs。
HttpClient(基于RxJS响应式编程)
相比于其他框架,更适用于复杂应用。性能高,体积小。


初始化

官方文档,快速上手
npm install -g @angular/cli安装脚手架
ng new my-app创建项目
cd my-app切换到项目
ng serve --open启动项目

文件目录结构


tslint配置

tslint.json

组件体验

模块

根模块
作用:启动应用


模块:独立、封闭的,模块间的引用通过导入和导出来完成
模块包含:组件、服务、指令,这些要配置后才生效

@NgModule

根组件装饰器
告诉angular将这个类当作模块处理
@NgModule{{元数据对象}}

@Component

组件装饰器


数据绑定

插值表达式

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

@Component({
  selector: 'app-root',
  // templateUrl: './app.component.html',
  template: '<div title="{{company}}">{{title}}--{{company}}--{{1+2}}-- {{fn()}}</div>',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = '塞纳河后台管理'
  company = 'snh'
  fn () {
    return '方法返回内容'
  }
}

属性绑定
插值表达式中的属性绑定方式,在执行时最终会转换成这种属性绑定

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

@Component({
  selector: 'app-root',
  // templateUrl: './app.component.html',
  template: `
    <a [href]="url">链接</a>
    <input type="checkbox" [checked]="isChecked" />
  `,
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  url = 'http://www.baidu.com'
  isChecked = true
}

事件绑定

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

@Component({
  selector: 'app-root',
  // templateUrl: './app.component.html',
  template: `
    <button (click)="handleClick()" (mouseenter)="handleMouseEnter()">请点击</button>
    <a [href]="url" (click)="handleClick($event)">链接</a>
  `,
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  url = 'http://www.baidu.com'
  handleClick (event) {
    // 阻止浏览器的默认行为
    event.preventDefault()
    console.log('handleClick', event)
  }
  handleMouseEnter () {
    console.log('handleMouseEnter')
  }
}

双向数据绑定
属性绑定和事件绑定的结合


app.module.ts

app.component.ts

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

@Component({
  selector: 'app-root',
  // templateUrl: './app.component.html',
  template: `
    <input type="text" [(ngModel)]="msg" />
    <p>{{msg}}</p>
  `,
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  msg = '我是默认值'
}

语言服务

指令

组件:拥有模板的指令
属性型指令:改变元素外观和行为的指令
结构型指令:添加和移除DOM元素,改变DOM布局的指令

[ngClass]

[ngStyle]

*ngIf

*ngFor

基本使用


隔行变色


使用trackBy解决性能问题
普通数据在渲染时没有性能问题,对象数组在渲染时有性能问题。这时需要使用trackBy来解决。

todos案例

组件通讯

ng generate component child 生成子组件,并自动完成了相关的配置

父组件传值给子组件





子组件传值给父组件



子组件创建事件,触发事件,传递参数




父组件绑定事件,接收参数

todos案例分离组件

ng generate module todos 创建todos模块
目标结构如下

指定位置,创建三个子组件
ng generate component todos/todo
ng generate component todos/todo-header
ng generate component todos/todo-list

在根模块中使用todos模块



然后将原来写在根模块中的内容,搬到todos模块中即可。
注意:FormsModule也要搬过去,否则todos模块中不能使用。

抽离todo-header组件
要用到组件之间的通讯,子传父 - 任务添加

抽离todo-list组件
任务展示、删除、状态切换
要用到组件之间的通讯,父传子 - 拿到todos数据
要用到组件之间的通讯,子传父 - 删除和修改状态时,要修改父组件中的数据,为了保持数据源的单一性

todo父组件 - 提供代办事项的数据

使用TypeScript

angular官方推荐使用ts
增强了项目的可维护性
有利于协作开发

ts语法

  1. 类型注解
let id: number
  1. 接口
// 创建接口
interface Todo {
  id: number,
  name: string,
  done: boolean
}
export class TodoComponent implements OnInit {
  constructor() { }
  // 任务列表
  todos: Todo[] = [
    { id: 1, name: '玩游戏啊', done: true },
    { id: 2, name: '点外卖呀', done: false },
    { id: 3, name: '看bilibili', done: false }
  ]
}
  1. 泛型
  @Output()
  add = new EventEmitter<string>()
  1. 类成员修饰符
  private todoName: string

接口的另一种使用方式


服务

组件:
提供数据绑定的属性和方法

服务:
处理业务逻辑,供组件使用,如从服务器获取数据、验证用户输入等

组件是服务的消费者

服务说明:

  1. @injectable()装饰器来表示一个服务
  2. 服务要注册提供商才能使用
  3. angular通过依赖注入(DI)来位组件提供服务
  4. DI提供要使用的服务即可。不需要手动创建服务实例
  5. 推荐在constructor中提供组件中用到的服务

服务的创建和基本使用

ng generate service todos/todos 生成服务

todos.service.ts中,如下是服务的提供商,必须有@Injectable

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

@Injectable({
  providedIn: 'root'
})
export class TodosService {
  constructor() { }
  // 提供方法
  todoTest () {
    console.log('test-TodoService')
  }
}

使用方式,todo.component.ts,这样,当点击添加的时候,即可调用服务

// 导入服务
import { TodosService } from '../todos.service'

export class TodoComponent implements OnInit, OnChanges {

  constructor(private todoService: TodosService) { }
  addTodo(todoName: string) {
    // 演示服务的调用
    this.todoService.todoTest()
  }

  ngOnInit() {
  }
  ngOnChanges() {
  }
}

注册提供商的三种方式

  1. 根级提供商,在所有组件中都可以调用
@Injectable({
  providedIn: 'root'
})
  1. 模块内可用的提供商

todos.service.ts

@Injectable()

todos.module.ts

// 导入服务
import { TodosService } from './todos.service'
@NgModule({
  providers: [
    TodosService
  ]
})
  1. 组件内可用的提供商(其子组件也可以使用,兄弟组件不可使用)

todos.service.ts

@Injectable()

todo.component.ts

// 导入服务
import { TodosService } from './../todos.service';

@Component({
  providers: [TodosService]
})

export class TodoComponent implements OnInit, OnChanges {
  constructor(private todoService: TodosService) { }
  addTodo(todoName: string) {
    // 演示服务的调用
    this.todoService.todoTest()
  }
  ngOnInit() {
  }
  ngOnChanges() {
  }
}

todos案例使用服务修改

把组件中的业务逻辑抽离到服务中

HttpClient

angular是内置的客户端,不是使用第三方的。

  • 作用: 发送http请求
  • 封装了XMLHttpRequest
  • 使用基于可观察(Observable)对象的api
  • 提供了请求和响应拦截器
  • 流式错误处理机制

HttpClient的基本使用

app.module.ts中导入

// 导入HttpClient模块
import {HttpClientModule} from '@angular/common/http'
@NgModule({
  imports: [
    HttpClientModule
  ],
})

app.component.html触发事件

<div>
  <button (click)="getData()">获取数据</button>
  <h3>通过HttpClient获取到的数据是:{{name}}</h3>
</div>

app.component.ts 接口是用的assets中的json文件模拟的

// 导入HttpClient
import { HttpClient } from '@angular/common/http'
export class AppComponent {
  constructor(private http: HttpClient) { }
  name: string
  getData () {
    this.http.get('../assets/todos.json').subscribe((res: any) => {
      console.log(res)
      this.name = res.name
    })
  }
}

添加类型检查后的app.component.ts ,这种写法比较严谨

interface Todo {
  name: string,
  description: string
}
export class AppComponent {
  constructor(private http: HttpClient) { }
  name: string
  getData () {
    this.http.get<Todo>('../assets/todos.json').subscribe((res: Todo) => {
      console.log(res)
      this.name = res.name
    })
  }
}

获取完整的响应

使用{ observe: 'response' }即可获取完整的响应
类型检查时要使用HttpResponse

// 导入HttpClient
import { HttpClient, HttpResponse } from '@angular/common/http'

export class AppComponent {
  constructor(private http: HttpClient) { }
  name: string
  getData() {
    this.http.get<Todo>('../assets/todos.json', { observe: 'response' })
    .subscribe((res: HttpResponse<Todo>) => {
      console.log(res)
      console.log(res.headers.get('content-type'), res.body)
      this.name = res.body.name
    })
  }
}

错误处理

  getData() {
    this.http.get<Todo>('../assets/todos.json1', { observe: 'response' })
      .subscribe((res: HttpResponse<Todo>) => {
        console.log(res)
        console.log(res.headers.get('content-type'), res.body)
        this.name = res.body.name
      },
      err => {
        console.log(err)
      }
    )
  }

json-server

json-server官方文档
npm install -g json-server安装包
新建db.json文件,在里面写好json数据

{
  "todos": [
    { "id": 1, "name": "玩游戏啊", "done": true },
    { "id": 2, "name": "点外卖啊", "done": false },
    { "id": 3, "name": "看bilibii", "done": false }
  ]
}

json-server --watch db.json


这就是接口的地址

其他请求

接口地址使用的是json-server生成的地址
app.component.html

<div>
  <button (click)="getData()">get获取</button>
  <button (click)="addData()">post增加</button>
  <button (click)="delData()">del删除</button>
  <button (click)="updateData()">patch修改</button>
</div>

app.component.ts

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

// 导入HttpClient
import { HttpClient } from '@angular/common/http'

interface Todo {
  name: string,
  description: string
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  constructor(private http: HttpClient) { }
  name: string
  url = 'http://localhost:3000/todos'
  // get
  getData() {
    this.http.get(this.url).subscribe(res => {
      console.log(res)
    })
  }
  // post
  addData() {
    this.http.post(this.url,{
      name: '测试测试',
      done: true
    }).subscribe(res => {
      console.log('post success:', res)
    })
  }
  // del
  delData() {
    this.http.delete(`${this.url}/2`).subscribe(res => {
      console.log('delete success:', res);
    })
  }
  // patch
  updateData() {
    this.http.patch(`${this.url}/3`,{
      name: '黑呀~我是修改后的数据呀~'
    }).subscribe(res => {
      console.log('patch success:', res)
    })
  }
}

todos案例 HttpClient

路由

实现SPA(单页应用程序)的基础设施
URL和组件的对应规则
angular使用HTML5风格(history.pushState)的导航
支持:重定向、路由高亮、通配符路由、路由参数、子路由、路由模块、路由守卫、异步路由

路由的基本使用

路由的基本使用

  1. index.html
  <base href="/">
  1. 导入RouterModule

  2. 配置路由规则appRoutes

  3. 根模块中配置


    forRoot说明:
    路由服务应该是单例的,但在路由懒加载等场景下会造成服务多次注册,所以使用forRoot()方法导入模块,保证项目中只有一个Router服务

  4. 添加路由出口<router-outlet></router-outlet>

配置更多路由

新建组件
配置多个路由规则即可

默认路由

  { path: '', redirectTo: '/home', pathMatch: 'full' }

通配符路由

用来匹配没有的路由规则,跳转到404页面

  // 通配符路由,要放在最后面,不然所有的路由都不能正常匹配
  { path: '**', component: PageNotFoundComponent }`

编程式导航

import { Component, OnInit } from '@angular/core';
// 导入路由提供的服务
import { Router } from '@angular/router'
@Component({
  selector: 'app-page-not-found',
  templateUrl: './page-not-found.component.html',
  styleUrls: ['./page-not-found.component.css']
})
export class PageNotFoundComponent implements OnInit {
  // 注入服务
  constructor(private router: Router) { }
  time = 5

  ngOnInit() {
    const timerId = setInterval( () => {
      this.time--
      if (this.time === 0) {
        clearInterval(timerId)
        // 编程式导航
        this.router.navigate(['/home'])
      }
    }, 1000)
  }
}

路由的参数

ng g c car新建模块
配路由规则,:id表示路由参数

  { 
    path: 'car/:id', 
    component: CarComponent 
  },

app.component.html中设置跳转

  <ul>
      <li>
        <a routerLink="car/1">野马</a>
      </li>
      <li>
          <a routerLink="car/2">法拉利</a>
      </li>
      <li>
          <a routerLink="car/3">兰博基尼</a>
      </li>
      <li>
          <a routerLink="car/4">奇瑞QQ</a>
      </li>
    </ul>

car.component.ts中获取路由参数

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

// 导入路由服务
import { ActivatedRoute } from '@angular/router'

@Component({
  selector: 'app-car',
  templateUrl: './car.component.html',
  styleUrls: ['./car.component.css']
})
export class CarComponent implements OnInit {

  constructor(private route: ActivatedRoute) { }

  ngOnInit() {
    this.route.paramMap.subscribe(param => {
      // param中可拿到当前路由跳转的参数
      const id = param.get('id')
      console.log(param, id);
    })
  }
}

子路由


新建组件
配置路由规则

  {
    path: 'home',
    component: HomeComponent,
    children: [{
      path: 'home/child',
      component: HomeChildComponent
    }]
  },

给子路由出口, home.component.html

<p>
  home works!
  <a routerLink="home/child">子路由</a>
  <router-outlet></router-outlet>
</p>

路由激活高亮

  <!-- 在css中设置actived的样式,即为高亮样式 -->
  <!-- [routerLinkActiveOptions]是精确匹配才高亮 -->
  <a routerLink='/home' routerLinkActive="actived" [routerLinkActiveOptions]="{exact: true}">首页</a>
  <a routerLink='/about' routerLinkActive="actived">关于</a>

表单

  • 响应式表单
    很强大,推荐
    模型驱动,数据驱动视图的思想
    同步的数据访问,保证数据和视图是一致的、可预测的
    增强了可测试性,让测试变得简单
    内置表单验证器

  • 模板驱动表单
    数据双向绑定实现
    vue angular.js

响应式表单

响应式表单

  1. 导入响应式表单模块
import { ReactiveFormsModule } from '@angular/forms';
  1. 生成并导入一个新的表单控件
  2. 在模板中注册该控件
  3. 更新用户名和获取用户名方法
  // 获取用户名
  getUserName () {
    console.log(this.username.value)
  }
  // 更新用户名
  setUserName () {
    this.username.setValue('fdd')
  }

表单验证

  • 内置表单验证器
    username.errors中拿值,判断是否通过校验
    username.dirty中拿值,判断是否输入过
<p *ngIf="username.dirty && username.errors?.required">用户名为必填项</p>

hasError()中拿到值,判断是否通过校验

ngOnInit () {
    console.log(this.username)
    console.log(this.username.hasError('required'))
  }

多个条件的校验

  password = new FormControl('123', [
    Validators.required,
    Validators.minLength(4)
  ])
  <p *ngIf="password.dirty && password.errors?.minlength">密码格式不正确</p>
  • 自定义表单验证器
  // 自定义表单验证
  nickname = new FormControl('', [this.nicknameValidate])
  nicknameValidate(control) {

    console.log(control);
    if (/^[a-z]{3,6}$/.test(control.value)) {
      return null
    }
    return { error: true }
  }
  <p *ngIf="nickname.dirty && nickname.hasError('error')">昵称格式不正确</p>

FormGroup

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

// 导入表单控件
import { FormControl, Validators, FormGroup } from '@angular/forms';

@Component({
  selector: 'app-form-group',
  templateUrl: './form-group.component.html',
  styleUrls: ['./form-group.component.css']
})
export class FormGroupComponent implements OnInit {
  constructor() { }

  loginForm = new FormGroup({
    username: new FormControl('', Validators.required),
    password: new FormControl('123', [
      Validators.required,
      Validators.minLength(4)
    ])
  });
  onSubmit() {
    if (this.loginForm.valid) {
      console.log('submit');
    } else {
      console.log('err');
    }
  }
  ngOnInit() {
    // console.log(this.loginForm)
  }
}
<div>
  <form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
    <label>
      <span>用户名:</span>
      <input type="text" formControlName="username" />
      <span>{{this.loginForm.value.username}}</span>
    </label>
    <label>
      <span>密码:</span>
      <input type="text" formControlName="password" />
      <span>{{this.loginForm.value.password}}</span>
    </label>
    <button type="submit">提交</button>
  </form>
    <p *ngIf="this.loginForm.controls.username.dirty && this.loginForm.controls.username.errors?.required">用户名为必填项</p>
    <p *ngIf="this.loginForm.controls.password.dirty && this.loginForm.controls.password.errors?.minlength">密码格式不正确</p>
  </div>

FormBuilder

生成表单控件的便捷方法
导入、注入

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

推荐阅读更多精彩内容