Web前端学习笔记——AngularJS之组件、模板语法

目录

组件

typora-copy-images-to: media

组件的定义

组件的模板

组件通信

父子通信:Input Down

父子通信:Output Up

父子通信:父组件直接访问子组件 public 成员

没有直接关系通信:Service 单例

利用 Session 进行通信

小结

组件生命周期

动态组件

组件内容分发

第三方组件库

模板语法

插值

文本绑定

属性绑定

使用 JavaScript 表达式

列表渲染

条件渲染

NgIf

NgSwitch

事件处理

表单输入绑定

文本

多行文本

复选框

Class 与 Style 绑定

Class

NgClass 指令

Style

NgStyle 指令

Angular 中的计算属性

模板引用变量

过滤器

总结


组件


typora-copy-images-to: media

ng-component

几乎所有前端框架都在玩“组件化”,而且最近都不约而同地选择了“标签化”这种思路,Angular 也不例外。

对新版本的 Angular 来说,一切都是围绕着“组件化”展开的,组件是 Angular 的核心概念模型。

Component Tree

组件的定义

以下是一个最简单的 Angular 组件定义:

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'itcast';
}
  • @Component:这是一个 Decorator(装饰器),其作用类似于 Java 里面的注解。Decorator 这个语言特性目前(2017-10)处于 Stage 2(草稿)状态,还不是 ECMA 的正式规范。
  • selector:组件的标签名,外部使用者可以这样来使用这个组件:。默认情况下,ng 命令生成出来的组件都会带上一个 app 前缀,如果你不喜欢,可以在 angular-cli.json 里面修改 prefix 配置项,设置为空字符串将会不带任何前缀。
  • templateUrl:引用外部的 HTML 模板。如果你想直接编写内联模板,可以使用 template,支持 ES6 引入的“模板字符串”写法
  • styleUrls:引用外部 CSS 样式文件,这是一个数组,也就意味着可以引用多份 CSS 文件。
  • export class AppComponent:这是 ES6 里面引入的模块和 class 定义方式。

组件的模板

  • 内联模板
  • 模板文件

你可以在两种地方存放组件模板。 你可以使用template属性把它定义为内联的,或者把模板定义在一个独立的 HTML 文件中, 再通过@Component装饰器中的templateUrl属性, 在组件元数据中把它链接到组件。

无论用哪种风格,模板数据绑定在访问组件属性方面都是完全一样的。

具体模板语法请参考:模板语法

组件通信

参考官方文档:https://angular.io/guide/component-interaction

组件就像零散的积木,我们需要把这些积木按照一定的规则拼装起来,而且要让它们互相之间能进行通讯,这样才能构成一个有机的完整系统。

在真实的应用中,组件最终会构成树形结构,就像人类社会中的家族树一样:

891636a0-af23-11e7-b111-4d6e630f480d.png

在树形结构里面,组件之间有几种典型的关系:父子关系、兄弟关系、没有直接关系。

相应地,组件之间有以下几种典型的通讯方案:

  • 直接的父子关系:父组件直接访问子组件的 public 属性和方法。
  • 直接的父子关系:借助于 @Input 和 @Output 进行通讯
  • 没有直接关系:借助于 Service 单例进行通讯。
  • 利用 cookie 和 localstorage 进行通讯。
  • 利用 session 进行通讯。

无论你使用什么前端框架,组件之间的通讯都离开不以上几种方案,这些方案与具体框架无关。

父子通信:Input Down

参考文档:https://angular.io/guide/component-interaction#pass-data-from-parent-to-child-with-input-binding

  1. 父组件通过子组件标签传递属性
  2. 子组件在内部声明 @Input 接收

  3. Input 是单向的

    • 父组件如果把数据改了,子组件也会更新
    • 但是反之不会
    • 有一个例外,引用类型修改

下面是一个示例:

子组件:

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

import { Hero } from './hero';

