Angular- 巧用@component装饰器属性

Posted angelsunlike

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Angular- 巧用@component装饰器属性相关的知识,希望对你有一定的参考价值。

带有 @Component() 装饰器的 TypeScript 类是 Angular 组件组成非常重要的一部分,常用的属性有以下几种

  1. selector: CSS 选择器,用于定义如何在模板中使用组件
  2. template或者templateUrl: html 模板
  3. styles或styleUrls: 一组可选的 CSS 样式

还有一些不太常用的属性,我们一起来看看有哪些。

changeDetection

使用方式:

@Component({
  selector: \'app-child\',
  templateUrl: \'./child.component.html\',
  styleUrls: [\'./child.component.scss\'],
  changeDetection: ChangeDetectionStrategy.OnPush
})

changeDetection 用于给当前组件定义变更检测策略,当组件实例化之后,Angular 就会创建一个变更检测器,它负责传播组件各个绑定值的变化。源码中它的值为:changeDetection?: ChangeDetectionStrategy,我们再来看看源码中ChangeDetectionStrategy是如何定义的

export declare enum ChangeDetectionStrategy {
    OnPush = 0,
    Default = 1
}

Default : 变更策略为 CheckAlways,默认值。
这个没什么好说的,始终检测,即使组件内的属性和@Input属性都没有变化,也会不断检测变更
OnPush : 变更策略为 CheckOnce(按需)。
使用 ChangeDetectionStrategy.OnPush 也是优化性能的一种方法,毕竟它是按需检测嘛,不会不停的检测,但是使用它是有严格的条件的,不然一不小心就产生bug了,如果业务满足以下条件,可以放心使用:

  • 组件的视图变化仅仅依赖 @Input 属性的变化,如果有依赖组件本身的属性则不可以
  • @Input 属性为非对象的时候,或者为对象的时候的每次 @Input 属性的改变是改变对象本身的方式(eg: obj = xxx ),而不是改变引用的方式( eg: obj.propertyName = xxx )。

如果 @Input 属性的改变是改变引用的方式,还是想触发视图检测更改,还可以使用 ChangeDetectorRef 手动触发变更。
注意: Angular 每次做变更检测都是组件树从上至下,如果父组件设置了 OnPush 时,会按照此策略检测父组件以及其子组件,此时子组件即使设置为Default 也是无效的。而且由于父组件为 OnPush ,会导致传输给子组件的属性即使变化了也不会更新到子组件(因为视图没有重新渲染),使用它一定要考虑特别周全
哈哈,感觉越讲越多,changeDetection 可以单独写一篇了,如果大家有需要了解的,我再单独开一篇。好了,the next one~

viewProviders

提到这个就需要了解依赖注入,但是关于DI需要学习的非常多,值得单独讲解,本章的重点不是DI,所以不会讲太多。
viewProviders 在源码中的值为: viewProviders?: Provider[]; 表示定义一组仅在视图可用的Provider对象
注意:前提是该对象没有被注入到根模块,或者组件所在的模块,不然定义 viewProviders 显得毫无意义,如下我们有一个 ServiceDemo service

@Injectable() //注意这里不能加上{providedIn: \'root\'}不然就注入到根模块了,到处都可以使用
export class ServiceDemo {
  ...
}

我们把 ServiceDemo 加入 ChildComponent 组件的 viewProviders ServiceDemo 既没有被注入根模块也没有被注入 ChildComponent 所在的模块

@Component({
  selector: \'app-child\',
  templateUrl: \'./child.component.html\',
  styleUrls: [\'./child.component.scss\'],
  viewProviders: [ServiceDemo]
})
export class ChildComponent implements OnInit {
  ...
}

我们只能在 ChildComponent 的视图使用 ServiceDemo 的属性或者方法,如果在组件内使用就会报错。

providers

接以上,如果我们想在 ChildComponent 的视图和组件内使用 ServiceDemo 的属性或者方法,或者仅在组件内使用,则应该把它加入 providers,如下:

@Component({
  selector: \'app-child\',
  templateUrl: \'./child.component.html\',
  styleUrls: [\'./child.component.scss\'],
  providers: [ServiceDemo]
})
export class ChildComponent implements OnInit {
  ...
}

使用 viewProviders 或者 providers,目的是只把 provider 对象注入到某个组件或者组件视图,这样的 provider 对象一定是小范围内使用的封装对象,避免注入到根模块影响首屏加载。

encapsulation

用于定义 Component 的模板和样式封装选项。encapsulation 在源码中的值为 encapsulation?: ViewEncapsulation,来看看 ViewEncapsulation 如何定义的

export declare enum ViewEncapsulation {
    Emulated = 0, // 默认
    Native = 1, // 已废弃
    None = 2,
    ShadowDom = 3
}
  • Emulated:这是默认选项,会向 Host 元素添加 _nghost-xxx-xxx ,非 Host 元素添加 _ngcontent-xxx-xxx 属性模拟原生选择器,用来替代 class id ,以隔离组件的样式不影响外部,组件内定义的样式作用域仅在该组件。

    <app-parent _nghost-uvk-c19>
      <h1 _ngcontent-uvk-c19>parent component</h1>
      <p _ngcontent-uvk-c19>parent works!</p>
      <app-child _ngcontent-uvk-c19 _nghost-uvk-c20>
        <p _ngcontent-uvk-c20>child works</p>
      </app-child>
    </app-parent>
    
  • None :不提供任何模板或样式封装,意味着该组件内的样式会被添加到全局,应用到整个document
  • ShadowDom:使用原生的 Shadow DOM 特性封装,并为组件的 Host 元素创建 ShadowRoot,组件样式将不受全局样式影响,组件内定义的样式作用域限定为该 Shadow DOM 。使用了 ShadowDom 组件的子组件也会应用到该组件样式,因为子组件也在 ShadowRoot 中, ShadowDom 可应用于微前端样式隔离。

interpolation

改写默认的插值表达式起止分界符({{ 和 }})。它的值定义为 interpolation?: [string, string]

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

如果你不喜欢使用{{ value }}这样的插值方式,可以改成你喜欢的方式eg: (value)

preserveWhitespaces

它的值为 Boolean ,默认为 false,编译后的模板中移除可能多余的空白字符。 空白字符就是指那些能在 javascript 正则表达式中匹配 \\s 的字符。为 true 则保留。

entryComponents

一个组件的集合,它应该和当前组件一起编译。对于这里列出的每个组件,Angular 都会创建一个 ComponentFactory 并保存进 ComponentFactoryResolver 中, 官方的解释,是不是看完还是不知道是干嘛的?跟我一起了解一下

假设你在项目定义了一些组件,这些组件编译工具没有识别出来它在项目中有使用,Tree Shaking 工具就会把这些组件从最终的代码包中摘出去。但是其实这些组件可能是路由组件或者是动态加载的,必须把它们显式添加到 entryComponents 中, 路由组件需要添加到 @NgModuleentryComponents 中,动态加载的组件看它的使用范围,如果仅仅在一两个组件内使用了,可以把它们加入组件的 entryComponents

animations

用来引入定义动画的触发器,需要结合 @angular/animations 使用,本章就不重点讲解

moduleId

包含该组件的那个模块的ID。在使用CommonJS 作为模块化工具的时候用得到,angular 使用的是es6模块化语法,日常开发估计用不到这个属性。


以下是继承自 @Directive 的属性

inputs

列举一组可供数据绑定的输入属性,作用类似 @Input ,Angular 会在变更检测期间自动更新输入属性,使用方法如下:

@Component({
  selector: \'app-child\',
  templateUrl: \'./child.component.html\',
  styleUrls: [\'./child.component.scss\'],
  inputs: [\'name\', \'id: child-id\']
})
export class ChildComponent implements OnInit {
  name: string;
  id: number; // 指代child-id的值
}

<app-child [name]="\'child\'" [child-id]="1"></app-child>

使用inputs的时候编辑器还会提示 Use @Input rather than the inputs metadata property,看来官方也不推荐 inputs,使用 @Input 可以定义私有或共有,还可以定义数据类型,实在找不到inputs存在的意义哈哈~~
或许不用引入 @Input,仅仅查看 @Component 装饰器的配置就能知道输入属性是它的优点,也或许它又其他的优势,只是我技术有限还还没发现哈哈~~~

outputs

同inputs类似,功能同@Output一样,用法如下:

@Component({
  selector: \'app-child\',
  outputs: [ \'bankNameChange\' ]
  template: `<input (input)="bankNameChange.emit($event.target.value)" />`
})
class ChildDir {
 bankNameChange: EventEmitter<string> = new EventEmitter<string>();
}

<app-child (bankNameChange)="onBankNameChange($event)"></app-child>

exportAs

定义一个名字,用于在模板中把该指令赋值给一个变量。
Component 虽然继承了 Directive 这个属性,但我觉得这个属性在 Component 发挥不了什么特别的作用,在 Directive 还是有点用处的。
假设我们定义了一个 child-dir 指令

@Directive({
  selector: \'child-dir\',
  exportAs: \'child\'
})
class ChildDir {
}

现在我们在模板中使用它

<div child-dir #c="child" #c1></div>

在组件中获取它们

@ViewChild(\'c\') child;
@ViewChild(\'c1\') child1;

打印它们可以发现c为 child-dir 指令的实例, c1为 ElementRef 实例。这时好像还是不明白它的具体用处。
ElementRef 实例可以用来操作DOM, 获取child-dir 实例就可以访问它的属性和方法了,下面就 Angular 的内置指令 NgModel 举例说明。
NgModel 指令也用到了 exportAs 值为 ngModel

<input type="text" [(ngModel)]="text" #textModel="ngModel" [required]="true">

// 模板中
<p *ngIf="textModel.hasError(\'required\')" class="error">This field is required</p>

// 组件中
@ViewChild(\'textModel\') textModel: NgModel;

/** 现在可以访问
this.textModel.hasError(\'required\')
this.textModel.valid
this.textModel.disabled
**/

是不是很方便,不用 new FormGroup 或者 FormControl 也能访问我们想要的属性和方法,以上仅适用于简单的表单验证,建议还是使用 new FormGroup 创建一个 Form 对象对表单进行处理,让代码更具有可读性。

queries

配置一些查询,它们将被注入到该指令中。
作用同 @ViewChildren@ContentChildren@ViewChild@ContentChild类似

host

使用一组键-值对,把类的属性映射到宿主元素的绑定(Property、Attribute 和事件)。

@Component({
  selector: \'app-child\',
  templateUrl: \'./child.component.html\',
  host: {
    \'name\': \'app-child\',
    \'onclick\': \'return false\' // 表示调用DOM 事件的 preventDefault 函数,这个语句中可以引用局部变量 event 来获取事件数据
  }
})
// 渲染结果
<app-child _ngcontent-kge-c48 name="app-child" onclick="return false" _nghost-kge-c49></app-child>

jit

如果存在,则该指令/组件将被 AOT 编译器忽略。它会保留在发布代码中,并且 JIT 编译器会尝试在运行时在浏览器中对其进行编译。为了确保其行为正确,该应用程序必须导入 @angular/compiler 。

喜欢就点个赞吧~~~

以上是关于Angular- 巧用@component装饰器属性的主要内容,如果未能解决你的问题,请参考以下文章

Angular 2 '@Component' 装饰器是不是总是需要元素名称选择器?

Angular 中的 @Directive 与 @Component

Angular 中的 @Directive 与 @Component

Angular 中的 @Directive 与 @Component

Angular 中的 @Directive 与 @Component

Angular 中的 @Directive 与 @Component