Angular核心概念之指令(Directive)

因为最近事情比较多,平时上班处于饱和状态,晚上回家也已经很累了,加上周末要去运动放松,实在难找出一整块时间去整理学习(ㄒoㄒ)。有过原创博客经历的人都知道,去高质量的总结、分享一些知识点其实非常的费时间,就比如去年分享的组件那篇,足足耗费了我一个下午,可是我还是觉得有很多细节没有表述清楚,加上我文笔一般,经常是想写的很多,但是打开编辑器就忘了思路…

虽然时间很紧,但我还是更倾向于高质量的总结和分享,年轻时要克服浮躁和焦虑,脚踏实地,稳步前进!

概述

Angular中的Directive分为三类:

  • 组件(Component): 带有模板的指令
  • 属性指令(Attribute Directives): 添加、删除DOM元素改变DOM结构
  • 结构指令(Structural directives): 改变元素、组件、其他指令外观和行为

组件是一种特殊的指令,详见组件。 本篇我们重点介绍另外两种指令。

常用的属性指令有内置的 NgStyleNgClass 等用来改变元素的属性、样式,还有官方实例中的HighLight指令,用来高亮元素,等等。

常用的结构指令有内置的 NgForNgIf 用来改变视图的结构。

创建指令

创建一个指令最基本的操作:

  1. 导入Directive装饰器(结构化指令还需要Input、TemplateRef和ViewContainerRef)
  2. 设置CSS选择器,Angular会在文本中定位此选择器
  3. 给指令类添加装饰器

示例代码

来源: Angular官方

  • 属性指令
<h1>My First Attribute Directive</h1>

<h4>Pick a highlight color</h4>
<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>
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
 
@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {
 
  constructor(private el: ElementRef) { }
 
  @Input() defaultColor: string;
 
  @Input('appHighlight') highlightColor: string;
 
  @HostListener('mouseenter') onMouseEnter() {
    this.highlight(this.highlightColor || this.defaultColor || 'red');
  }
 
  @HostListener('mouseleave') onMouseLeave() {
    this.highlight(null);
  }
 
  private highlight(color: string) {
    this.el.nativeElement.style.backgroundColor = color;
  }
}
  • 结构指令
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';

/**
 * Add the template content to the DOM unless the condition is true.
 *
 * If the expression assigned to `appUnless` evaluates to a truthy value
 * then the templated elements are removed removed from the DOM,
 * the templated elements are (re)inserted into the DOM.
 *
 * <div *ngUnless="errorCount" class="success">
 *   Congrats! Everything is great!
 * </div>
 *
 * ### Syntax
 *
 * - `<div *appUnless="condition">...</div>`
 * - `<ng-template [appUnless]="condition"><div>...</div></ng-template>`
 *
 */
@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;
    }
  }
}

注意事项

  1. 这里的方括号([])表示它的属性型选择器。Angular 会在模板中定位每个有一个属性叫 appHighlight 的元素,并且为这些元素加上本指令的逻辑。
  2. 一个宿主元素最多只能绑定一个结构指令,但是可以有多个属性指令
  3. 结构指令前面的星号(*)是语法糖,其实是Angular帮我们把宿主元素嵌入到中,所以结构指令一般都有两种写法,官方推荐带星号的语法糖形式
  4. 对于带有输入属性的指令,在模板中是否加([])的这个问题,例如:
<p [appHighlight]="'yellow'">Highlighted in yellow</p>
<p appHighlight="orange">Highlighted in orange</p>

[]是一个绑定到 @Input 的语法,等号(=)后面的内容是变量名,这不是Directive特有的,而是整个Angular的语法,详见Angular模板语法

  • 有方括号([])时:等号右侧引号内的变量必须在ts文件中存在,否则绑定失败,或者双引号内的变量是一个string,并且用单引号引起来
  • 没有方括号([])时:自定义的Directive或者Input属性将按照HTML规定的属性绑定去解析,双引号内的变量将会是string
  • 特殊情况:Boolean类型的true和false,是否写方括号,它的值都将会被正确解析。