@Component({
  selector: 'app-hero-child',
  template: `
    <h3>{{hero.name}} says:</h3>
    <p>I, {{hero.name}}, am at your service, {{masterName}}.</p>
  `
})
export class HeroChildComponent {
  // 声明接收父组件传递的数据
  @Input() hero: Hero;
  @Input('master') masterName: string; // 接收 master 重命名为 masterName
}

父组件:

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

import { HEROES } from './hero';

@Component({
  selector: 'app-hero-parent',
  template: `
    <h2>{{master}} controls {{heroes.length}} heroes</h2>
    <!-- 在子组件标签上传递数据 -->
    <app-hero-child *ngFor="let hero of heroes"
      [hero]="hero"
      [master]="master">
    </app-hero-child>
  `
})
export class HeroParentComponent {
  heroes = HEROES;
  master = 'Master';
}

父子通信:Output Up

参考文档:https://angular.io/guide/component-interaction#parent-listens-for-child-event

@Output 的本质是事件机制,我们可以利用它来订阅子组件上发布的事件,子组件上这样写:

import { Component, EventEmitter, Input, Output } from '@angular/core';

@Component({
  selector: 'app-voter',
  template: `
    <h4>{{name}}</h4>
    <button (click)="vote(true)"  [disabled]="voted">Agree</button>
    <button (click)="vote(false)" [disabled]="voted">Disagree</button>
  `
})
export class VoterComponent {
  @Input()  name: string;
  @Output() onVoted = new EventEmitter<boolean>();
  voted = false;

  vote(agreed: boolean) {
    this.onVoted.emit(agreed); // 传递的数据就是事件对象
    this.voted = true;
  }
}

在父组件中订阅处理:

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

@Component({
  selector: 'app-vote-taker',
  template: `
    <h2>Should mankind colonize the Universe?</h2>
    <h3>Agree: {{agreed}}, Disagree: {{disagreed}}</h3>
    <app-voter *ngFor="let voter of voters"
      [name]="voter"
      (onVoted)="onVoted($event)">
    </app-voter>
    <!-- $event在这里是自定义事件对象,接收到的是子组件内部发布事件传递的数据 -->
  `
})
export class VoteTakerComponent {
  agreed = 0;
  disagreed = 0;
  voters = ['Mr. IQ', 'Ms. Universe', 'Bombasto'];

  onVoted(agreed: boolean) {
    agreed ? this.agreed++ : this.disagreed++;
  }
}

父子通信:父组件直接访问子组件 public 成员

参考文档:https://angular.io/guide/component-interaction#parent-interacts-with-child-via-local-variable

对于有直接父子关系的组件,父组件可以直接访问子组件里面 public 型的属性和方法,示例代码片段如下:

<app-foo #child></app-foo>
<button (click)="child.increment()">调用子组件的方法</button>

显然,子组件里面必须暴露一个 public 型的 childFn 方法,就像这样:

export class FooComponent implements OnInit {
  public message: string = 'foo message'
  public count: number = 0

  constructor() { }

  public increment (): void {
    this.count++
  }

  ngOnInit() {
  }
}

以上是通过在模板里面定义局部变量的方式来直接调用子组件里面的 public 型方法。在父组件的内部也可以访问到子组件的实例,需要利用到 @ViewChild 装饰器,示例如下:

@ViewChild(ChildComponent)
private childComponent: ChildComponent;

关于 @ViewChild 在后面的内容里面会有更详细的解释。

很明显,如果父组件直接访问子组件,那么两个组件之间的关系就被固定死了。父子两个组件紧密依赖,谁也离不开谁,也就都不能单独使用了。所以,除非你知道自己在做什么,最好不要直接在父组件里面直接访问子组件上的属性和方法,以免未来一改一大片。

没有直接关系通信:Service 单例

参考文档:https://angular.io/guide/component-interaction#parent-and-children-communicate-via-a-service

