Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(module:root): make nz-root optional #36

Merged
merged 1 commit into from
Aug 24, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/components/ng-zorro-antd.module.spec.ts
Original file line number Diff line number Diff line change
@@ -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();
}));
});
9 changes: 7 additions & 2 deletions src/components/ng-zorro-antd.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
// ---------------------------------------------------------
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 },
]
};
}
Expand Down
32 changes: 32 additions & 0 deletions src/components/root/nz-root-config.spec.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
32 changes: 32 additions & 0 deletions src/components/root/nz-root-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { InjectionToken } from '@angular/core';

export interface NzRootConfig {
extraFontName: string;
extraFontUrl: string;
}

export const NZ_ROOT_CONFIG = new InjectionToken<NzRootConfig>('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);
}
}
}
11 changes: 11 additions & 0 deletions src/components/root/nz-root-style.component.ts
Original file line number Diff line number Diff line change
@@ -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 { }
52 changes: 52 additions & 0 deletions src/components/root/nz-root.component.spec.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
43 changes: 15 additions & 28 deletions src/components/root/nz-root.component.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,37 @@
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 : `
<ng-content></ng-content>
`,
styleUrls : [
'../style/index.less',
'./style/index.less',
]
})
export class NzRootComponent implements OnInit {

// Extra font definition
@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();
}
}
}
58 changes: 58 additions & 0 deletions src/components/root/nz-root.module.spec.ts
Original file line number Diff line number Diff line change
@@ -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<NzRootStyleComponent>;
let mockComponentRef: ComponentRef<NzRootStyleComponent>;

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);
}));
});
28 changes: 22 additions & 6 deletions src/components/root/nz-root.module.ts
Original file line number Diff line number Diff line change
@@ -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<NzRootStyleComponent>;

export class NzRootModule {
constructor(@Inject(DOCUMENT) _document: Document, injector: Injector, resolver: ComponentFactoryResolver) {
const componentFactory = resolver.resolveComponentFactory(NzRootStyleComponent);
Copy link
Member

@vthinkxie vthinkxie Aug 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

users may insert nz-root manually , maybe a judgement condition should be added here.

Copy link
Contributor Author

@trotyl trotyl Aug 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's already been resolved on NzRootComponent side:

this.nzExtraFontName && this.nzExtraFontUrl && !this.options.

const div = _document.createElement('div');
this.styleHostComponent = componentFactory.create(injector, null, div);
}

ngOnDestroy() {
this.styleHostComponent.destroy();
}
}
Loading