angular 学习记录

环境安装

Node.js

yarn(https://www.yarnpkg.cn/)

npm install -g yarn

@angular/cli 命令

https://angular.cn/cli

npm install -g @angular/cli

ng new 创建项目

--routing = true| false(defualt)

--skip-install 跳过安装

ng serve 启动开发服务器

--port 4200

--open = true | false

ng serve --host 0.0.0.0    手机调试(手机和电脑连接同个wifi,手机打开电脑ip:4200)

ng bulid 应用程序或库编译到给定输出路径处名为 dist/ 的输出目录中。

--aot = true | false 使用提前编译进行构建

--prod = true | false    压缩打包

--base-href = baseHref 在index.html生成 <base> 标签

ng generate 创建模块/组件/服务

  • ng generate module [name]
    

    案例:

    ng generate module app-routing --flat --module=app
    
    参数 详情
    --flat 把这个文件放进了 src/app 中,而不是单独的目录中。
    --module=app 告诉 ng generate 把它注册到 AppModule 的 imports 数组中。
    --routing 生成 routing.module.ts 文件
  • ng generate component [name]
    

    案例:

    ng generate component info
    

  • ng generate service [name]
    

    案例:

    ng generate service info/infoService

    这里的info/info,前面个 info 是目录,后面 infoService 是名字

总结:

  1. ng generate component <name> 或 ng g c <name>:生成一个新的组件。这将创建一个包含 TypeScript、HTML 和 CSS 文件的新目录。
  2. ng generate directive <name> 或 ng g d <name>:生成一个新的指令。
  3. ng generate pipe <name> 或 ng g p <name>:生成一个新的管道。
  4. ng generate service <name> 或 ng g s <name>:生成一个新的服务。
  5. ng generate module <name> 或 ng g m <name>:生成一个新的模块。
  6. ng generate class <name> 或 ng g cl <name>:生成一个新的类。
  7. ng generate interface <name> 或 ng g i <name>:生成一个新的接口。
  8. ng generate enum <name> 或 ng g e <name>:生成一个新的枚举。
  9. ng generate guard <name> 或 ng g g <name>:生成一个新的守卫。
  10. ng generate resolver <name> 或 ng g r <name> 命令用于在 Angular 应用中生成一个新的 Resolver。

每个 ng generate 命令都会生成一个或多个文件,并更新项目的配置。例如,ng generate component 命令会更新 app.module.ts 文件,以声明新生成的组件。

注意:在 <name> 的位置,你应该提供你想要生成的实体的名称。例如,如果你想生成一个名为 "my-component" 的组件,你应该运行 ng generate component my-component

理解构建过程

main.js    包含从src/app 文件夹编译的输出

polyfills.js    包含javascriupt polyfill, 用于应用程序使用到,但是目标浏览器不支持的功能

runtime.js    加载其他模块

stlyes.js    全局CSS的JS代码

vendor.js    包含程序依赖的第三方包,包含 angular 包

顺序: runtime.js -> polyfills.js -> stlyes.js -> vendor.js -> main.js

文件加载顺序

绑定语法

数据绑定的类型

Angular 根据数据流的方向提供三种类型的数据绑定:

  • 从源到视图
  • 从视图到源
  • 双向,从视图到源再到视图
类型 语法 分类
插值 <br>Property <br>Attribute <br><br>样式 {{expression}} [target]="expression" 单向从数据源到视图
事件 (target)="statement" 单向从视图到数据源
双向 [(target)]="expression" 双向

插值以外的绑定类型在等号左侧有一个目标名称。绑定的目标是 property 或事件,你可以用方括号 ( [ ] ) 字符、括号 ( ( ) ) 字符或两者 ( [( )] ) 字符括起来。

[]()[()] 这些绑定标点以及前缀,用来指定数据流的方向。

  • 使用 [] 从源绑定到视图
  • 使用 () 从视图绑定到源
  • 使用 [()] 进行双向绑定,将视图绑定到源再绑定到视图

将表达式或语句放在双引号 "" 中等号的右侧。有关更多信息,请参见插值模板语句

绑定类型和目标

数据绑定的目标可以是 Property、事件或 Attribute 的名称。源指令的每个 public 成员都可以自动用于绑定模板表达式或模板语句中。下表总结了不同绑定类型的目标。

类型 目标 例子
属性 元素属性 <br>组件属性 <br>指令属性 下面例子中的 altsrchero 和 ngClass<br><br>``<img [alt]="hero.name" [src]="heroImageUrl"> <br><app-hero-detail [hero]="currentHero"></app-hero-detail>``<br><a [ngClass]="{frist:false,'second':true, three:true}">信息展示</a> ngclass 这里是对象,生成 <a _ngcontent-cfx-c16="" ng-reflect-ng-class="[object Object]" class="second three">信息展示</a>
事件 元素事件 <br>组件事件 <br>指令事件 下面例子中的 clickdeleteRequest 和 myClick<br><br>``<button type="button" (click)="onSave()">Save</button>  <br><app-hero-detail (deleteRequest)="deleteHero()"></app-hero-detail> <br><div (myClick)="clicked=$event" clickable>click me</div>
双向 事件与属性 <input [([ngModel)]="name">
Attribute Attribute (少数特例情况) <button type="button" [attr.aria-label]="help">help</button> help = false``这里生成 aria-label="false"
class 属性 <div [class.special]="isSpecial">Special</div> class="special"``当绑定表达式  isSpecial 为真值时,Angular 会添加类,当表达式为假值时,它会删除类

Property 和 Attribute 的比较

尽管从技术角度上说,可以设置 [attr.disabled] Attribute 这个绑定,但是它的值是不同的,差异在于其 Property 绑定必须是布尔值,而其相应的 Attribute 绑定则取决于该值是否为 null。考虑以下情况:

<input [disabled]="condition ? true : false"> 
<input [attr.disabled]="condition ? 'disabled' : null">

第一行使用 disabled 这个 Property,要使用布尔值。第二行使用 disabled 这个 Attribute,要判定是否为 null

通常,要使用 Property 绑定而不是 Attribute 绑定。因为布尔值很容易阅读,语法较短,并且 Property 绑定的性能更高。

要在运行的应用程序中查看 disabled 按钮,请参见现场演练 / 下载范例。本示例说明如何从组件中切换 disabled 这个 Property。


属性绑定: [属性名]

property 绑定

要为 image 元素的目标属性(src)赋值,请键入以下代码:

<img alt="item" [src]="itemImageUrl">

attribute 绑定

在 Attribute 名称前面加上前缀 attr,后跟一个点 .。然后,使用解析为字符串的表达式设置 Attribute 值。

<p [attr.attribute-you-are-targeting]="expression"></p>

类和样式绑定(属于 property 绑定):

1 绑定到单个 CSS class

要创建单个类绑定,请键入以下内容:

[class.sale]="onSale"

当绑定表达式 onSale 为真值时,Angular 会添加类,当表达式为假值时,它会删除类 —— undefined 除外。有关更多信息,参阅样式委托

2 绑定到多个 CSS 类

要绑定到多个类,请键入以下内容:

[class]="classExpression"

表达式可以是以下之一:

  • 用空格分隔的类名字符串。
  • 以类名作为键名并将真或假表达式作为值的对象。
  • 类名的数组。

对于对象格式,Angular 会在其关联的值为真时才添加类。

绑定类型 语法 输入属性 范例输入值
单一类绑定 [class.sale]="onSale" `boolean\ undefined\
多重类绑定 [class]="classExpression" string "my-class-1 my-class-2 my-class-3"
多重类绑定 [class]="classExpression" `Record<string, boolean\ undefined\
多重类绑定 [class]="classExpression" Array<string> ['foo', 'bar']

3 多个样式的绑定和嵌入式绑定

事件绑定: (event)

在事件绑定中,Angular 会为目标事件配置事件处理函数。你还可以将事件绑定用于自定义事件。

当组件或指令引发事件时,处理程序就会执行模板语句。模板语句会执行一个动作来响应这个事件。

处理事件

处理事件的常见方法之一是把事件对象 $event 传给处理该事件的方法。$event 对象通常包含该方法所需的信息,比如用户名或图片 URL。

目标事件决定了 $event 对象的形态。如果目标事件是来自原生 DOM 元素的,那么 $event 是一个DOM 事件对象,它具有 target 和 target.value 等属性。

在下面的例子中,代码通过绑定到 name 来设置 <input> 的 value 属性。

<input [value]="currentItem.name"
       (input)="currentItem.name=getValue($event)">

这个例子中,会发生下列操作:

  1. 该代码绑定到 <input> 元素的 input 事件,该事件允许代码监听这些更改。
  2. 当用户做出更改时,该组件会引发 input 事件。
  3. 这个绑定会在一个上下文中执行该语句,此上下文中包含 DOM 事件对象 $event
  4. Angular 会通过调用 getValue($event.target) 来获取更改后的文本,并用它更新 name 属性。

如果该事件属于某个指令或组件,那么 $event 就具有指令或组件中生成的形态。

在模板中,$event.target 的类型只是 EventTarget。在 getValue() 方法中,把此目标转为 HTMLInputElement 类型,以允许对其 value 属性进行类型安全的访问。

getValue(event: Event): string {
  return (event.target as HTMLInputElement).value;
}

双向绑定: [(....)]

双向绑定为应用中的组件提供了一种共享数据的方式。使用双向绑定绑定来侦听事件并在父组件和子组件之间同步更新值。

前提条件

为了充分利用双向绑定,你应该对以下概念有基本的了解:

双向绑定将属性绑定与事件绑定结合在一起:

绑定 详情
属性绑定 设置特定的元素属性。
事件绑定 侦听元素更改事件。

双向绑定工作原理

为了使双向数据绑定有效,@Output() 属性的名字必须遵循 inputChange 模式,其中 input 是相应 @Input() 属性的名字。比如,如果 @Input() 属性为 size,则 @Output() 属性必须为 sizeChange

后面的 sizerComponent 具有值属性 size 和事件属性 sizeChangesize 属性是 @Input(),因此数据可以流入 sizerComponentsizeChange 事件是一个 @Output(),它允许数据从 sizerComponent 流出到父组件。

案例

<!-- 1.普通的属性和事件实现绑定 -->
<!-- <input type="text" [value]="username" (input)="handuleInput($event)"> -->

<!-- 2.普通双向绑定 表单Form:使用 ngModel -->
<input type="text" [(ngModel)]="username">

<!-- 3.(失败了,当前版本不行,本想使用 [(username)])自定义get, set 方法 -->
<!-- <input type="text" [value]="username" (input)="username"> -->

<span>你好,{{username}}</span>

<!-- 在 Angular v6 中已不推荐把 ngModel 输入属性、ngModelChange 事件与响应式表单指令一起使用,并将在 Angular 的未来版本中删除。 -->
<!-- <input type="text" [ngModel]="username" (ngModelChange)="handuleInput($event)"> -->

horizontal-grid.component.ts

export class HorizontalGridComponent implements OnInit {

  // 1的普通绑定和2的双向绑定都行
  @Input() username = '';
  handuleInput(event:Event): void{
    this.username = (event.target as HTMLInputElement).value;
  }


  // 3.(失败了,当前版本不行,本想使用 [(username)])自定义get, set 方法
  // private _username = '';
  // @Output() usernameChange = new EventEmitter<string>();

  // @Input()
  // public get username() : string {
  //   return this._username;
  // }

  // public set username(username : string) {
  //   this._username = username;
  //   this.usernameChange.emit(username);
  // }


  constructor() { }

  ngOnInit() {
  }
}

NgNonBindable 停用绑定

通过 NgNonBindable 停用 Angular 处理过程

<p >This should  evaluate: {{ 1 + 1 }}</p>
<p ngNonBindable>This should not evaluate: {{ 1 + 1 }}</p>

指令

属性指令:修改宿主元素的行为和外观的类

如何使用属性指令:属性指令是已经应用 @Directive 装饰器的类

内置属性型指令

ngClass

第一种 text

<a [ngClass]="'first second'">信息展示</a>

生成

<a _ngcontent-qxr-c16="" ng-reflect-ng-class="first second" class="first second">信息展示</a>

第二种 array

<a [ngClass]="['frist','second']">信息展示</a>

生成

<a _ngcontent-yos-c16="" ng-reflect-ng-class="frist,second" class="frist second">信息展示</a>

第三种 object

<a [ngClass]="{frist:false,'second':true, three:true}">信息展示</a>

生成

<a _ngcontent-cfx-c16="" ng-reflect-ng-class="[object Object]" class="second three">信息展示</a>

ngStyle

添加和删除一组 HTML 样式

ngModel

将双向数据绑定添加到 HTML 表单元素

内置结构指令

应用结构指令时,它们通常以星号 * 为前缀,例如 *ngIf

NgIf

ts先声明一下:

export class AppComponent {
  public condition = true;
}

1 具有简写语法的简单形式:

<div *ngIf="condition">1.Content to render when condition is true.</div>具有扩展语法的简单形式:

2 扩展语法:

<ng-template [ngIf]="condition"><div>2.Content to render when condition is true.</div></ng-template>

3 带有 “else” 块的格式:

<div *ngIf="condition; else elseBlock">3.1 Content to render when condition is true.</div>
    <ng-template #elseBlock>3.2 Content to render when condition is false.</ng-template>

如果 condition = false;

4 带 “then” 和 “else” 块的简写形式:

<div *ngIf="condition; then thenBlock else elseBlock">4.1 这里的 condition 是条件,true 显示 #thenBlock</div>
<ng-template #thenBlock>4.1 Content to render when condition is true.</ng-template>
<ng-template #elseBlock>4.2 Content to render when condition is false.</ng-template>本地存储值的形式:

如果 condition = false;

5 本地存储值的形式:

<div *ngIf="condition as value; else elseBlock">{{value}}</div>
<ng-template #elseBlock>5.value is null.</ng-template>

如果 condition = false;

6 简写语法

*ngIf 的简写语法会把 "then" 和 "else" 子句分别扩展成两个独立的模板。比如,考虑下列简写语句,它要在等待数据加载期间显示一个加载中页面。

<div class="hero-list" *ngIf="heroes else loading">
 这里作为模板
</div>

<ng-template #loading>
 <div>Loading...</div>
</ng-template>

你可以看到,"else" 子句引用了带有 #loading 标签的 <ng-template>,而 then (就是为true的内容) 子句的模板是作为宿主元素的内容提供的。

不过,当 Angular 扩展此简写语法的时候,它创建了另一个带有 ngIf 和 ngIfElse 指令的 <ng-template>then(就是为true的内容) 需要自己创建 <ng-template> 的内容。

<ng-template [ngIf]="heroes" [ngIfElse]="loading">
 <div class="hero-list">
  单独需要模板
 </div>
</ng-template>

<ng-template #loading>
 <div>Loading...</div>
</ng-template>

NgFor

ngForOf 指令通常在 *ngFor 的简写形式内部使用。在这种形式下,每次迭代要渲染的模板是包含指令的锚点元素的内容。

<li> 元素中包含一些选项的简写语法。

<li *ngFor="let item of items; index as i; trackBy: trackByFn">...</li>

简写形式会扩展为使用 <ng-template> 元素 ngForOf 选择器的长形式。<ng-template> 元素的内容是包裹此简写格式指令的 <li> 元素。

<ng-template ngFor let-item [ngForOf]="items" let-i="index" [ngForTrackBy]="trackByFn">
  <li>...</li>
</ng-template>

局部变量

<li *ngFor="let user of users; index as i; first as isFirst">
   {{i}}/{{users.length}}. {{user}} <span *ngIf="isFirst">default</span>
</li>

NgForOf 导出了一系列值,可以指定别名后作为局部变量使用:

  • $implicit: T:迭代目标(绑定到 ngForOf)中每个条目的值。
  • ngForOf: NgIterable<T>:迭代表达式的值。当表达式不局限于访问某个属性时,这会非常有用,比如在使用 async 管道时(userStreams | async)。
  • index: number:可迭代对象中当前条目的索引。
  • count: number:可迭代对象的长度。
  • first: boolean:当前条目是第一个条目则为 true
  • last: boolean:当前条目是最后一个条目则为 true
  • even: boolean:当前条目在索引号为偶数则为 true
  • odd: boolean:当前条目在索引号为奇数则为 true

html

<ul>
  <li *ngFor="let item of tabs, let i = index">
    <a href="#" [class.active]="i === selectIndex" (click)="handleSelected(i)">{{item.title}}</a>
  </li>
</ul>

ts

export class AppComponent {
  title = 'pinduoduo';
  selectIndex = -1;
  ...
  handleSelected(index: number){
    this.selectIndex = index;
}

TrackBy

一个可选地传入 NgForOf 指令的函数,以自定义 NgForOf 如何唯一标识迭代中的条目。

在所有这些场景中,通常希望仅更新与受更改影响的条目关联的 DOM 元素。此行为对以下内容很重要:

  • 修改迭代器时保留任何特定于 DOM 的 UI 状态(例如光标位置、焦点、文本选择)
  • 条目添加、删除和重新排序的动画
  • 当使用 NgForOf 动态填充嵌套 <option> 元素并更新绑定迭代器时,保留 <select> 元素的值

NgSwitch

ngTemplateOutlet 重复内容块

<ng-template #titleTemplate>
  <h4 class="p-2 bg-success text-white">Repeated Content</h4>
</ng-template>

<ng-template [ngTemplateOutlet]="titleTemplate"></ng-template>
<div class="bg-info p-2">
  There are {{getProductCount()}} products.
</div>
<ng-template [ngTemplateOutlet]="titleTemplate"></ng-template>

ngTemplateOutletContext 接收上下文

为了接收上下文,在包含重复的 ng-template定义一个 let-属性,用于定义变量,类似 ngFor的指令属性。

下面创建一个 text的变量,并通过对表达式 title求值,为其赋值: let-text="title",然后使用 ngTemplateOutletContext提供映射对象。

<ng-template #titleTemplate2 let-text="title">
  <h4 class="p-2 bg-success text-white">{{text}}</h4>
</ng-template>

<ng-template [ngTemplateOutlet]="titleTemplate2" [ngTemplateOutletContext]="{title: 'Header'}" ></ng-template>
<div class="bg-info p-2">
  There are {{getProductCount()}} products.
</div>
<ng-template [ngTemplateOutlet]="titleTemplate2" [ngTemplateOutletContext]="{title: 'Footer'}"></ng-template>

其他指令

NgForOf

NgSwitchCase

NgSwitchDefault

自定义属性指令

a) 创建简单颜色属性案例

第一步,添加指令 @Directive

// attr.directive.ts
import { Directive, ElementRef, Renderer2 } from '@angular/core';

@Directive({
  selector: '[pa-attr]',
})
export class PaAttrDirective {
  constructor(element: ElementRef, private renderer2: Renderer2) {
    // element.nativeElement.classList.add('bg-success', 'text-white');
    this.renderer2.addClass(element.nativeElement, 'bg-success');
    this.renderer2.addClass(element.nativeElement, 'text-white');
  }
}

第二步,@NgModuledeclarations 添加指令 PaAttrDirective

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
// import { AppComponent } from './app.component';
import { ProductComponent } from './component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { PaAttrDirective } from './directive/attr.directive';

@NgModule({
  declarations: [ProductComponent, PaAttrDirective], //列出应用的类(组件、指令和管道),告诉Angular哪些类构成了这个模块。构成了这个模块。
  imports: [BrowserModule, FormsModule, ReactiveFormsModule],
  providers: [], //提供服务
  bootstrap: [ProductComponent], //根组件
})
export class AppModule {}

第三步,应用HTML

<div class="col-6">
    <table class="table table-sm table-bordered table-striped">
      <tr>
        <th></th><th>Name</th><th>Category</th><th>Price</th>
      </tr>
      <tr *ngFor="let item of getProducts(); let i=index" pa-attr>
        <td>{{i+1}}</td>
        <td>{{item.name}}</td>
        <td>{{item.category}}</td>
        <td>{{item.price}}</td>
      </tr>
    </table>
  </div>
</div>

b) 指令中访问应用程序数据

b.1) 读取宿主属性 @Attribute