d2615600-af23-11e7-9203-4582e2e80f6b.png

e9aaf1e0-af23-11e7-b111-4d6e630f480d.png

利用 Session 进行通信

79246040-af24-11e7-b111-4d6e630f480d.png

小结

组件间的通讯方案是通用的,无论你使用什么样的前端框架,都会面临这个问题,而解决的方案无外乎本文所列出的几种。

组件生命周期

参考文档:https://angular.io/guide/lifecycle-hooks

hooks-in-sequence.png

  • ngOnChanges()
  • ngOnInit()
    • 只执行一次
  • ngDoCheck()
  • ngAfterContentInit()
    • 只执行一次
  • ngAfterContentChecked()
  • ngAfterViewInit()
    • 只执行一次
  • ngAfterViewChecked()
  • ngOnDestroy()
    • 只执行一次
  • Angular 一共暴露了8个“钩子”,构造函数不算。
  • 并没有组件或者指令会实现全部钩子。
  • 绿色的4个钩子可能会被执行很多次,紫色的只会执行一次。
  • Content 和 View 相关的4个钩子只对组件有效,指令上不能使用。因为在新版本的 Angular 里面,指令不能带有 HTML 模板。指令没有自己的 UI,当然就没有 View 和 Content 相关的“钩子”了。
  • 请不要在生命周期钩子里面实现复杂的业务逻辑,尤其是那4个会被反复执行的钩子,否则一定会造成界面卡顿。

动态组件

参考文档:https://angular.io/guide/dynamic-component-loader

注意:用代码动态创建组件这种方式在一般的业务开发里面不常用,而且可能存在一些隐藏的坑,如果你一定要用,请小心避雷。

组件内容分发

第三方组件库

模板语法

1200px-TempEngGen015.svg.png

插值

文本绑定

<p>Message: {{ msg }}</p>

<p [innerHTML]="msg"></p>

属性绑定

<!-- 写法一 -->
<img src="{{ heroImageUrl }}">

<!-- 写法二,推荐 -->
<img [src]="heroImageUrl">

<!-- 写法三 -->
<img bind-src="heroImageUrl">

在布尔特性的情况下,它们的存在即暗示为 true,属性绑定工作起来略有不同,在这个例子中:

<button [disabled]="isButtonDisabled">Button</button>

如果 isButtonDisabled 的值是 nullundefined 或 false,则 disabled 特性甚至不会被包含在渲染出来的 <button> 元素中。

使用 JavaScript 表达式

<p>1 + 1 = {{ 1 + 1 }}</p>
<p>{{ num + 1 }}</p>
<p>{{ isDone ? '完了' : '没完' }}</p>
<p>{{ title.split('').reverse().join('') }}</p>

<p [title]="title.split('').reverse().join('')">{{ title }}</p>

<ul>
  <li [id]="'list-' + t.id" *ngFor="let t of todos">
    {{ t.title }}
  </li>
</ul>

编写模板表达式所用的语言看起来很像 JavaScript。 很多 JavaScript 表达式也是合法的模板表达式,但不是全部。

Angular 遵循轻逻辑的设计思路,所以在模板引擎中不能编写非常复杂的 JavaScript 表达式,这里有一些约定:

  • 赋值 (=+=-=, ...)
  • new 运算符
  • 使用 ; 或 , 的链式表达式
  • 自增或自减操作符 (++--)

列表渲染

基本用法:

export class AppComponent {
  heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
}
<p>Heroes:</p>
<ul>
  <li *ngFor="let hero of heroes">
    {{ hero }}
  </li>
</ul>

也可以获取 index 索引:

<div *ngFor="let hero of heroes; let i=index">{{i + 1}} - {{hero.name}}</div>

条件渲染

NgIf

<p *ngIf="heroes.length > 3">There are many heroes!</p>

ngIf 和 <ng-template>

<ng-template [ngIf]="condition"><div>...</div></ng-template>