未完待续…

Angular依赖注入-Dependency Injection

2018年继续来搞Angular!

什么是依赖注入?

根据维基百科,依赖注入(Dependency Injection)是种解决项目依赖性的设计模式, 好处有大致以下几点:

  • 各模块间的解耦
  • 代码容易维护
  • 开发者无需关注依赖的生产过程,拿来即用

Angular中的DI

Angular的依赖注入有三个重要概念:

  • 注入器(Injector)
  • Provider
  • 依赖(Dependence)

注入器连接了调用方和提供方,使得开发人员很轻松的实现依赖注入。(注:Angular框架已经实现了注入器的生成和调用,并不需要开发者去实现)

注入服务

  1. 通过import导入被依赖对象的服务
  2. Angular读取 @Component@Injectable@Module 装饰器里providers元数据
  3. 在组件构造函数中声明

这样几步,此组件及其子组件都能共享根组件创建的实例,如果子组件或模块不想复用从根组件获取的服务,可以在自己的注入器中重新配置注入(层级注入)。

需要注意的是:

  • Angular没有模块级别作用域,只有程序级和组件级作用域
  • 对于不同的执行上下文,有着不同的注入器,并且执行上下文中的每个依赖对象都是单例的
  • 后面初始化的服务会覆盖前面初始化的服务
  • 由于组件本身是一个类,类有继承关系,但是派生类组件不能继承父类组件的注入器,二者的注入器对象并没有关联,需要使用 super() 将对应的注入服务传递到父类
  • @Host@Optional等装饰器的巧妙使用会带来意想不到的惊喜

Provider

Provider这种设计模式由来已久,在前后台各种技术领域中被广泛使用。它藐视了注入器如何初始化Token所对应的依赖服务,最终注入到组件或者其他服务中。

Provider注册方式

  • 类Provider
    对于调用者来说,业务代码和接口没有改变,从而带来极大的便利
    {provider: Render, useClass: DomRender} //DOM渲染方式
    //{provider: Render, useClass: CanvasRender} //Canvas渲染方式
    //{provider: Render, useClass: ServerRender} //服务端渲染方式
    
  • 值Provider
    实际项目中,以来的对象不一定是类
    {provider: 'name', useValue: 'William Jing'}
    
  • 别名Provider
    实现多个依赖,一个对象实例的所用,例如为了让新旧服务同时可用,新服务兼容老服务,可以使用此种注册方式
    {provider: NewService, useClass: NewService}
    {provider: OldService, useExisting: NewService}
    
  • 工厂Provider
    有时候依赖对象是动态变化的,可能需要环境、执行权限来生成,工厂Provider可以提供解决这个问题,通过暴露一个工厂方法,返回一个最终的依赖对象
    let contactServiceFactory = (_logger: LoggerService, _userService: UserService) =>{
    return new contactService(_logger, _userService.user.isAuthorized)
    }
    
    export let contactServiceProvider = {
    provider: ContactService,
    userFactory: contactServiceFactory,
    deps: [LoggerService, UserService]
    };
    
Angular生命周期钩子

🎅🎅🎅提前祝Merry Xmas🎅🎅🎅

简介

Angular内部管理组件或指令的生命周期,给我们提供了一些接口(这些接口被称为生命周期钩子)来在允许开发者在这些事件触发时,执行相应的回调函数。

Angular一共提供了8个生命周期钩子接口,每个接口有一个唯一的前面加上‘ng’的方法,开发者可以根据实际情况实现其中的一个或者几个来对生命周期的各个阶段进行自定义处理。

图示

下图是Angular钩子方法执行顺序

hooks-in-sequence