添加一个属性指定宿主元素加入的CSS类。

html

<div class="col-6">
    <table class="table table-sm table-bordered table-striped">
      <tr>
        <th></th><th>Name</th><th>Category</th><th>Price</th>
      </tr>
      <tr *ngFor="let item of getProducts(); let i=index" pa-attr>
        <td>{{i+1}}</td>
        <td>{{item.name}}</td>
        <td pa-attr pa-attr-class="bg-warning">{{item.category}}</td>
        <td pa-attr pa-attr-class="bg-info">{{item.price}}</td>
      </tr>
    </table>
  </div>

pa-attr 属性被用于2个td元素,有一个新属性 pa-attr-class . 下面根据新属性,修改宿主元素

应用了 @Attribute 装饰器,创建指令类的新实例时,应该使用该属性为这个构造函数提供形参。

@Attribute 的限制:

元素属性是静态的。

// attr.directive.ts
import { Attribute, Directive, ElementRef, Renderer2 } from '@angular/core';

@Directive({
  selector: '[pa-attr]',
})
export class PaAttrDirective {
  constructor(
    element: ElementRef,
    private renderer2: Renderer2,
    @Attribute('pa-attr-class') bgClass: string
  ) {
    // element.nativeElement.classList.add('bg-success', 'text-white');
    this.renderer2.addClass(element.nativeElement, bgClass || 'bg-success');
    this.renderer2.addClass(element.nativeElement, 'text-white');
  }
}

