动手写一个angular版本的Message组件

学习一个框架或库的最好方法是看官方文档,并着手去写例子。最近在利用空闲的时间学习angular,那今天就尝试写一个message组件,并通过message服务动态加载message组件。
我所参与的项目基本是用jquery完成的。之前,在项目中自己动手写过一个简单的message插件,样子如下图。


success.jpg

那现在就使用angular(版本5.0.0)来实现message组件。

message组件

message组件要根据传入的类型、消息和duration来显示。创建三个文件:message.component.ts,message.component.html,message.component.css,代码如下。

//message.component.ts
import {Component,Input,OnInit,ChangeDetectionStrategy} from '@angular/core';
import {
   trigger,
   state,
   style,
   transition,
   animate
 } from '@angular/animations';
const mapping={
   success:'glyphicon-ok-sign',
   warning:'glyphicon-exclamation-sign',
   error:'glyphicon-exclamation-sign',
   info:'glyphicon-ok-circle'
}
@Component({
   selector:'upc-ng-message',
   templateUrl:'./message.component.html',
   styleUrls:['./message.component.css'],
   changeDetection:ChangeDetectionStrategy.OnPush
})
export class MessageComponent implements OnInit{
   ngOnInit(): void {
       this.typeClass=['upc-message-' + this.msgType];
       this.typeIconClass=[mapping[this.msgType]];
   }
   @Input() msgType:'success' | 'info' | 'warning' | 'error'='info'

   @Input() payload:string = ''

   private typeClass
   private typeIconClass
}
<!--*message.component.html-->
<div class="upc-message">
        <div class="upc-message-content" [ngClass]="typeClass">
            <i class="glyphicon" [ngClass]="typeIconClass"></i>
            {{payload}}
        </div>
</div>
.upc-message {
    position: fixed;
    z-index: 1999;
    width: 100%;
    top: 36px;
    left: 0;
    pointer-events: none;
    padding: 8px;
    text-align: center;
  }
  .upc-message i {
      margin-right: 8px;
      font-size: 14px;
      top: 1px;
      position: relative;
  }
  .upc-message-success i {
      color: green;
  }
  .upc-message-warning i {
      color: yellow;
  }
  .upc-message-error i {
      color: red;
  }
  .upc-message-content {
      padding: 8px 16px;
      -ms-border-radius: 4px;
      border-radius: 4px;
      -webkit-box-shadow: 0 2px 8px #000000;
      -ms-box-shadow: 0 2px 8px #000000;
      box-shadow: 0 2px 8px #000000;
      box-shadow: 0 2px 8px rgba(0,0,0,.2);
      background: #fff;
      display: inline-block;
      pointer-events: all;
  }

ComponentLoader

通过官方文档动态组件一节,可以了解动态创建组件需要通过ComponentFactoryResolver来完成。使用ComponentFactoryResolver创建 ComponentFactory,再通过ComponentFactory的create方法创建组件。看官方文档中API的说明,ComponentFactory的create方法至少需要一个injector参数,而injector的创建在文档中也有提到,其中参数providers为需要注入的类。再梳理下整个过程:

  1. 提供providers
  2. 创建Injector实例
  3. 创建ComponetFactory
  4. 使用ComponetFactory创建ComponentRef
//ComponentFactory的create方法
create(injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string|any, ngModule?: NgModuleRef<any>): ComponentRef<C>

//使用Injector的create创建injector实例
static create(providers: StaticProvider[], parent?: Injector): Injector

为了代码的复用,这里创建通用的loader类来完成组件的动态创建。其中,attch方法用于初始化ComponetFactory(参数为组件类型);to方法用于标识组件的父容器;provider方法用于初始化可注入的类;create方法用于创建组件并手动变更检测;remove方法用于移除组件。