钩子方法

  • ngOnChanges()
    首次调用发生在ngOnInit之前,并且,当且仅当组件输入数据变化时被调用,输入数据指的是通过@Input装饰器显示指定的那些变量。
  • ngOnInit()
    创建组件之后立刻调用,经常会使用ngOnInit获取数据。
  • ngDoCheck()
    用于变化监测那些Angular忽略的更改,每次变化监测发生时被调用
  • ngAfterContentInit()
    在组件中使用自定义内容的情况下在第一次ngDoCheck执行后调用,只执行一次
  • ngAfterContentChecked()
    Angular将外部内容嵌入到组件视图后,每次变化监测都会调用ngAfterContentChecked
  • ngAfterViewInit()
    Angular创建了组件的视图以及其子视图之后被调用
  • ngAfterViewChecked()
    视图以及其子视图第一次初始化之后和每次变化监测时被调用
  • ngOnDestroy()
    组件消失之前调用。根据官方文档描述,这里是用来释放那些不会被垃圾收集器自动回收的各类资源的地方。 取消那些对可观察对象和DOM事件的订阅。停止定时器。注销该指令曾注册到全局服务或应用级服务中的各种回调函数。 如果不这么做,就会有导致内存泄露的风险。

注意事项

  • constructor并不是生命周期钩子,而是Class级别的构造函数,constructor总是在所有钩子函数执行前执行
  • 接口是可选的,也就是说不必在Component或Directive后面加上implements…但是,官方还是强烈建开发者在指定类中添加接口以获得强类型和IDE编辑器带来的好处
  • 有的组件还提供了自己特有的生命周期钩子
  • 可能有人会问,为什么不在constructor里获取数据?是因为构造函数做的事应该尽可能简单,比如变量的初始化,不应该负责组件里的内容
  • 绝大多数情况下ngDoCheck和ngOnChanges不应该一起使用
  • ngDoCheck要慎用,因为每个检测周期内,无论数值是否发生变化,ngDoCheck都会被调用,导致调用非常频繁,所以我们的实现要必须非常轻量级
  • ngOnChanges如果输入属性是对象的话,只会检测对象的引用是否变化,而不会去监测对象属性的变化
  • AfterContent和AfterView一共4个钩子是组件专属的,不适用于指令

参考链接

中文官方文档

在线例子

万丈高楼平地起

趁今日闲暇时刻,来给当前的自己“存个档”。

领悟

最开始我立下flag称要写一个关于Angular的技术总结,两周一更新?哈哈哈哈哈,很明显,现在没有实现,打脸啪啪响?ionic框架做一个App?又打脸啪啪响? 其实不然,正是因为最近自己领悟到了–基本功才是硬道理。

很明显的例子,在我用ionic做App的时候,我十分依赖官方的组件,显然官方的组件是不够支撑起所有的需求,这时候如何“创造”一个组件就成了我的大难题。由于缺乏CSS的知识,我只能照猫画虎,生搬硬套,做出来的东西毫无条理性。还有,作为刚入行的新人,我是多么希望能有一位资深的“大佬”来带我们飞啊,但是我并没有在我的周围找到这样一个人。

前端,任重道远

所以,靠谁也不如靠自己!我在学Angular和ionic这种流行框架的同时(放心,Angular还会更新的,App也会做完的(^__^)),我还在追赶前端知识,犀牛书和H5&CSS3从入门到精(fàng)通(qì),不得不说,淘宝团队翻译的犀牛书真是精品,使我受益匪浅,而H5和CSS3的诸多高级玩法也让我练练发出“我屮艸芔茻,这也行”。

废话不多说了,希望自己尽快掌握并精通“前端三板斧”,走向前端大神之路。

基于Ionic3的Node.js中文社区客户端

是不是好久没更新博客了?

是因为我在“练功“–最近在弄一个开源项目:为Nodejs中文社区做一个第三方App,GitHub Repo->CNode-ionic,希望有志同道合的小伙伴多多提建议。

目前实现的功能有:(截止2017年10月9日)

  • 话题展示
  • 话题详情
  • 下拉刷新
  • Loading More
  • 3D Touch

正在进行

  • 扫码登陆
  • 个人信息展示
  • 评论点赞和互动
  • 本地缓存
  • 重构垃圾代码!

Update

  • 回头去看看自己半年前写的代码,啧啧啧,哎,不说了,看来是我进步了…