From a84becca522403c0f2cddff075eeb252e4e6bf12 Mon Sep 17 00:00:00 2001 From: Trotyl Date: Wed, 23 Aug 2017 12:00:43 +0800 Subject: [PATCH] feat(module:root): make nz-root optional --- src/components/ng-zorro-antd.module.spec.ts | 26 +++ src/components/ng-zorro-antd.module.ts | 9 +- src/components/root/nz-root-config.spec.ts | 32 +++ src/components/root/nz-root-config.ts | 32 +++ .../root/nz-root-style.component.ts | 11 + src/components/root/nz-root.component.spec.ts | 52 +++++ src/components/root/nz-root.component.ts | 43 ++-- src/components/root/nz-root.module.spec.ts | 58 ++++++ src/components/root/nz-root.module.ts | 28 ++- src/showcase/app.component.html | 188 +++++++++--------- src/showcase/app.module.ts | 2 +- .../nz-intro-getting-started/README.md | 10 +- 12 files changed, 352 insertions(+), 139 deletions(-) create mode 100644 src/components/ng-zorro-antd.module.spec.ts create mode 100644 src/components/root/nz-root-config.spec.ts create mode 100644 src/components/root/nz-root-config.ts create mode 100644 src/components/root/nz-root-style.component.ts create mode 100644 src/components/root/nz-root.component.spec.ts create mode 100644 src/components/root/nz-root.module.spec.ts diff --git a/src/components/ng-zorro-antd.module.spec.ts b/src/components/ng-zorro-antd.module.spec.ts new file mode 100644 index 0000000000..8855a5757f --- /dev/null +++ b/src/components/ng-zorro-antd.module.spec.ts @@ -0,0 +1,26 @@ +import { async, TestBed } from '@angular/core/testing'; +import { NgZorroAntdModule, NZ_ROOT_CONFIG, NzRootConfig } from './ng-zorro-antd.module'; + +describe('NgZorroAntdModule with Angular integration', () => { + it('should not provide root config with empty forRoot', async(() => { + TestBed.configureTestingModule({ + imports: [ + NgZorroAntdModule.forRoot(), + ], + }).compileComponents(); + + expect(TestBed.get(NZ_ROOT_CONFIG)).not.toBeDefined(); + })); + + it('should provide root config with params in forRoot', async(() => { + const options: NzRootConfig = { extraFontName: '', extraFontUrl: '' }; + + TestBed.configureTestingModule({ + imports: [ + NgZorroAntdModule.forRoot(options), + ], + }).compileComponents(); + + expect(TestBed.get(NZ_ROOT_CONFIG)).toBeDefined(); + })); +}); diff --git a/src/components/ng-zorro-antd.module.ts b/src/components/ng-zorro-antd.module.ts index 5c5ca2cd6e..15d166a8d9 100644 --- a/src/components/ng-zorro-antd.module.ts +++ b/src/components/ng-zorro-antd.module.ts @@ -53,6 +53,9 @@ import { NzMessageService } from './message/nz-message.service'; import { NzModalService } from './modal/nz-modal.service'; import { NzModalSubject } from './modal/nz-modal-subject.service'; +// Tokens (eg. global services' config) +import { NZ_ROOT_CONFIG, NzRootConfig } from './root/nz-root-config' + // --------------------------------------------------------- // | Exports // --------------------------------------------------------- @@ -108,6 +111,7 @@ export { NzModalSubject } from './modal/nz-modal-subject.service'; // Tokens (eg. global services' config) export { NZ_MESSAGE_CONFIG } from './message/nz-message-config'; export { NZ_NOTIFICATION_CONFIG } from './notification/nz-notification-config'; +export { NZ_ROOT_CONFIG, NzRootConfig } from './root/nz-root-config'; // --------------------------------------------------------- // | Root module @@ -159,13 +163,14 @@ export { NZ_NOTIFICATION_CONFIG } from './notification/nz-notification-config'; }) export class NgZorroAntdModule { - static forRoot(): ModuleWithProviders { + static forRoot(options?: NzRootConfig): ModuleWithProviders { return { ngModule: NgZorroAntdModule, providers: [ // Services NzNotificationService, - NzMessageService + NzMessageService, + { provide: NZ_ROOT_CONFIG, useValue: options }, ] }; } diff --git a/src/components/root/nz-root-config.spec.ts b/src/components/root/nz-root-config.spec.ts new file mode 100644 index 0000000000..99947c62e0 --- /dev/null +++ b/src/components/root/nz-root-config.spec.ts @@ -0,0 +1,32 @@ +import { createNzRootInitializer, NzRootConfig } from './nz-root-config'; + +describe('NzRootConfig', () => { + let mockDocument: Document; + let mockConfig: NzRootConfig; + let mockElement: HTMLDivElement; + + beforeEach(() => { + mockDocument = { head: { appendChild: () => null }, createElement: () => null } as any; + mockConfig = { extraFontName: '', extraFontUrl: '' } as any; + mockElement = {} as any; + + spyOn(mockDocument, 'createElement').and.returnValue(mockElement); + spyOn(mockDocument.head, 'appendChild'); + }); + + it('should apply extra font style when option provided', () => { + const iniliatizer = createNzRootInitializer(mockDocument, mockConfig); + iniliatizer(); + + expect(mockDocument.createElement).toHaveBeenCalledWith('style'); + expect(mockDocument.head.appendChild).toHaveBeenCalledWith(mockElement); + }); + + it('should not apply extra font style when option not provided', () => { + const iniliatizer = createNzRootInitializer(mockDocument); + iniliatizer(); + + expect(mockDocument.createElement).not.toHaveBeenCalled(); + expect(mockDocument.head.appendChild).not.toHaveBeenCalled(); + }); +}); diff --git a/src/components/root/nz-root-config.ts b/src/components/root/nz-root-config.ts new file mode 100644 index 0000000000..e3d56c0de6 --- /dev/null +++ b/src/components/root/nz-root-config.ts @@ -0,0 +1,32 @@ +import { InjectionToken } from '@angular/core'; + +export interface NzRootConfig { + extraFontName: string; + extraFontUrl: string; +} + +export const NZ_ROOT_CONFIG = new InjectionToken('NzRootConfig'); + +export function createNzRootInitializer(document: Document, options?: NzRootConfig) { + return function nzRootInitializer() { + if (options) { + const style = document.createElement('style'); + style.innerHTML = ` + @font-face { + font-family: '${options.extraFontName}'; + src: url('${options.extraFontUrl}.eot'); /* IE9*/ + src: + /* IE6-IE8 */ + url('${options.extraFontUrl}.eot?#iefix') format('embedded-opentype'), + /* chrome、firefox */ + url('${options.extraFontUrl}.woff') format('woff'), + /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/ + url('${options.extraFontUrl}.ttf') format('truetype'), + /* iOS 4.1- */ + url('${options.extraFontUrl}.svg#iconfont') format('svg'); + } + `; + document.head.appendChild(style); + } + } +} diff --git a/src/components/root/nz-root-style.component.ts b/src/components/root/nz-root-style.component.ts new file mode 100644 index 0000000000..799536d13c --- /dev/null +++ b/src/components/root/nz-root-style.component.ts @@ -0,0 +1,11 @@ +import { Component, ViewEncapsulation } from '@angular/core'; + +@Component({ + template : ``, + styleUrls : [ + '../style/index.less', + './style/index.less', + ], + encapsulation : ViewEncapsulation.None, +}) +export class NzRootStyleComponent { } diff --git a/src/components/root/nz-root.component.spec.ts b/src/components/root/nz-root.component.spec.ts new file mode 100644 index 0000000000..b07898f2d7 --- /dev/null +++ b/src/components/root/nz-root.component.spec.ts @@ -0,0 +1,52 @@ +import { ComponentFactoryResolver, Injector, ComponentRef, ComponentFactory, APP_INITIALIZER } from '@angular/core'; +import { async, inject, TestBed } from '@angular/core/testing'; +import { DOCUMENT } from '@angular/common'; +import { NzRootComponent } from './nz-root.component'; +import { NZ_ROOT_CONFIG, NzRootConfig } from './nz-root-config'; + +describe('NzRootComponent', () => { + let component: NzRootComponent; + let mockDocument: Document; + let mockConfig: NzRootConfig; + let mockElement: HTMLDivElement; + + beforeEach(() => { + mockDocument = { head: { appendChild: () => null }, createElement: () => null } as any; + mockConfig = { extraFontName: '', extraFontUrl: '' } as any; + mockElement = {} as any; + + spyOn(mockDocument, 'createElement').and.returnValue(mockElement); + spyOn(mockDocument.head, 'appendChild'); + }); + + it('should apply extra font style when input being set & option not provided', () => { + component = new NzRootComponent(mockDocument, undefined); + component.nzExtraFontName = 'some-name'; + component.nzExtraFontUrl = 'some-url'; + + component.ngOnInit(); + + expect(mockDocument.createElement).toHaveBeenCalledWith('style'); + expect(mockDocument.head.appendChild).toHaveBeenCalledWith(mockElement); + }); + + it('should not apply extra font style when option being provided', () => { + component = new NzRootComponent(mockDocument, mockConfig); + component.nzExtraFontName = 'some-name'; + component.nzExtraFontUrl = 'some-url'; + + component.ngOnInit(); + + expect(mockDocument.createElement).not.toHaveBeenCalled(); + expect(mockDocument.head.appendChild).not.toHaveBeenCalled(); + }); + + it('should not apply extra font style when option being provided', () => { + component = new NzRootComponent(mockDocument, undefined); + + component.ngOnInit(); + + expect(mockDocument.createElement).not.toHaveBeenCalled(); + expect(mockDocument.head.appendChild).not.toHaveBeenCalled(); + }); +}); diff --git a/src/components/root/nz-root.component.ts b/src/components/root/nz-root.component.ts index a9163a0b2a..1281f274ff 100644 --- a/src/components/root/nz-root.component.ts +++ b/src/components/root/nz-root.component.ts @@ -1,22 +1,18 @@ import { Component, Input, - ViewEncapsulation, OnInit, - Inject + Inject, + Optional, } from '@angular/core'; import { DOCUMENT } from '@angular/common'; +import { NZ_ROOT_CONFIG, NzRootConfig, createNzRootInitializer} from './nz-root-config'; @Component({ - selector : '[nz-root],nz-root', - encapsulation: ViewEncapsulation.None, - template : ` + selector : '[nz-root],nz-root', + template : ` `, - styleUrls : [ - '../style/index.less', - './style/index.less', - ] }) export class NzRootComponent implements OnInit { @@ -24,27 +20,18 @@ export class NzRootComponent implements OnInit { @Input() nzExtraFontName: string; @Input() nzExtraFontUrl: string; - constructor(@Inject(DOCUMENT) private _document: Document) { } + constructor( + @Inject(DOCUMENT) private _document: Document, + // Cannot use type annotation here due to https://github.com/angular/angular-cli/issues/2034 + // Should be revisited after AOT being made the only option + @Inject(NZ_ROOT_CONFIG) @Optional() private options: any | undefined, + ) { } ngOnInit() { - if (this.nzExtraFontName && this.nzExtraFontUrl) { - const style = this._document.createElement('style'); - style.innerHTML = ` - @font-face { - font-family: '${this.nzExtraFontName}'; - src: url('${this.nzExtraFontUrl}.eot'); /* IE9*/ - src: - /* IE6-IE8 */ - url('${this.nzExtraFontUrl}.eot?#iefix') format('embedded-opentype'), - /* chrome、firefox */ - url('${this.nzExtraFontUrl}.woff') format('woff'), - /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/ - url('${this.nzExtraFontUrl}.ttf') format('truetype'), - /* iOS 4.1- */ - url('${this.nzExtraFontUrl}.svg#iconfont') format('svg'); - } - `; - this._document.head.appendChild(style); + if (this.nzExtraFontName && this.nzExtraFontUrl && !this.options) { + const options: NzRootConfig = { extraFontName: this.nzExtraFontName, extraFontUrl: this.nzExtraFontUrl }; + const initializer = createNzRootInitializer(this._document, options); + initializer(); } } } diff --git a/src/components/root/nz-root.module.spec.ts b/src/components/root/nz-root.module.spec.ts new file mode 100644 index 0000000000..674196fc3e --- /dev/null +++ b/src/components/root/nz-root.module.spec.ts @@ -0,0 +1,58 @@ +import { ComponentFactoryResolver, Injector, ComponentRef, ComponentFactory, APP_INITIALIZER } from '@angular/core'; +import { async, inject, TestBed } from '@angular/core/testing'; +import { NzRootModule } from './nz-root.module'; +import { NzRootStyleComponent } from './nz-root-style.component'; + +describe('NzRootModule', () => { + let ngModule: NzRootModule; + let mockDocument: Document; + let mockInjector: Injector; + let mockFactoryResolver: ComponentFactoryResolver; + let mockElement: HTMLDivElement; + let mockComponentFactory: ComponentFactory; + let mockComponentRef: ComponentRef; + + beforeEach(() => { + mockDocument = { createElement: () => null } as any; + mockInjector = {} as any; + mockFactoryResolver = { resolveComponentFactory: () => null } as any; + mockElement = {} as any; + mockComponentFactory = { create: () => null } as any; + mockComponentRef = { destroy: () => null } as any; + + spyOn(mockDocument, 'createElement').and.returnValue(mockElement); + spyOn(mockComponentRef, 'destroy'); + spyOn(mockComponentFactory, 'create').and.returnValue(mockComponentRef); + spyOn(mockFactoryResolver, 'resolveComponentFactory').and.returnValue(mockComponentFactory); + }); + + beforeEach(() => { + ngModule = new NzRootModule(mockDocument, mockInjector, mockFactoryResolver); + }); + + it('should create style component when start', () => { + expect(mockDocument.createElement).toHaveBeenCalledWith('div'); + expect(mockFactoryResolver.resolveComponentFactory).toHaveBeenCalledWith(NzRootStyleComponent); + expect(mockComponentFactory.create).toHaveBeenCalledWith(mockInjector, null, mockElement); + }); + + it('should destroy style component when terminate', () => { + ngModule.ngOnDestroy(); + + expect(mockComponentRef.destroy).toHaveBeenCalled(); + }) +}); + +describe('NzRootModule with Angular integration', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [NzRootModule], + declarations: [], + providers: [], + }).compileComponents(); + })); + + it('should provide APP_INITIALIZER', inject([APP_INITIALIZER], (initializers: Function[]) => { + expect(initializers.some(x => x.name === 'nzRootInitializer')).toBe(true); + })); +}); diff --git a/src/components/root/nz-root.module.ts b/src/components/root/nz-root.module.ts index 24f7e4cf23..f37156ae6b 100644 --- a/src/components/root/nz-root.module.ts +++ b/src/components/root/nz-root.module.ts @@ -1,12 +1,28 @@ -import { NgModule } from '@angular/core'; +import { NgModule, OnDestroy, ComponentRef, ComponentFactoryResolver, Inject, Optional, Injector, APP_INITIALIZER } from '@angular/core'; +import { CommonModule, DOCUMENT } from '@angular/common'; import { NzRootComponent } from './nz-root.component'; -import { CommonModule } from '@angular/common'; +import { NzRootStyleComponent } from './nz-root-style.component'; +import { NZ_ROOT_CONFIG, createNzRootInitializer } from './nz-root-config'; @NgModule({ - exports : [ NzRootComponent ], - declarations: [ NzRootComponent ], - imports : [ CommonModule ] + exports : [ NzRootComponent ], + declarations : [ NzRootComponent, NzRootStyleComponent ], + imports : [ CommonModule ], + entryComponents : [ NzRootStyleComponent ], + providers : [ + { provide: APP_INITIALIZER, multi: true, useFactory: createNzRootInitializer, deps: [DOCUMENT, [new Optional(), NZ_ROOT_CONFIG]] }, + ], }) +export class NzRootModule implements OnDestroy { + private styleHostComponent: ComponentRef; -export class NzRootModule { + constructor(@Inject(DOCUMENT) _document: Document, injector: Injector, resolver: ComponentFactoryResolver) { + const componentFactory = resolver.resolveComponentFactory(NzRootStyleComponent); + const div = _document.createElement('div'); + this.styleHostComponent = componentFactory.create(injector, null, div); + } + + ngOnDestroy() { + this.styleHostComponent.destroy(); + } } diff --git a/src/showcase/app.component.html b/src/showcase/app.component.html index 8825347d54..0f7a179351 100644 --- a/src/showcase/app.component.html +++ b/src/showcase/app.component.html @@ -1,100 +1,98 @@ - -
- +
+
+
+ +
+
+
-
- + +
diff --git a/src/showcase/app.module.ts b/src/showcase/app.module.ts index d48d5179bc..a1f26071a9 100644 --- a/src/showcase/app.module.ts +++ b/src/showcase/app.module.ts @@ -19,7 +19,7 @@ import { NzHighlightModule } from './share/nz-highlight/nz-highlight.module'; BrowserAnimationsModule, FormsModule, HttpModule, - NgZorroAntdModule.forRoot(), + NgZorroAntdModule.forRoot({ extraFontName: 'anticon', extraFontUrl: './assets/fonts/iconfont' }), NzCodeBoxModule, NzHighlightModule, RouterModule.forRoot(routes, { useHash: true, preloadingStrategy: PreloadAllModules }) diff --git a/src/showcase/nz-intro-getting-started/README.md b/src/showcase/nz-intro-getting-started/README.md index 80007923b4..aca2aaa9bb 100644 --- a/src/showcase/nz-intro-getting-started/README.md +++ b/src/showcase/nz-intro-getting-started/README.md @@ -67,18 +67,14 @@ import { AppComponent } from './app.component'; export class AppModule { } ``` -这样就成功在全局引入了 ng-zorro-antd +这样就成功在全局引入了 ng-zorro-antd。 +> `NgZorroAntdModule.forRoot()` 方法能够接受一个可选的配置对象,用于引入外部的字体文件,类型为 `{ extraFontName: string, extraFontUrl: string }`。 用下面的代码替换 `/src/app/app.component.html` -> **注意**:务必要引入 `nz-root` 根组件, `nz-root` 必须放置在根component `(app.component.html)` 中,而不是替换 `app-root` ,根组件只能引入一次,所有 ng-zorro组件都应该包裹在下面,否则部分组件将不能正常工作 - ```html - - - - + ``` [查看](#/components/button)最简单的Button效果