NgSwitch

NgSwitch 的语法比较啰嗦,使用频率小一些。

<div [ngSwitch]="currentHero.emotion">
  <app-happy-hero    *ngSwitchCase="'happy'"    [hero]="currentHero"></app-happy-hero>
  <app-sad-hero      *ngSwitchCase="'sad'"      [hero]="currentHero"></app-sad-hero>
  <app-confused-hero *ngSwitchCase="'confused'" [hero]="currentHero"></app-confused-hero>
  <app-unknown-hero  *ngSwitchDefault           [hero]="currentHero"></app-unknown-hero>
</div>

ngswitch

事件处理

事件绑定只需要用圆括号把事件名包起来即可:

<button (click)="onSave()">Save</button>

可以把事件对象传递到事件处理函数中:

<button (click)="onSave($event)">On Save</button>

也可以传递额外的参数(取决于你的业务):

<button (click)="onSave($event, 123)">On Save</button>

当事件处理语句比较简单的时候,我们可以内联事件处理语句:

<button (click)="message = '哈哈哈'">内联事件处理</button>

我们可以利用 属性绑定 + 事件处理 的方式实现表单文本框双向绑定:

<input [value]="message"
       (input)="message=$event.target.value" >

事件绑定的另一种写法,使用 on- 前缀的方式:

<!-- 绑定事件处理函数 -->
<button on-click="onSave()">On Save</button>

表单输入绑定

文本

<p>{{ message }}</p>
<input type="text" [(ngModel)]="message">

运行之后你会发现报错了,原因是使用 ngModel 必须导入 FormsModule 并把它添加到 Angular 模块的 imports列表中。

导入 FormsModule 并让 [(ngModel)] 可用的代码如下:

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

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

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

通过以上的配置之后,你就可以开心的在 Angular 中使用双向数据绑定了?。

多行文本

<textarea cols="30" rows="10" [(ngModel)]="message"></textarea>

复选框

<h3>单选框</h3>
<input type="checkbox" [(ngModel)]="seen">
<div class="box" *ngIf="seen"></div>

###单选按钮

<input type="radio" [value]="0" [(ngModel)]="gender"> 男
<input type="radio" [value]="1" [(ngModel)]="gender"> 女
<p>选中了:{{ gender }}</p>

###下拉列表

<select [(ngModel)]="hobby">
  <option [value]="0">吃饭</option>
  <option [value]="1">睡觉</option>
  <option [value]="2">打豆豆</option>
</select>
<p>选中的爱好:{{ hobby }}</p>

Class 与 Style 绑定

Class

<!-- standard class attribute setting  -->
<div class="bad curly special">Bad curly special</div>

<!-- reset/override all class names with a binding  -->
<div class="bad curly special"
     [class]="badCurly">Bad curly</div>

<!-- toggle the "special" class on/off with a property -->
<div [class.special]="isSpecial">The class binding is special</div>

<!-- binding to `class.special` trumps the class attribute -->
<div class="special"
     [class.special]="!isSpecial">This one is not so special</div>

NgClass 指令

NgClass 指令接收一个对象,对象的 key 指定 css 类名,value 给定一个布尔值,当布尔值为真则作用该类名,当布尔值为假则移除给类名。

语法规则:

<div [ngClass]="{
  css类名: 布尔值,
  css类名: 布尔值
}">测试文本</div>

基本示例:

.saveable{
    font-size: 18px;
} 
.modified {
    font-weight: bold;
}
.special{
    background-color: #ff3300;
}
currentClasses: {};
setCurrentClasses() {
  // CSS classes: added/removed per current state of component properties
  this.currentClasses =  {
    'saveable': this.canSave,
    'modified': !this.isUnchanged,
    'special':  this.isSpecial
  };
}
<div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special</div>

Style

通过样式绑定,可以设置内联样式。