b.2) 创建数据绑定输入属性 @Input

如果是需要动态的属性,就需要绑定对表达式的支持。

<div class="col-6">
    <table class="table table-sm table-bordered table-striped">
      <tr>
        <th></th><th>Name</th><th>Category</th><th>Price</th>
      </tr>
      <tr *ngFor="let item of getProducts(); let i=index" 
        [pa-attr]="getProducts().length < 6 ? 'bg-success':'bg-warning'">
        <td>{{i+1}}</td>
        <td>{{item.name}}</td>
        <td [pa-attr]="item.category == 'Ruby' ? 'bg-info': null">{{item.category}}</td>
        <td [pa-attr]="'bg-info'">{{item.price}}</td>
      </tr>
    </table>
  </div>

tr,td 的 pa-attr 属性中包含表达式,而不仅仅是静态的CSS类。

要实现绑定,需要在指令中创建一个输入属性。

// attr.directive.ts
import { Directive, ElementRef, Input, Renderer2 } from '@angular/core';

@Directive({
  selector: '[pa-attr]',
})
export class PaAttrDirective {
  @Input('pa-attr') bgClass?: string | null; //因为可以为 null. --> [pa-attr]="item.category == 'Ruby' ? 'bg-info': null"

  constructor(private element: ElementRef, private renderer2: Renderer2){}