import {
    ComponentFactoryResolver,
    ComponentFactory,
    ComponentRef,
    Type,
    Injector,
    Provider,
    ElementRef
} from '@angular/core';
export class ComponentLoader<T>{
    constructor(private _cfr: ComponentFactoryResolver,
        private _injector: Injector) {
    }
    private _componentFactory: ComponentFactory<T>
    attch(componentType: Type<T>): ComponentLoader<T> {
        this._componentFactory = this._cfr.resolveComponentFactory<T>(componentType);
        return this;
    }
    private _parent: Element
    to(parent: string | ElementRef): ComponentLoader<T> {
        if (parent instanceof ElementRef) {
            this._parent = parent.nativeElement;
        } else {
            this._parent = document.querySelector(parent);
        }

        return this;
    }
    private _providers: Provider[] = [];
    provider(provider: Provider) {
        this._providers.push(provider);
    }
    create(opts: {}): ComponentRef<T> {
        const injector = Injector.create(this._providers as any[], this._injector);
        const componentRef = this._componentFactory.create(injector);
        Object.assign(componentRef.instance, opts);
        if (this._parent) {
            this._parent.appendChild(componentRef.location.nativeElement);
        }
        componentRef.changeDetectorRef.markForCheck();
        componentRef.changeDetectorRef.detectChanges();
        return componentRef;
    }
    remove(ref:ComponentRef<T>){
        if(this._parent){
            this._parent.removeChild(ref.location.nativeElement)
        }
        ref=null;
    }
}

同时,为了便于loader的创建,再创建LoaderFactory类,代码如下:

import {
    ComponentFactoryResolver,
    Injector,
    Injectable,
    ElementRef
} from '@angular/core';
import { ComponentLoader } from './component-loader.class';

@Injectable()
export class ComponentLoaderFactory {
    constructor(private _injector: Injector,
        private _cfr: ComponentFactoryResolver) {

    }

    create<T>(): ComponentLoader<T> {
        return new ComponentLoader(this._cfr, this._injector);
    }
}

message service

message service提供显示message的API,代码如下:

import {Injectable,Injector} from '@angular/core';
import { ComponentLoaderFactory } from '../component-loader/component-loader.factory';
import {MessageComponent} from './message.component';
import {ComponentLoader} from '../component-loader/component-loader.class';

@Injectable()
export class MessageService{
    constructor(private _clf:ComponentLoaderFactory,private _injector:Injector){
        this.loader=this._clf.create<MessageComponent>();
    }
    private loader:ComponentLoader<MessageComponent>
    private createMessage(t,c,duration=2000){
        this.loader.attch(MessageComponent).to('body');
        const opts = {
            msgType: t,
            payload:c
        };
        const ref = this.loader.create(opts);
        ref.changeDetectorRef.markForCheck();
        ref.changeDetectorRef.detectChanges();
        let self=this;
        let st = setTimeout(() => {
            self.loader.remove(ref);
            st = null;
        }, duration);
    }
    public info(payload,duration?) {
        this.createMessage('info',payload,duration);
    }
    public success(payload,duration?) {
        this.createMessage('success',payload,duration);
    }
    public error(payload,duration?) {
        this.createMessage('error',payload,duration);
    }
    public warning(payload,duration?) {
        this.createMessage('warning',payload,duration);
    }
}

message.module

最后,增加message.module.ts。记得要把动态创建的组件添加到entryComponents数组中。

import {NgModule} from '@angular/core';
import { CommonModule } from '@angular/common';
import {MessageComponent} from './message.component';
import {MessageService} from './message.service';
import {ComponentLoaderFactory} from '../component-loader/component-loader.factory';

@NgModule({
   imports:[CommonModule],
   declarations:[MessageComponent],
   providers:[MessageService,ComponentLoaderFactory],
   entryComponents:[MessageComponent],
   exports:[MessageComponent]
})
export class MessageModule{
}

使用方法

注入MessageService,调用API使用Message组件。

this._msgService.success('成功了!');
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335