zoukankan      html  css  js  c++  java
  • Angular快速学习笔记(3) -- 组件与模板

    1. 显示数据

    在 Angular 中最典型的数据显示方式,就是把 HTML 模板中的控件绑定到 Angular 组件的属性。

    使用插值表达式显示组件属性

    要显示组件的属性,最简单的方式就是通过插值表达式 (interpolation) 来绑定属性名。 要使用插值表达式,就把属性名包裹在双花括号里放进视图模板,如 {{myHero}}。

    import { Component } from '@angular/core';
     
    @Component({
      selector: 'app-root',
      template: `
        <h1>{{title}}</h1>
        <h2>My favorite hero is: {{myHero}}</h2>
        `
    })
    export class AppComponent {
      title = 'Tour of Heroes';
      myHero = 'Windstorm';
    }
    

    注意

    • 这里直接使用的 template,而不是templateUrl,template可以直接书写html代码,简单的html推荐这种方式
    • @Component 装饰器中指定的 CSS 选择器 selector,它指定了一个叫 my-app 的元素。 该元素是 index.html 的 body 里的占位符。
    #src/index.html (body)
    <body>
      <app-root></app-root>
    </body>
    

    内联 (inline) 模板还是模板文件?

    angular提供两种地方存放组件模板

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

    到底选择内联 HTML 还是独立 HTML 取决于个人喜好、具体状况和组织级策略。 上面的应用选择内联 HTML ,是因为模板很小,而且没有额外的 HTML 文件显得这个演示简单些。

    为数据创建一个类

    使用angular提供的cli:

    ng generate class hero
    

    修改src/app/hero.ts

    export class Hero {
      constructor(
    	public id: number,
    	public name: string) { }
    }
    

    使用这个类:

    src/app/app.component.ts (heroes):

    heroes = [
      new Hero(1, 'Windstorm'),
      new Hero(13, 'Bombasto'),
      new Hero(15, 'Magneta'),
      new Hero(20, 'Tornado')
    ];
    myHero = this.heroes[0];
    template: `
      <h1>{{title}}</h1>
      <h2>My favorite hero is: {{myHero.name}}</h2>
      <p>Heroes:</p>
      <ul>
        <li *ngFor="let hero of heroes">
          {{ hero.name }}
        </li>
      </ul>
    `
    

    在模板里可以自己使用.语法,访问对象属性

    使用 ngFor 显示数组属性

    *ngFor 是 Angular 的“迭代”指令。 它将 <li> 元素及其子级标记为“迭代模板”.,同vue.js里的v-for

    
    export class AppComponent {
      title = 'Tour of Heroes';
      heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
      myHero = this.heroes[0];
    }
    
    template: `
      <h1>{{title}}</h1>
      <h2>My favorite hero is: {{myHero}}</h2>
      <p>Heroes:</p>
      <ul>
        <li *ngFor="let hero of heroes">
          {{ hero }}
        </li>
      </ul>
      ·
    

    不要忘记 *ngFor 中的前导星号 (*)。它是语法中不可或缺的一部分

    通过 NgIf 进行条件显示

    有时,应用需要只在特定情况下显示视图或视图的一部分,这个时候使用ngif,同vue.js里的v-if

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

    小结

    • 带有双花括号的插值表达式 (interpolation) 来显示一个组件属性
    • 用 ngFor 显示数组
    • 用一个 TypeScript 类来为你的组件描述模型数据并显示模型的属性
    • 用 ngIf 根据一个布尔表达式有条件地显示一段 HTML。

    2. 模板语法

    Angular 应用管理着用户之所见和所为,并通过 Component 类的实例(组件)和面向用户的模板来与用户交互。

    在 Angular 中,组件扮演着控制器视图模型的角色,模板则扮演视图的角色。

    模板中的 HTML

    HTML 是 Angular 模板的语言。几乎所有的 HTML 语法都是有效的模板语法。 但值得注意的例外是 <script> 元素,它被禁用了,以阻止脚本注入攻击的风险。(实际上,<script> 只是被忽略了。)

    插值表达式 ( {{...}} )

    插值表达式{{...}} 可以把计算后的字符串插入到 HTML 元素标签内的文本或对标签的属性进行赋值。

    <h3>
      {{title}}
      <img src="{{heroImageUrl}}" style="height:30px">
    </h3>
    

    一般来说,括号间的素材是一个模板表达式,Angular 先对它求值,再把它转换成字符串。 下列插值表达式通过把括号中的两个数字相加说明了这一点:

    <p>The sum of 1 + 1 is {{1 + 1}}</p>
    

    表达式还可以调用宿主组件的方法,就像下面用的 getVal():

    <p>The sum of 1 + 1 is not {{1 + 1 + getVal()}}</p>
    

    模板表达式

    模板表达式产生一个值。 Angular 执行这个表达式,并把它赋值给绑定目标的属性,这个绑定目标可能是 HTML 元素、组件或指令。

    典型的表达式上下文就是这个组件实例,它是各种绑定值的来源。 在下面的代码片段中,双花括号中的 title 和引号中的 isUnchanged 所引用的都是 AppComponent 中的属性。

    {{title}}
    <span [hidden]="isUnchanged">changed</span>
    

    表达式中的上下文变量是由模板变量、指令的上下文变量(如果有)和组件的成员叠加而成的。 如果你要引用的变量名存在于一个以上的命名空间中,那么,模板变量是最优先的,其次是指令的上下文变量,最后是组件的成员。

    <div *ngFor="let hero of heroes">{{hero.name}}</div>
    <input #heroInput> {{heroInput.value}}
    

    模板语句

    模板语句用来响应由绑定目标(如 HTML 元素、组件或指令)触发的事件。 它出现在 = 号右侧的引号中,就像这样:(event)="statement"。

    <button (click)="deleteHero()">Delete hero</button>
    

    和模板表达式一样,模板语句使用的语言也像 JavaScript。 模板语句解析器和模板表达式解析器有所不同,特别之处在于它支持基本赋值 (=) 和表达式链 (; 和 ,)

    某些 JavaScript 语法仍然是不允许的:

    • new 运算符
    • 自增和自减运算符:++ 和 --
    • 操作并赋值,例如 += 和 -=
    • 位操作符 | 和 &
    • 模板表达式运算符

    和表达式中一样,语句只能引用语句上下文中 —— 通常是正在绑定事件的那个组件实例。

    典型的语句上下文就是当前组件的实例。 (click)="deleteHero()" 中的 deleteHero 就是这个数据绑定组件上的一个方法。

    模板语句不能引用全局命名空间的任何东西。比如不能引用 window 或 document,也不能调用 console.log 或 Math.max。

    绑定语法

    数据绑定是一种机制,用来协调用户所见和应用数据。绑定的类型可以根据数据流的方向分成三类: 从数据源到视图、从视图到数据源以及双向的从视图到数据源再到视图。

    数据方向 语法 绑定类型
    单向从数据源到视图 {{expression}} [target]="expression" bind-target="expression" 插值表达式属性AttributeCSS 类样式
    从视图到数据源的单向绑定 (target)="statement"、on-target="statement" 事件
    双向 [(target)]="expression" 、bindon-target="expression" 双向

    新的思维模型

    数据绑定的威力和允许用自定义标记扩展 HTML 词汇的能力,会让你把模板 HTML 当成 HTML+。

    在正常的 HTML 开发过程中,你使用 HTML 元素来创建视觉结构, 通过把字符串常量设置到元素的 attribute 来修改那些元素。

    <div class="special">Mental Model</div>
    <img src="assets/images/hero.png">
    <button disabled>Save</button>
    

    在 Angular 模板中,你仍使用同样的方式创建结构和初始化 attribute 值。然后,用封装了 HTML 的组件创建新元素,并把它们当作原生 HTML 元素在模板中使用。

    <!-- Normal HTML -->
    <div class="special">Mental Model</div>
    <!-- Wow! A new element! -->
    <app-hero-detail></app-hero-detail>
    

    改变定式思维;

    <button [disabled]="isUnchanged">Save</button>
    

    直觉告诉你,你正在绑定按钮的 disabled attribute。 并把它设置为组件的 isUnchanged 属性的当前值,但你的直觉是错的!isUnchanged为true时,button增加disabled属性。

    模板绑定是通过 property 和事件来工作的,而不是 attribute.

    数据绑定的目标是 DOM 中的某些东西。 这个目标可能是(元素 | 组件 | 指令的)property、(元素 | 组件 | 指令的)事件,或(极少数情况下) attribute 名。

    属性绑定
    <img [src]="heroImageUrl">
    <app-hero-detail [hero]="currentHero"></app-hero-detail>
    <div [ngClass]="{'special': isSpecial}"></div>
    

    注意需要加[]方括号。

    下面的代码是等价的:

    <p><img src="{{heroImageUrl}}"> is the <i>interpolated</i> image.</p>
    <p><img [src]="heroImageUrl"> is the <i>property bound</i> image.</p>
    

    在多数情况下,插值表达式是更方便的备选项。 实际上,在渲染视图之前,Angular 把这些插值表达式翻译成相应的属性绑定。

    事件绑定

    可以通过 Angular 事件绑定来声明对哪些用户动作感兴趣

    圆括号中的名称 —— 比如 (click) —— 标记出目标事件。在下面例子中,目标是按钮的 click 事件。

    <button (click)="onSave()">Save</button>
    <app-hero-detail (deleteRequest)="deleteHero()"></app-hero-detail>
    <div (myClick)="clicked=$event" clickable>click me</div>
    

    也可以使用on-前缀方式:

    <button on-click="onSave()">On Save</button>
    
    $event 对象

    在事件绑定中,Angular 会为目标事件设置事件处理器。绑定会通过名叫 $event 的事件对象传递关于此事件的信息。

    <input [value]="currentHero.name"
           (input)="currentHero.name=$event.target.value" >
    
    使用 EventEmitter 实现自定义事件

    通常,指令使用 Angular EventEmitter 来触发自定义事件。 指令创建一个 EventEmitter 实例,并且把它作为属性暴露出来。 指令调用 EventEmitter.emit(payload) 来触发事件,可以传入任何东西作为消息载荷。 父指令通过绑定到这个属性来监听事件,并通过 $event 对象来访问载荷。

    
    template: `
    <div>
      <img src="{{heroImageUrl}}">
      <span [style.text-decoration]="lineThrough">
        {{prefix}} {{hero?.name}}
      </span>
      <button (click)="delete()">Delete</button>
    </div>`
    
    deleteRequest = new EventEmitter<Hero>();
    
    delete() {
      this.deleteRequest.emit(this.hero);
    }
    
    

    使用自定义事件:

    <app-hero-detail (deleteRequest)="deleteHero($event)" [hero]="currentHero"></app-hero-detail>
    
    双向绑定

    对于需要显示数据属性,并在用户作出更改时更新该属性如何处理呢?
    在元素层面上,既要设置元素属性,又要监听元素事件变化。Angular 为此提供一种特殊的双向数据绑定语法:[(x)][(x)] 语法结合了属性绑定的方括号 [x] 和事件绑定的圆括号 (x)

    <input [(ngModel)]="name">
    
    Attribute

    attribute 绑定的语法与属性绑定类似。 但方括号中的部分不是元素的属性名,而是由attr前缀,一个点 (.) 和 attribute 的名字组成。

    <button [attr.aria-label]="help">help</button>
    
    CSS 类

    借助 CSS 类绑定,可以从元素的 class attribute 上添加和移除 CSS 类名。

    <div [class.special]="isSpecial">Special</div>
    
    样式

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

    <button [style.color]="isSpecial ? 'red' : 'green'">
    

    模板引用变量 ( #var )

    模板引用变量通常用来引用模板中的某个 DOM 元素,它还可以引用 Angular 组件或指令或Web Component。
    使用井号 (#) 来声明引用变量。 #phone 的意思就是声明一个名叫 phone 的变量来引用 <input> 元素。

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

    输入和输出属性

    输入属性是一个带有 @Input 装饰器的可设置属性。当它通过属性绑定的形式被绑定时,值会“流入”这个属性。

    输出属性是一个带有 @Output 装饰器的可观察对象型的属性。 这个属性几乎总是返回 Angular 的EventEmitter。 当它通过事件绑定的形式被绑定时,值会“流出”这个属性。

    你只能通过它的输入和输出属性将其绑定到其它组件。

    模板表达式操作符

    模板表达式语言使用了 JavaScript 语法的子集,并补充了几个用于特定场景的特殊操作符。 下面介绍其中的两个:管道和安全导航操作符

    管道操作符 ( | )

    在绑定之前,表达式的结果可能需要一些转换。例如,可能希望把数字显示成金额、强制文本变成大写,或者过滤列表以及进行排序。

    Angular 管道对像这样的小型转换来说是个明智的选择。 管道是一个简单的函数,它接受一个输入值,并返回转换结果。 它们很容易用于模板表达式中,只要使用管道操作符 (|) 就行了。

    <div>Title through uppercase pipe: {{title | uppercase}}</div>
    

    安全导航操作符 ( ?. ) 和空属性路径

    Angular 的安全导航操作符 (?.) 是一种流畅而便利的方式,用来保护出现在属性路径中 null 和 undefined 值。 下例中,当 currentHero 为空时,保护视图渲染器,让它免于失败。

    The current hero's name is {{currentHero?.name}}
    

    当绑定中 title 属性为空,仍然会继续渲染

    非空断言操作符(!)

    在 TypeScript 2.0 中,你可以使用 --strictNullChecks 标志强制开启严格空值检查。TypeScript 就会确保不存在意料之外的 null 或 undefined。
    在这种模式下,有类型的变量默认是不允许 null 或 undefined 值的,如果有未赋值的变量,或者试图把 null 或 undefined 赋值给不允许为空的变量,类型检查器就会抛出一个错误

    Angular 模板中的**非空断言操作符(!)也是同样的用途。

    <div *ngIf="hero">
      The hero's name is {{hero!.name}}
    </div>
    

    与安全导航操作符不同的是,非空断言操作符不会防止出现 null 或 undefined。 它只是告诉 TypeScript 的类型检查器对特定的属性表达式,不做 "严格空值检测"。

    3. angular 声明周期钩子

    每个组件都有一个被 Angular 管理的生命周期,Angular 提供了生命周期钩子,把这些关键生命时刻暴露出来,你可以做一些自定义操作。

    钩子 用途及时机
    ngOnChanges() 当 Angular(重新)设置数据绑定输入属性时响应。 该方法接受当前和上一属性值的 [SimpleChanges](https://angular.cn/api/core/SimpleChanges) 对象当被绑定的输入属性的值发生变化时调用,首次调用一定会发生在 ngOnInit() 之前。
    ngOnInit() 在 Angular 第一次显示数据绑定和设置指令/组件的输入属性之后,初始化指令/组件。Initialize the directive/component after Angular first displays the data-bound properties and sets the directive/component's input properties.在第一轮 ngOnChanges() 完成之后调用,只调用一次
    ngDoCheck() 检测,并在发生 Angular 无法或不愿意自己检测的变化时作出反应。在每个 Angular 变更检测周期中调用,ngOnChanges() 和 ngOnInit() 之后。
    ngAfterContentInit() 当把内容投影进组件之后调用。第一次 ngDoCheck() 之后调用,只调用一次。
    ngAfterContentChecked() 每次完成被投影组件内容的变更检测之后调用。ngAfterContentInit() 和每次 ngDoCheck() 之后调用
    ngAfterViewInit() 初始化完组件视图及其子视图之后调用。第一次 ngAfterContentChecked() 之后调用,只调用一次。
    ngAfterViewChecked() 每次做完组件视图和子视图的变更检测之后调用。ngAfterViewInit() 和每次 ngAfterContentChecked() 之后调用。
    ngOnDestroy() 当 Angular 每次销毁指令/组件之前调用并清扫。 在这儿反订阅可观察对象和分离事件处理器,以防内存泄漏。在 Angular 销毁指令/组件之前调用

    OnInit钩子

    使用 ngOnInit() 有两个原因:

    • 在构造函数之后马上执行复杂的初始化逻辑
    • 在 Angular 设置完输入属性之后,对该组件进行准备。

    OnDestroy()钩子

    一些清理逻辑必须在 Angular 销毁指令之前运行,把它们放在 ngOnDestroy() 中。

    OnChanges() 钩子

    一旦检测到该组件(或指令)的输入属性发生了变化,Angular 就会调用它的 ngOnChanges() 方法

    ngOnChanges(changes: SimpleChanges) {
      for (let propName in changes) {
        let chng = changes[propName];
        let cur  = JSON.stringify(chng.currentValue);
        let prev = JSON.stringify(chng.previousValue);
        this.changeLog.push(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
      }
    }
    

    4.组件之间的交互

    通过输入型绑定把数据从父组件传到子组件

    HeroChildComponent 有两个输入型属性,它们通常带@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;
    }
    

    父组件 HeroParentComponent。

    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';
    }
    

    通过 setter 截听输入属性值的变化

    如果要对父组件赋值做修改,可以使用setter

    import { Component, Input } from '@angular/core';
     
    @Component({
      selector: 'app-name-child',
      template: '<h3>"{{name}}"</h3>'
    })
    export class NameChildComponent {
      private _name = '';
     
      @Input()
      set name(name: string) {
        this._name = (name && name.trim()) || '<no name set>';
      }
     
      get name(): string { return this._name; }
    }
    

    通过ngOnChanges()来截听输入属性值的变化

    使用 OnChanges 生命周期钩子接口的 ngOnChanges() 方法来监测输入属性值的变化并做出回应

    下面的 VersionChildComponent 会监测输入属性 major 和 minor 的变化,并把这些变化编写成日志以报告这些变化

    import { Component, Input, OnChanges, SimpleChange } from '@angular/core';
     
    @Component({
      selector: 'app-version-child',
      template: `
        <h3>Version {{major}}.{{minor}}</h3>
        <h4>Change log:</h4>
        <ul>
          <li *ngFor="let change of changeLog">{{change}}</li>
        </ul>
      `
    })
    export class VersionChildComponent implements OnChanges {
      @Input() major: number;
      @Input() minor: number;
      changeLog: string[] = [];
     
      ngOnChanges(changes: {[propKey: string]: SimpleChange}) {
        let log: string[] = [];
        for (let propName in changes) {
          let changedProp = changes[propName];
          let to = JSON.stringify(changedProp.currentValue);
          if (changedProp.isFirstChange()) {
            log.push(`Initial value of ${propName} set to ${to}`);
          } else {
            let from = JSON.stringify(changedProp.previousValue);
            log.push(`${propName} changed from ${from} to ${to}`);
          }
        }
        this.changeLog.push(log.join(', '));
      }
    }
    

    父组件监听子组件的事件

    子组件暴露一个 EventEmitter 属性,当事件发生时,子组件利用该属性 emits(向上弹射)事件。

    子组件的 EventEmitter 属性是一个输出属性,通常带有@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;
      }
    }
    

    父组件,可以绑定onVoted事件,框架(Angular)把事件参数(用 $event 表示)传给事件处理方法:

    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>
      `
    })
    export class VoteTakerComponent {
      agreed = 0;
      disagreed = 0;
      voters = ['Mr. IQ', 'Ms. Universe', 'Bombasto'];
     
      onVoted(agreed: boolean) {
        agreed ? this.agreed++ : this.disagreed++;
      }
    }
    

    父组件和子组件通过服务来通讯

    在之前讲服务时就提过,同一个module下的组件间,可以通过服务进行通讯。父组件和它的子组件共享同一个服务,利用该服务在家庭内部实现双向通讯。

    5.组件样式

    Angular 应用使用标准的 CSS 来设置样式。这意味着你可以把关于 CSS 的那些知识和技能直接用于 Angular 程序中,例如:样式表、选择器、规则以及媒体查询等。

    有几种方式把样式加入组件:

    • 设置 styles 或 styleUrls 元数据
    • 内联在模板的 HTML 中
    • 通过 CSS 文件导入

    预编译css

    如果使用 CLI 进行构建,那么你可以用 sass、less 或 stylus 来编写样式,并使用相应的扩展名(.scss、.less、.styl)把它们指定到 @Component.styleUrls 元数据中。例子如下:

    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.scss']
    })
    

    6.属性指令

    在 Angular 中有三种类型的指令:

    1. 组件 — 拥有模板的指令
    2. 结构型指令 — 通过添加和移除 DOM 元素改变 DOM 布局的指令
    3. 属性型指令 — 改变元素、组件或其它指令的外观和行为的指令。

    创建属性指令:

    ng generate directive highlight
    

    编写代码,这个指令实现高亮元素:

    import { Directive, ElementRef } from '@angular/core';
    
    @Directive({
      selector: '[appHighlight]'
    })
    export class HighlightDirective {
        constructor(el: ElementRef) {
           el.nativeElement.style.backgroundColor = 'yellow';
        }
    }
    

    通过ElementRef访问目标元素。

    使用属性型指令

    <p appHighlight>Highlight me!</p>
    

    使用 @Input 数据绑定向指令传递值

    @Input() highlightColor: string;
    
    <p appHighlight highlightColor="yellow">Highlighted in yellow</p>
    <p appHighlight [highlightColor]="'orange'">Highlighted in orange</p>
    

    绑定到 @Input 别名

    可以使用Input的别名

    @Input('appHighlight') highlightColor: string;
    <p [appHighlight]="color">Highlight me!</p>
    

    作者:Jadepeng
    出处:jqpeng的技术记事本--http://www.cnblogs.com/xiaoqi
    您的支持是对博主最大的鼓励,感谢您的认真阅读。
    本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    三菱Q系列PLC MC协议通讯
    相机常用属性配置简介[转]---Labview IMAQ 修改相机曝光等参数的方法
    数码显微镜的实际放大倍数的正确计算方法【转载】
    VS2012 C# 配置log4net
    CHM格式帮助文档无法打开的问题
    win10 下安装win7虚拟机
    杂记:使用RawCap和Wireshark对 127.0.0.1或localhost 进行抓包
    杂记:01
    linux应用编程一:文件IO和目录操作
    QTableWidget常用函数及注意事项
  • 原文地址:https://www.cnblogs.com/xiaoqi/p/angular-quick-study-part3.html
Copyright © 2011-2022 走看看