  ngOnInit(){
    this.renderer2.addClass(this.element.nativeElement,  this.bgClass || 'bg-success');
    this.renderer2.addClass(this.element.nativeElement, 'text-white');
  }
}

@Input 装饰器应用于属性,并使用它来指定包含表达式的属性的名称。

顺序:

-> angular创建指令类的新实例

-> 构造函数

-> 设置输入的值 (意味构造函数阶段不能获取输入的值,所以需要在 ngOnInit 设置值)

-> ngOnInit

b.3) 响应输入属性的变化 ngOnChanges

添加新项仅影响新项的外观,而不影响现有元素。所以必须实现 ngOnChanges,实时接收通知。

SimpleChange 类属性和方法

previousValue: 输入属性的前值

currentValue: 输入属性的现值

isFirstChange: ngOnChanges 在 ngOnInit 调用之前,返回 true。

// attr.directive.ts
import {
  Directive,
  ElementRef,
  Input,
  OnChanges,
  Renderer2,
  SimpleChanges,
} from '@angular/core';

@Directive({
  selector: '[pa-attr]',
}) //implements OnChanges
export class PaAttrDirective {
  @Input('pa-attr') bgClass?: string | null; //因为可以为 null. --> [pa-attr]="item.category == 'Ruby' ? 'bg-info': null"