样式绑定的语法与属性绑定类似。 但方括号中的部分不是元素的属性名,而由style前缀,一个点 (.)和 CSS 样式的属性名组成。 形如:[style.style-property]

<button [style.color]="isSpecial ? 'red': 'green'">Red</button>
<!-- 也可以 backgroundColor -->
<button [style.background-color]="canSave ? 'cyan': 'grey'" >Save</button>

有些样式绑定中的样式带有单位。在这里,以根据条件用 “em” 和 “%” 来设置字体大小的单位。

<button [style.font-size.em]="isSpecial ? 3 : 1" >Big</button>
<button [style.font-size.%]="!isSpecial ? 150 : 50" >Small</button>

提示:样式属性命名方法可以用中线命名法,像上面的一样 也可以用驼峰式命名法,如fontSize

NgStyle 指令

虽然这是设置单一样式的好办法,但我们通常更喜欢使用 NgStyle指令 来同时设置多个内联样式。

currentStyles: {};
setCurrentStyles() {
  // CSS styles: set per current state of component properties
  this.currentStyles = {
    'font-style':  this.canSave      ? 'italic' : 'normal',
    'font-weight': !this.isUnchanged ? 'bold'   : 'normal',
    'font-size':   this.isSpecial    ? '24px'   : '12px'
  };
}
<div [ngStyle]="currentStyles">
  This div is initially italic, normal weight, and extra large (24px).
</div>

ngStyle 这种方式相当于在代码里面写 CSS 样式,比较丑陋,违反了注意点分离的原则,而且将来不太好修改,非常不建议这样写(足够简单的情况除外)。

Angular 中的计算属性

模板引用变量

模板引用变量通常用来引用模板中的某个DOM元素,它还可以引用Angular组件或指令或Web Component

使用井号 (#) 来声明引用变量。 #phone的意思就是声明一个名叫phone的变量来引用<input>元素。

<input #phone placeholder="phone number">

我们可以在模板中的任何地方引用模板引用变量。 比如声明在<input>上的phone变量就是在模板另一侧的<button>上使用的。

<input #phone placeholder="phone number">

<!-- lots of other elements -->

<!-- phone refers to the input element; pass its `value` to an event handler -->
<button (click)="callPhone(phone.value)">Call</button>

大多数情况下,Angular会把模板引用变量的值设置为声明它的那个元素。在上面例子中,phone 引用的是表示电话号码<input>框。 "拨号"按钮的点击事件处理器把这个input值传给了组件的callPhone方法。 不过,指令也可以修改这种行为,让这个值引用到别处,比如它自身。 NgForm指令就是这么做的。

模板引用变量使用注意:

  • 模板引用变量的作用范围是整个模板。 不要在同一个模板中多次定义同一个变量名,否则它在运行期间的值是无法确定的。
  • 如果我在模板里面定义的局部变量和组件内部的属性重名会怎么样呢
    • 如果真的出现了重名,Angular 会按照以下优先级来进行处理
    • 模板局部变量 > 指令中的同名变量 > 组件中的同名属性
  • 我们也可以用ref-前缀代替#。 下面的例子中就用把fax变量声明成了ref-fax而不是#fax
<input ref-fax placeholder="fax number">
<button (click)="callFax(fax.value)">Fax</button>

过滤器

在 Angular 中,过滤器也叫管道。它最重要的作用就是用来格式化数据的输出。

举个简单例子:

public currentTime: Date = new Date();
<h1>{{currentTime | date:'yyyy-MM-dd HH:mm:ss'}}</h1>

Angular 一共内置了16个过滤器:https://angular.io/api?type=pipe。

在复杂业务场景中,内置的过滤器肯定是不够用的,所有 Angular 也支持自定义过滤器。

管道还有另外一个重要的作用就是做国际化。

总结

发布了296 篇原创文章 · 获赞 562 · 访问量 50万+

猜你喜欢

转载自blog.csdn.net/tichimi3375/article/details/82845110