  constructor(private element: ElementRef, private renderer2: Renderer2) {}

  // ngOnInit(){
  //   this.renderer2.addClass(this.element.nativeElement,  this.bgClass || 'bg-success');
  //   this.renderer2.addClass(this.element.nativeElement, 'text-white');
  // }

  ngOnChanges(changes: SimpleChanges) {
    console.count()
    let change = changes['bgClass'];
    let classList = this.element.nativeElement.classList;
    if (!change.isFirstChange() && classList.contains(change.previousValue)) {
      classList.remove(change.previousValue);
    }
    if (!classList.contains(change.currentValue)) {
      classList.add(change.currentValue);
    }
  }

}

现在都更新了。

b.4) 创建自定义事件

EventEmitter 类为angular提供了事件机制。下面的例子创建EventEmitter赋值给一个click的变量。

  @Output("pa-category") click= new EventEmitter<string>();

emit(value) 这个方法触发与 EventEmitter 对象相关的自定义事件,为监听器提供对象或值。

输出属性 @Output 是一项让指令向宿主元素添加自定义事件的angular特性。

// attr.directive.ts
import {
  Directive,
  ElementRef,
  EventEmitter,
  Input,
  OnChanges,
  Output,
  Renderer2,
  SimpleChanges,
} from '@angular/core';
import { Product } from '../product.model';

@Directive({
  selector: '[pa-attr]',
}) //implements OnChanges
export class PaAttrDirective {
  @Input('pa-attr') bgClass?: string | null; //因为可以为 null. --> [pa-attr]="item.category == 'Ruby' ? 'bg-info': null"
  @Input("pa-product") product? :Product; 
  @Output("pa-category") click= new EventEmitter<string>();

  constructor(private element: ElementRef, private renderer2: Renderer2) {
    this.element.nativeElement.addEventListener('click', (event:any) => {
      if(this.product != null){
        this.click.emit(this.product.category);
      }
    });
  }

  ngOnChanges(changes: SimpleChanges) {
    console.count()
    let change = changes['bgClass'];
    let classList = this.element.nativeElement.classList;
    if (!change.isFirstChange() && classList.contains(change.previousValue)) {
      classList.remove(change.previousValue);
    }
    if (!classList.contains(change.currentValue)) {
      classList.add(change.currentValue);
    }
  }

}

下面绑定到自定义事件

<!-- 15.4 创建自定义事件 -->
<style>
  input.ng-dirty.ng-invalid {
    border: 2px solid #ff0000
  }

  input.ng-dirty.ng-valid {
    border: 2px solid #6bc502
  }
</style>

<div class="row m-2">
  <div class="col-6">
    <form class="m-2" novalidate (ngSubmit)="submitForm()">
      <div class="form-group">
        <label>Name</label>
        <input class="form-control" name="name" [(ngModel)]="newProduct.name" />
      </div>

      <div class="form-group">
        <label>Category</label>
        <input class="form-control" name="category" [(ngModel)]="newProduct.category" />
      </div>

      <div class="form-group">
        <label>Price</label>
        <input class="form-control" name="price" [(ngModel)]="newProduct.price" />
      </div>

      <button class="btn btn-primary" type="submit">
        Create
      </button>
    </form>
  </div>

  <div class="col-6">
    <table class="table table-sm table-bordered table-striped">
      <tr>
        <th></th><th>Name</th><th>Category</th><th>Price</th>
      </tr>
      <tr *ngFor="let item of getProducts(); let i=index" 
        [pa-attr]="getProducts().length < 6 ? 'bg-success':'bg-warning'"
        [pa-product]="item" 
        (pa-category)="newProduct.category = $event">
        <td>{{i+1}}</td>
        <td>{{item.name}}</td>
        <td [pa-attr]="item.category == 'Ruby' ? 'bg-info': null">{{item.category}}</td>
        <td [pa-attr]="'bg-info'">{{item.price}}</td>
      </tr>
    </table>
  </div>
</div>

$event用于由指令传递给 EventEmitter.emit 方法的值。

效果:点击后,赋值给Category.

c) 处理用户事件

本节会展示如何检测用户何时将鼠标移入或移出元素以及如何通过设置或清除突出显示颜色来进行响应。

HostListener

一个装饰器,用于声明要监听的 DOM 事件,并提供在该事件发生时要运行的处理器方法。

ng generate directive highlight

import { Directive, ElementRef, HostListener, Renderer2 } from '@angular/core';

@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {

  constructor(private el: ElementRef, private r2: Renderer2) {
    //第1种: 直接操作ElementRef
    // this.el.nativeElement.style.backgroundColor = 'yellow';
    //第2种: 推荐使用 renderer2 设置
    // this.r2.setStyle(this.el.nativeElement, "background-color", 'yellow');
   }

   @HostListener('mouseenter') onMouseEnter() {
    this.highlight('yellow');
  }

  @HostListener('mouseleave') onMouseLeave() {
    this.highlight('');
  }

  private highlight(color: string) {
    this.r2.setStyle(this.el.nativeElement, "background-color", color);
  }
}

html 当作属性使用

<p appHighlight>Highlight me!</p>

d) 将值传递给属性

app.component.html

若要绑定到 AppComponent.color 并回退为默认颜色“紫罗兰(violet)”,请添加以下 HTML。在这里,defaultColor 绑定没有使用方括号 [],因为它是静态的

  <h2>Pick a highlight color</h2>
  <div>
    <input type="radio" name="colors" (click)="color='lightgreen'">Green
    <input type="radio" name="colors" (click)="color='yellow'">Yellow
    <input type="radio" name="colors" (click)="color='cyan'">Cyan
  </div>
  <p [appHighlight]="color">Highlight me!</p>
  <p [appHighlight]="color" defaultColor="violet">
    Highlight me too!
  </p>

修改 AppComponent.color

export class AppComponent {
  color = '';
}

修改指令的 onMouseEnter,使其首先尝试使用 appHighlight 进行突出显示,然后尝试 defaultColor,如果两个属性都 undefined,则变回 red

@HostListener('mouseenter') onMouseEnter() {
  this.highlight(this.appHighlight || this.defaultColor || 'red');
}

如果没有默认颜色(defaultColor)绑定,则默认为红色。当用户选择一种颜色时,所选的颜色将成为突出显示的颜色。

<p >This should  evaluate: {{ 1 + 1 }}</p>
<p ngNonBindable>This should not evaluate: {{ 1 + 1 }}</p>

自定义结构指令

结构型指令简写形式

应用结构指令时,它们通常以星号 * 为前缀,例如 *ngIf。本约定是 Angular 解释并转换为更长形式的速记。Angular 会将结构指令前面的星号转换为围绕宿主元素及其后代的 <ng-template>

每个元素一个结构指令

重复一个 HTML 块是一个很常见的用例,但前提是在特定条件为真时。一种直观的方法是将 *ngFor 和 *ngIf 放在同一个元素上。但是,由于 *NgFor 和 *ngIf 都是结构指令,因此编译器会将其视为错误。你只能将一个 结构 指令应用于一个元素。

原因是简单。结构指令可以用宿主元素及其后代做复杂的事情。

这个用例有一个简单的解决方案:将 *ngIf 放在包装 *ngFor 元素的容器元素上。一个或两个元素可以是 <ng-container>,以便不会生成额外的 DOM 元素。****

ng-container

一种特殊元素,可以在不向 DOM 添加新元素的情况下保存结构指令。

理解:如果需要不用 div,就是用文本,就使用 ng-container。

例子:这里 condition 为 true,就显示文本1,而没有在其他的结构下面,当然一般可以用 span。

<ng-container *ngIf="condition; else templateA">
1
</ng-container>
<ng-template #templateA>
2
</ng-template>

创建结构型指令

UnlessDirective 与 *NgIf 相反,并且 condition 值可以设置为 true 或 false*NgIf 为 true 时显示模板内容;而 UnlessDirective 在这个条件为 false 时显示内容。

ng generate directive unless

每当条件的值更改时,Angular 都会设置 appUnless 属性。

  • 如果条件是假值,并且 Angular 以前尚未创建视图,则此 setter 会导致视图容器从模板创建出嵌入式视图。
  • 如果条件为真值,并且当前正显示着视图,则此 setter 会清除容器,这会导致销毁该视图。
// 4.2 自定义结构指令 
import { Directive, Input, TemplateRef, ViewContainerRef} from '@angular/core';

@Directive({
  selector: '[appUnless]'
})

export class UnlessDirective {
  private hasView = false;
  constructor(  private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef) { 

    }

    @Input() set appUnless(condition: boolean) {
      if (!condition && !this.hasView) {
        this.viewContainer.createEmbeddedView(this.templateRef);
        this.hasView = true;
      } else if (condition && this.hasView) {
        this.viewContainer.clear();
        this.hasView = false;
      }
    }
}

directive-test.component.html

<div *appUnless="condition" class="unless a">
  (A) 显示这一段是因为condition 为 false。
</div>


<div *appUnless="!condition" class="unless b">
  (B) 虽然condition 是 true,  显示这一段是因为appUnless被设置为false。
</div>

<p>
The condition =
<span [ngClass]="{ 'a': !condition, 'b': condition, 'unless': true }">{{condition}}</span>.
<button
  type="button"
  (click)="condition = !condition"
  [ngClass] = "{ 'a': condition, 'b': !condition }" >
  Toggle condition to {{condition ? 'false' : 'true'}}
</button>
</p>

error: NG0303: Can't bind to 'appUnless' since it isn't a known property of 'XXX'

如果'XXX'是一个Angular组件,并且它的输入是'appUnless',那么请确认它是声明该组件的@NgModule的一部分。

这里记录一下,遇到个问题,UnlessDirective 才发现忘记导出了。

指令的样式绑定和事件绑定

指令没有模板,要寄宿到一个宿主上(HOST)

@HostBinding: 绑定宿主的属性或者样式

@HostListener: 监听的 DOM 事件

@HostBinding: 绑定 Dom 的属性或者样式

静态的绑定

如果需要传入值,和 @Input() 一起使用。

export class HighlightDirective {
  // @Input() appHighlight = '';
  @Input() defaultColor = '';
  @HostBinding('style.background-color') @Input() appHighlight = '';
  constructor() {
   }
}

路由

angular - 路由

服务

服务用于放置和特定组件无关并希望跨组件共享的数据或逻辑。
服务出现的目的在于解耦组件类中的代码,是组件类中的代码干净整洁。

服务是由 Injectable 装饰器装饰的类。

依赖注入

提供依赖项

(1) 组件和Module级别依赖注入

第一步是添加 @Injectable 装饰器以表明此类可以被注入。

@Injectable()
class HeroService {

在组件级别,使用 @Component 装饰器的 providers 字段

@Component({
  selector: 'hero-list',
  template: '...',
  providers: [HeroService]
})
class HeroListComponent {}

在 NgModule 级别,要使用 @NgModule 装饰器的 providers 字段。在这种情况下,HeroService 可用于此 NgModule 或与本模块位于同一个 ModuleInjector 的其它模块中声明的所有组件、指令和管道。当你向特定的 NgModule 注册提供者时,同一个服务实例可用于该 NgModule 中的所有组件、指令和管道。要理解所有边缘情况,参见多级注入器

@NgModule({
  declarations: [HeroListComponent]
  providers: [HeroService]
})
class HeroListModule {}

新语法:

服务里面些注入到哪里

@Injectable({
    providedIn: HeroListModule 
})
class HeroService{}

(2) 全局依赖注入

在应用程序根级别,允许将其注入应用程序中的其他类。这可以通过将 providedIn: 'root' 字段添加到 @Injectable 装饰器来实现

@Injectable({
  providedIn: 'root'
})
class HeroService {}

当你在根级别提供服务时,Angular 会创建一个 HeroService 的共享实例,并将其注入到任何需要它的类中。在 @Injectable 元数据中注册提供者还允许 Angular 通过从已编译的应用程序中删除没用到的服务来优化应用程序,这个过程称为摇树优化(tree-shaking)。

注入依赖项

注入依赖项的最常见方法是在类的构造函数中声明它。当 Angular 创建组件、指令或管道类的新实例时,它会通过查看构造函数的参数类型来确定该类需要哪些服务或其他依赖项。例如,如果 HeroListComponent 要用 HeroService,则构造函数可以如下所示:

@Component({ … })
class HeroListComponent {
  constructor(private service: HeroService) {}
}

参考

多级注入器

依赖注入实战

表单

angular - 表单

模块与组件

angualr - 模块与组件

Rxjs

angular - Rxjs

HTTP Client

angular - HttpClient

参考

使用 HTTP 与后端服务进行通信

Ngrx

angular - Ngrx (Redux)

Proxy 代理(跨域问题)

  1. 在项目的 src/ 目录下创建一个 proxy.conf.json 文件。

  2. 往这个新的代理配置文件中添加如下内容:

{
"/api": {
  "target": "http://localhost:3000",
  "secure": false
}
}
  1. 在 CLI 配置文件 angular.json 中为 serve 目标添加 proxyConfig 选项:

参考


跨域解决方案

https://angular.cn/guide/build#proxying-to-a-backend-server

插件

Chrome扩展插件商店 - Angular DevTools

参考

Configure environment-specific defaults

配置应用环境 environment

Component

装饰器 · TypeScript中文网 · TypeScript——JavaScript的超集


概览 Overview

https://angular.cn/guide/component-overview

v17: https://v17.angular.cn/guide/component-overview

生命周期 Lifecycle

https://angular.cn/guide/lifecycle-hooks

v17: https://v17.angular.cn/guide/component-overview

组件之间的交互 Component interaction

https://angular.cn/guide/component-interaction

v17: https://v17.angular.cn/guide/component-interaction

在父子指令及组件之间共享数据 Sharing data between child and parent directives and components

https://angular.cn/guide/inputs-outputs

v17: https://v17.angular.cn/guide/inputs-outputs

Routing and navigation


基本路由

https://angular.cn/guide/routing/common-router-tasks

v17: https://v17.angular.cn/guide/router#defining-a-basic-route

嵌套路由

https://angular.cn/guide/routing/common-router-tasks#nesting-routes

v17: https://v17.angular.cn/guide/router#nesting-routes

防止未经授权的访问(路由守卫)

https://angular.cn/guide/routing/common-router-tasks#preventing-unauthorized-access

v17: https://v17.angular.cn/guide/router#preventing-unauthorized-access

惰性加载

https://v17.angular.cn/guide/router#lazy-loading

v17: https://v17.angular.cn/guide/lazy-loading-ngmodules

HTTP Client


https://angular.cn/guide/http

RxJS


https://rxjs.tech/guide/overview

操作符

https://rxjs.tech/guide/operators

Style guide


https://angular.cn/guide/styleguide


1.1 Directives folder

This folder contain all custom angular directive

https://angular.cn/guide/built-in-directives

1.2 Interceptors folder

This folder contain all custom angular interceptor

  • loading interceptor : add loading spiner when http to be executing
  • error interceptor : hanle error when http to be executing
  • auth interceptor : add token and antifogrey key when http to be executing

https://angular.cn/guide/http#intercepting-requests-and-responses

1.3 Interfaces folder

This folder contain all custom angular interface

1.4 Models folder

This folder contain all custom angular model

1.5 Pipe folder

This folder contain all custom angular pipe

  • translate pipe : mapping language file with key
  • filter pagination : filter list for elipsis pagination
  • filter checkbox : filter list for multi-level checkbox list

https://angular.cn/guide/pipes-overview

1.6 Services folder

This folder contain all service file which handle call api through http

https://angular.cn/guide/http

1.7 Types folder

This folder contain all custom user define type

1.8 Utilities folder

This folder contain all service file which contain common function

2. Pages folder

This folder contain all page, dialog

3. Shared folder

This folder contain all shared ui, custom control ...

4. Assets folder

This folder contain all media file (image, video, icon..) and language file

5. Environment folder

This folder contain all setting for each environment

https://angular.cn/guide/build#configuring-application-environments

posted @ 2023-01-28 15:04  【唐】三三  阅读(158)  评论(0编辑  收藏  举报