├── src ├── assets │ └── .gitkeep ├── app │ ├── app.component.css │ ├── shared │ │ ├── shared.module.ts │ │ └── shared.module.spec.ts │ ├── app.component.ts │ ├── bar │ │ ├── bar.module.spec.ts │ │ ├── components │ │ │ └── bar │ │ │ │ ├── bar.component.ts │ │ │ │ └── bar.component.spec.ts │ │ └── bar.module.ts │ ├── foo │ │ ├── foo.module.spec.ts │ │ ├── foo.module.ts │ │ └── components │ │ │ └── foo │ │ │ ├── foo.component.spec.ts │ │ │ └── foo.component.ts │ ├── app.module.ts │ ├── app.component.spec.ts │ └── app.component.html ├── tslint.json ├── favicon.ico ├── styles.css ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── tsconfig.app.json ├── tsconfig.spec.json ├── index.html ├── main.ts ├── test.ts ├── karma.conf.js └── polyfills.ts ├── .prettierignore ├── projects ├── dynamic-service │ ├── src │ │ ├── public_api.ts │ │ ├── lib │ │ │ ├── dynamic-service.module.ts │ │ │ ├── dynamic-service.service.spec.ts │ │ │ └── dynamic-component.service.ts │ │ └── test.ts │ ├── tslint.json │ ├── tsconfig.spec.json │ ├── ng-package.json │ ├── package.json │ ├── tsconfig.lib.json │ ├── karma.conf.js │ └── README.MD └── lazy │ ├── tslint.json │ ├── src │ ├── public_api.ts │ ├── lib │ │ ├── hero-loader.module.ts │ │ ├── hero-loader.directive.spec.ts │ │ └── hero-loader.directive.ts │ └── test.ts │ ├── tsconfig.spec.json │ ├── ng-package.json │ ├── package.json │ ├── tsconfig.lib.json │ ├── karma.conf.js │ └── README.MD ├── e2e ├── src │ ├── app.po.ts │ └── app.e2e-spec.ts ├── tsconfig.e2e.json └── protractor.conf.js ├── .editorconfig ├── prettier.config.js ├── browserslist ├── .gitignore ├── tsconfig.json ├── README.md ├── tslint.json ├── package.json └── angular.json /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json -------------------------------------------------------------------------------- /src/app/app.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": {} 4 | } 5 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/herodevs/herodevs-packages/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /projects/dynamic-service/src/public_api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of dynamic-service 3 | */ 4 | 5 | export * from './lib/dynamic-component.service'; 6 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "exclude": ["test.ts", "**/*.spec.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /projects/dynamic-service/src/lib/dynamic-service.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | 3 | @NgModule({ 4 | imports: [], 5 | declarations: [], 6 | exports: [], 7 | }) 8 | export class DynamicServiceModule {} 9 | -------------------------------------------------------------------------------- /projects/dynamic-service/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [true, "attribute", "lib", "camelCase"], 5 | "component-selector": [true, "element", "lib", "kebab-case"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /projects/lazy/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | // "directive-selector": [true, "attribute", "lazy", "camelCase"], 5 | // "component-selector": [true, "element", "lazy", "kebab-case"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /projects/lazy/src/public_api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of HeroLoader 3 | */ 4 | 5 | // Because you need the provider of NgModuleFactoryLoader, you have 6 | // to import the Module, not just the component 7 | 8 | export * from './lib/hero-loader.module'; 9 | -------------------------------------------------------------------------------- /src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | @NgModule({ 5 | imports: [CommonModule], 6 | declarations: [], 7 | exports: [], 8 | }) 9 | export class SharedModule {} 10 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": ["jasmine", "node"] 6 | }, 7 | "files": ["test.ts", "polyfills.ts"], 8 | "include": ["**/*.spec.ts", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /projects/lazy/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": ["jasmine", "node"] 6 | }, 7 | "files": ["src/test.ts"], 8 | "include": ["**/*.spec.ts", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'], 7 | }) 8 | export class AppComponent { 9 | title = 'hdpackages'; 10 | } 11 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /projects/dynamic-service/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": ["jasmine", "node"] 6 | }, 7 | "files": ["src/test.ts"], 8 | "include": ["**/*.spec.ts", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /projects/dynamic-service/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/dynamic", 4 | "lib": { 5 | "entryFile": "src/public_api.ts", 6 | "umdModuleIds": { 7 | "dynamicComponentService": "dynamicComponentService" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/app/bar/bar.module.spec.ts: -------------------------------------------------------------------------------- 1 | import { BarModule } from './bar.module'; 2 | 3 | describe('BarModule', () => { 4 | let barModule: BarModule; 5 | 6 | beforeEach(() => { 7 | barModule = new BarModule(); 8 | }); 9 | 10 | it('should create an instance', () => { 11 | expect(barModule).toBeTruthy(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/app/foo/foo.module.spec.ts: -------------------------------------------------------------------------------- 1 | import { FooModule } from './foo.module'; 2 | 3 | describe('FooModule', () => { 4 | let fooModule: FooModule; 5 | 6 | beforeEach(() => { 7 | fooModule = new FooModule(); 8 | }); 9 | 10 | it('should create an instance', () => { 11 | expect(fooModule).toBeTruthy(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/app/shared/shared.module.spec.ts: -------------------------------------------------------------------------------- 1 | import { SharedModule } from './shared.module'; 2 | 3 | describe('SharedModule', () => { 4 | let sharedModule: SharedModule; 5 | 6 | beforeEach(() => { 7 | sharedModule = new SharedModule(); 8 | }); 9 | 10 | it('should create an instance', () => { 11 | expect(sharedModule).toBeTruthy(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /projects/lazy/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/loader", 4 | "lib": { 5 | "entryFile": "src/public_api.ts", 6 | "umdModuleIds": { 7 | "heroLoader": "heroLoader" 8 | } 9 | }, 10 | "whitelistedNonPeerDependencies": ["@herodevs/dynamic-component-service"] 11 | } 12 | -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | 3 | describe('workspace-project App', () => { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('Welcome to hdpackages!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HD Packages 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 110, 3 | tabWidth: 2, 4 | useTabs: false, 5 | semi: true, 6 | singleQuote: true, 7 | trailingComma: 'all', 8 | bracketSpacing: true, 9 | jsxBracketSameLine: false, 10 | arrowParens: 'always', 11 | rangeStart: 0, 12 | rangeEnd: Infinity, 13 | requirePragma: false, 14 | insertPragma: false, 15 | proseWrap: 'preserve', 16 | }; 17 | -------------------------------------------------------------------------------- /browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # 5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed 6 | 7 | > 0.5% 8 | last 2 versions 9 | Firefox ESR 10 | not dead 11 | not IE 9-11 -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | 14 | -------------------------------------------------------------------------------- /projects/lazy/src/lib/hero-loader.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, NgModuleFactoryLoader, SystemJsNgModuleLoader } from '@angular/core'; 2 | import { HeroLoaderDirective } from './hero-loader.directive'; 3 | 4 | @NgModule({ 5 | imports: [], 6 | declarations: [HeroLoaderDirective], 7 | exports: [HeroLoaderDirective], 8 | providers: [{ provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader }], 9 | }) 10 | export class HeroLoaderModule {} 11 | -------------------------------------------------------------------------------- /src/app/bar/components/bar/bar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnDestroy, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-bar', 5 | template: ` 6 |

bar loaded! {{ num }}

7 | `, 8 | styles: [], 9 | }) 10 | export class BarComponent implements OnInit, OnDestroy { 11 | @Input() num = 0; 12 | 13 | constructor() {} 14 | 15 | ngOnInit() {} 16 | 17 | ngOnDestroy() { 18 | console.log('destroying barcomponent'); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /projects/dynamic-service/src/lib/dynamic-service.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { DynamicComponentService } from './dynamic-component.service'; 4 | 5 | describe('DynamicServiceService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: DynamicComponentService = TestBed.get(DynamicComponentService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /projects/lazy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@herodevs/hero-loader", 3 | "description": "This component allows you to lazily load your Angular module whenever you want, instead of being restricted to lazy loading on route changes.", 4 | "version": "2.0.1", 5 | "license": "MIT", 6 | "private": false, 7 | "dependencies": { 8 | "@herodevs/dynamic-component-service": "^2.0.1" 9 | }, 10 | "peerDependencies": { 11 | "@angular/common": "^8.0.0-rc.0 || ^8.0.0", 12 | "@angular/core": "^8.0.0-rc.0 || ^8.0.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/bar/bar.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule, Routes } from '@angular/router'; 4 | import { BarComponent } from './components/bar/bar.component'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: BarComponent, 10 | }, 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [CommonModule, RouterModule.forChild(routes)], 15 | declarations: [BarComponent], 16 | bootstrap: [BarComponent], 17 | }) 18 | export class BarModule {} 19 | -------------------------------------------------------------------------------- /src/app/foo/foo.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | import { HeroLoaderModule } from '@herodevs/hero-loader'; 5 | import { SharedModule } from '../shared/shared.module'; 6 | import { FooComponent } from './components/foo/foo.component'; 7 | 8 | @NgModule({ 9 | imports: [CommonModule, RouterModule, SharedModule, HeroLoaderModule], 10 | declarations: [FooComponent], 11 | exports: [FooComponent], 12 | }) 13 | export class FooModule {} 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | -------------------------------------------------------------------------------- /projects/dynamic-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@herodevs/dynamic-component-service", 3 | "description": "This is a dynamic service.", 4 | "version": "2.0.1", 5 | "license": "MIT", 6 | "private": false, 7 | "peerDependencies": { 8 | "@angular/common": "^8.0.0-rc.0 || ^8.0.0", 9 | "@angular/core": "^8.0.0-rc.0 || ^8.0.0" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/herodevs/herodevs-packages" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/herodevs/herodevs-packages/issues" 17 | }, 18 | "homepage": "https://github.com/herodevs/herodevs-packages", 19 | "author": "Frosty " 20 | } 21 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { RouterModule, Routes } from '@angular/router'; 4 | 5 | import { AppComponent } from './app.component'; 6 | import { FooComponent } from './foo/components/foo/foo.component'; 7 | import { FooModule } from './foo/foo.module'; 8 | 9 | const routes: Routes = [ 10 | { 11 | path: '', 12 | component: FooComponent, 13 | }, 14 | ]; 15 | 16 | @NgModule({ 17 | declarations: [AppComponent], 18 | imports: [BrowserModule, FooModule, RouterModule.forRoot(routes)], 19 | providers: [], 20 | bootstrap: [AppComponent], 21 | }) 22 | export class AppModule {} 23 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /src/app/bar/components/bar/bar.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { BarComponent } from './bar.component'; 4 | 5 | describe('BarComponent', () => { 6 | let component: BarComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [BarComponent], 12 | }).compileComponents(); 13 | })); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(BarComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/app/foo/components/foo/foo.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { FooComponent } from './foo.component'; 4 | 5 | describe('FooComponent', () => { 6 | let component: FooComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [FooComponent], 12 | }).compileComponents(); 13 | })); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(FooComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "importHelpers": true, 6 | "outDir": "./dist/out-tsc", 7 | "sourceMap": true, 8 | "declaration": false, 9 | "module": "esnext", 10 | "moduleResolution": "node", 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "target": "es2015", 14 | "typeRoots": ["node_modules/@types"], 15 | "lib": ["es2017", "dom"], 16 | "paths": { 17 | "@herodevs/hero-loader": ["dist/loader"], 18 | "@herodevs/hero-loader/*": ["dist/loader/*"], 19 | "@herodevs/dynamic-component-service": ["dist/dynamic"], 20 | "@herodevs/dynamic-component-service/*": ["dist/dynamic/*"] 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/app/foo/components/foo/foo.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-foo', 5 | template: ` 6 |

7 | foo works! 8 |

9 |
10 | Hello 11 | 12 |
13 | `, 14 | styles: [], 15 | }) 16 | export class FooComponent implements OnInit { 17 | moduleName = 'src/app/bar/bar.module#BarModule'; 18 | load = false; 19 | 20 | constructor() {} 21 | 22 | ngOnInit() {} 23 | 24 | saveComponentRef(ref) { 25 | let num = 0; 26 | setInterval(() => { 27 | ref.next({ num: num++ }); 28 | }, 1000); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /projects/lazy/src/lib/hero-loader.directive.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { HeroLoaderDirective } from './hero-loader.directive'; 3 | 4 | describe('LazyComponent', () => { 5 | let component: HeroLoaderDirective; 6 | let fixture: ComponentFixture; 7 | 8 | beforeEach(async(() => { 9 | TestBed.configureTestingModule({ 10 | declarations: [HeroLoaderDirective], 11 | }).compileComponents(); 12 | })); 13 | 14 | beforeEach(() => { 15 | fixture = TestBed.createComponent(HeroLoaderDirective); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /projects/lazy/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'core-js/es7/reflect'; 4 | import 'zone.js/dist/zone'; 5 | import 'zone.js/dist/zone-testing'; 6 | import { getTestBed } from '@angular/core/testing'; 7 | import { 8 | BrowserDynamicTestingModule, 9 | platformBrowserDynamicTesting, 10 | } from '@angular/platform-browser-dynamic/testing'; 11 | 12 | declare const require: any; 13 | 14 | // First, initialize the Angular testing environment. 15 | getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting()); 16 | // Then we find all the tests. 17 | const context = require.context('./', true, /\.spec\.ts$/); 18 | // And load the modules. 19 | context.keys().map(context); 20 | -------------------------------------------------------------------------------- /projects/dynamic-service/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'core-js/es7/reflect'; 4 | import 'zone.js/dist/zone'; 5 | import 'zone.js/dist/zone-testing'; 6 | import { getTestBed } from '@angular/core/testing'; 7 | import { 8 | BrowserDynamicTestingModule, 9 | platformBrowserDynamicTesting, 10 | } from '@angular/platform-browser-dynamic/testing'; 11 | 12 | declare const require: any; 13 | 14 | // First, initialize the Angular testing environment. 15 | getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting()); 16 | // Then we find all the tests. 17 | const context = require.context('./', true, /\.spec\.ts$/); 18 | // And load the modules. 19 | context.keys().map(context); 20 | -------------------------------------------------------------------------------- /projects/lazy/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "target": "es2015", 6 | "module": "es2015", 7 | "moduleResolution": "node", 8 | "declaration": true, 9 | "sourceMap": true, 10 | "inlineSources": true, 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "importHelpers": true, 14 | "types": [], 15 | "lib": ["dom", "es2015"] 16 | }, 17 | "angularCompilerOptions": { 18 | "annotateForClosureCompiler": true, 19 | "skipTemplateCodegen": true, 20 | "strictMetadataEmit": true, 21 | "fullTemplateTypeCheck": true, 22 | "strictInjectionParameters": true, 23 | "enableResourceInlining": true 24 | }, 25 | "exclude": ["src/test.ts", "**/*.spec.ts"] 26 | } 27 | -------------------------------------------------------------------------------- /projects/dynamic-service/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "target": "es2015", 6 | "module": "es2015", 7 | "moduleResolution": "node", 8 | "declaration": true, 9 | "sourceMap": true, 10 | "inlineSources": true, 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "importHelpers": true, 14 | "types": [], 15 | "lib": ["dom", "es2015"] 16 | }, 17 | "angularCompilerOptions": { 18 | "annotateForClosureCompiler": true, 19 | "skipTemplateCodegen": true, 20 | "strictMetadataEmit": true, 21 | "fullTemplateTypeCheck": true, 22 | "strictInjectionParameters": true, 23 | "enableResourceInlining": true 24 | }, 25 | "exclude": ["src/test.ts", "**/*.spec.ts"] 26 | } 27 | -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './src/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: require('path').join(__dirname, './tsconfig.e2e.json') 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HeroDevs Packages 2 | 3 | This is a repo where HeroDevs develop and test our public packages. 4 | To see the source for each public npm package, head into the 5 | `projects` folder and see each project. 6 | 7 | #### Publishing a new package 8 | 9 | To add a new package to be published, do the following: 10 | 11 | 1. Use the CLI to add the new package. 12 | 2. Modify the `tsconfig.json` to account for the new package and it's mock-npm status in this repo. See the `path` section in `tsconfig.json`. 13 | 3. Add a build and package step into the `package.json` so that it can be built. 14 | 4. Add a way in the main app for package to be tested and used. 15 | 5. Build and package your project into the `dist` directory. 16 | 6. The first time you publish it, you will need to cd into the dist folder and run `npm publish --access public`. After the first time, you can simply run `npm publish` from the `dist/` folder. 17 | 18 | If you have any access errors when publishing, request to be added to the HeroDevs team on npm. 19 | -------------------------------------------------------------------------------- /src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | describe('AppComponent', () => { 4 | beforeEach(async(() => { 5 | TestBed.configureTestingModule({ 6 | declarations: [AppComponent], 7 | }).compileComponents(); 8 | })); 9 | it('should create the app', async(() => { 10 | const fixture = TestBed.createComponent(AppComponent); 11 | const app = fixture.debugElement.componentInstance; 12 | expect(app).toBeTruthy(); 13 | })); 14 | it(`should have as title 'hdpackages'`, async(() => { 15 | const fixture = TestBed.createComponent(AppComponent); 16 | const app = fixture.debugElement.componentInstance; 17 | expect(app.title).toEqual('hdpackages'); 18 | })); 19 | it('should render title in a h1 tag', async(() => { 20 | const fixture = TestBed.createComponent(AppComponent); 21 | fixture.detectChanges(); 22 | const compiled = fixture.debugElement.nativeElement; 23 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to hdpackages!'); 24 | })); 25 | }); 26 | -------------------------------------------------------------------------------- /projects/lazy/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | basePath: "", 7 | frameworks: ["jasmine", "@angular-devkit/build-angular"], 8 | plugins: [ 9 | require("karma-jasmine"), 10 | require("karma-chrome-launcher"), 11 | require("karma-jasmine-html-reporter"), 12 | require("karma-coverage-istanbul-reporter"), 13 | require("@angular-devkit/build-angular/plugins/karma") 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require("path").join(__dirname, "../../coverage"), 20 | reports: ["html", "lcovonly"], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ["progress", "kjhtml"], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ["Chrome"], 29 | singleRun: false 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /projects/dynamic-service/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | basePath: "", 7 | frameworks: ["jasmine", "@angular-devkit/build-angular"], 8 | plugins: [ 9 | require("karma-jasmine"), 10 | require("karma-chrome-launcher"), 11 | require("karma-jasmine-html-reporter"), 12 | require("karma-coverage-istanbul-reporter"), 13 | require("@angular-devkit/build-angular/plugins/karma") 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require("path").join(__dirname, "../../coverage"), 20 | reports: ["html", "lcovonly"], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ["progress", "kjhtml"], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ["Chrome"], 29 | singleRun: false 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |

Welcome to {{ title }}!

4 | Angular Logo 9 |
10 |

Here are some links to help you start:

11 | 37 | 38 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Web Animations `@angular/platform-browser/animations` 3 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 4 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 5 | **/ 6 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 7 | 8 | /** 9 | * By default, zone.js will patch all possible macroTask and DomEvents 10 | * user can disable parts of macroTask/DomEvents patch by setting following flags 11 | */ 12 | 13 | // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 14 | // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 15 | // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 16 | 17 | /* 18 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 19 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 20 | */ 21 | // (window as any).__Zone_enable_cross_context_check = true; 22 | 23 | /*************************************************************************************************** 24 | * Zone JS is required by default for Angular itself. 25 | */ 26 | import 'zone.js/dist/zone'; // Included with Angular CLI. 27 | 28 | /*************************************************************************************************** 29 | * APPLICATION IMPORTS 30 | */ 31 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": ["node_modules/codelyzer"], 3 | "rules": { 4 | "arrow-return-shorthand": true, 5 | "callable-types": true, 6 | "class-name": true, 7 | "deprecation": { 8 | "severity": "warn" 9 | }, 10 | "forin": true, 11 | "import-blacklist": [true, "rxjs/Rx"], 12 | "interface-over-type-literal": true, 13 | "label-position": true, 14 | "member-access": false, 15 | "member-ordering": [ 16 | true, 17 | { 18 | "order": ["static-field", "instance-field", "static-method", "instance-method"] 19 | } 20 | ], 21 | "no-arg": true, 22 | "no-bitwise": true, 23 | "no-console": [true, "debug", "info", "time", "timeEnd", "trace"], 24 | "no-construct": true, 25 | "no-debugger": true, 26 | "no-duplicate-super": true, 27 | "no-empty": false, 28 | "no-empty-interface": true, 29 | "no-eval": true, 30 | "no-inferrable-types": [true, "ignore-params"], 31 | "no-misused-new": true, 32 | "no-non-null-assertion": true, 33 | "no-redundant-jsdoc": true, 34 | "no-shadowed-variable": true, 35 | "no-string-literal": false, 36 | "no-string-throw": true, 37 | "no-switch-case-fall-through": true, 38 | "no-unnecessary-initializer": true, 39 | "no-unused-expression": true, 40 | "no-use-before-declare": true, 41 | "no-var-keyword": true, 42 | "object-literal-sort-keys": false, 43 | "prefer-const": true, 44 | "radix": true, 45 | "triple-equals": [true, "allow-null-check"], 46 | "unified-signatures": true, 47 | "variable-name": false, 48 | "whitespace": [true, "check-branch", "check-decl", "check-operator", "check-separator", "check-type"], 49 | "no-output-on-prefix": true, 50 | "no-inputs-metadata-property": true, 51 | "no-outputs-metadata-property": true, 52 | "no-host-metadata-property": true, 53 | "no-input-rename": true, 54 | "no-output-rename": true, 55 | "use-lifecycle-interface": true, 56 | "use-pipe-transform-interface": true, 57 | "component-class-suffix": true, 58 | "directive-class-suffix": true 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "herodevs-packages", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "precommit": "lint-staged", 7 | "start": "ng serve", 8 | "build": "ng build", 9 | "test": "ng test", 10 | "lint": "ng lint", 11 | "e2e": "ng e2e", 12 | "build-lazy": "ng build lazy", 13 | "build-dynamic": "ng build dynamicService", 14 | "npm-pack-lazy": "cd dist/loader && npm pack", 15 | "npm-pack-dynamic": "cd dist/dynamic && npm pack", 16 | "package-lazy": "npm run build-lazy && npm run npm-pack-lazy", 17 | "package-dynamic": "npm run build-dynamic && npm run npm-pack-dynamic", 18 | "package": "rm -rf dist/ && npm run package-dynamic && npm run package-lazy" 19 | }, 20 | "private": false, 21 | "dependencies": { 22 | "@angular/animations": "^8.0.0", 23 | "@angular/common": "^8.0.0", 24 | "@angular/compiler": "^8.0.0", 25 | "@angular/core": "^8.0.0", 26 | "@angular/forms": "^8.0.0", 27 | "@angular/platform-browser": "^8.0.0", 28 | "@angular/platform-browser-dynamic": "^8.0.0", 29 | "@angular/router": "^8.0.0", 30 | "core-js": "^2.5.4", 31 | "rxjs": "~6.5.2", 32 | "zone.js": "~0.9.1" 33 | }, 34 | "devDependencies": { 35 | "@angular-devkit/build-angular": "~0.800.0", 36 | "@angular-devkit/build-ng-packagr": "~0.800.0", 37 | "@angular/cli": "~8.0.2", 38 | "@angular/compiler-cli": "^8.0.0", 39 | "@angular/language-service": "^8.0.0", 40 | "@types/jasmine": "~2.8.8", 41 | "@types/jasminewd2": "~2.0.3", 42 | "@types/node": "~8.9.4", 43 | "codelyzer": "^5.0.1", 44 | "husky": "1.3.1", 45 | "jasmine-core": "~2.99.1", 46 | "jasmine-spec-reporter": "~4.2.1", 47 | "karma": "~3.0.0", 48 | "karma-chrome-launcher": "~2.2.0", 49 | "karma-coverage-istanbul-reporter": "~2.0.1", 50 | "karma-jasmine": "~1.1.2", 51 | "karma-jasmine-html-reporter": "^0.2.2", 52 | "lint-staged": "8.1.0", 53 | "ng-packagr": "^5.1.0", 54 | "prettier": "1.16.1", 55 | "protractor": "~5.4.0", 56 | "ts-node": "~7.0.0", 57 | "tsickle": "^0.35.0", 58 | "tslib": "^1.9.0", 59 | "tslint": "~5.11.0", 60 | "typescript": "~3.4.5" 61 | }, 62 | "lint-staged": { 63 | "*.{ts,tsx}": [ 64 | "prettier --parser typescript --writeprettier --parser typescript --write", 65 | "git add" 66 | ] 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /projects/lazy/src/lib/hero-loader.directive.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AfterViewInit, 3 | Directive, 4 | EventEmitter, 5 | Injector, 6 | Input, 7 | NgModuleFactory, 8 | NgModuleFactoryLoader, 9 | OnChanges, 10 | OnDestroy, 11 | Output, 12 | SimpleChanges, 13 | ViewContainerRef, 14 | } from '@angular/core'; 15 | import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs'; 16 | import { distinctUntilChanged, filter, first } from 'rxjs/operators'; 17 | import { 18 | DynamicComponentService, 19 | ICreatedModule, 20 | ICreatedComponentInterface, 21 | } from '@herodevs/dynamic-component-service'; 22 | 23 | // @ts-ignore 24 | @Directive({ 25 | selector: 'hero-loader, [hero-loader]', 26 | }) 27 | export class HeroLoaderDirective implements AfterViewInit, OnChanges, OnDestroy { 28 | // @ts-ignore 29 | @Input() moduleName: string; 30 | 31 | @Output() init = new EventEmitter(); 32 | 33 | componentRef: ICreatedComponentInterface; 34 | 35 | changesBS = new BehaviorSubject(null); 36 | 37 | // Need to wait until the component view has inited 38 | afterInitBS = new BehaviorSubject(false); 39 | 40 | /** 41 | * This observable fires once the component has been init'd 42 | * and once the changes come through 43 | * and once the changes that has an input value that is a function 44 | * 45 | * It only fires once. If the input changes, this observable 46 | * will not fire again. 47 | */ 48 | // @ts-ignore 49 | action$ = combineLatest( 50 | this.changesBS.asObservable().pipe( 51 | filter((val: SimpleChanges) => { 52 | return val && val.moduleName && val.moduleName.currentValue; 53 | }), 54 | first(), 55 | ), 56 | this.afterInitBS.asObservable().pipe( 57 | filter((init) => init), 58 | distinctUntilChanged(), 59 | ), 60 | ); 61 | 62 | subs: Subscription[] = [ 63 | this.action$.subscribe(() => { 64 | // Uses the loader function to lazy load and compile a module. 65 | this.loader 66 | .load(this.moduleName) 67 | .then((compiledModule: NgModuleFactory) => { 68 | if (this.destroyed) return {}; 69 | return this.lazy.createAndAttachModuleAsync(compiledModule, this.injector, { vcr: this.vcr }); 70 | }) 71 | .then(({ moduleRef, componentRef }: ICreatedModule) => { 72 | this.componentRef = componentRef; 73 | this.init.emit(componentRef); 74 | }); 75 | }), 76 | ]; 77 | 78 | destroyed = false; 79 | 80 | constructor( 81 | private lazy: DynamicComponentService, 82 | private vcr: ViewContainerRef, 83 | private injector: Injector, 84 | private loader: NgModuleFactoryLoader, 85 | ) {} 86 | 87 | ngAfterViewInit() { 88 | this.afterInitBS.next(true); 89 | } 90 | 91 | ngOnChanges(changes: SimpleChanges): void { 92 | this.changesBS.next(changes); 93 | } 94 | 95 | ngOnDestroy(): void { 96 | this.destroyed = true; 97 | this.subs.forEach((s) => s.unsubscribe()); 98 | 99 | // If the component has init'd, destroy it. 100 | if (this.componentRef) { 101 | this.componentRef.detach(); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /projects/dynamic-service/README.MD: -------------------------------------------------------------------------------- 1 | # DynamicComponentService 2 | 3 | A service that makes dynamically creating components easy. 4 | 5 | When you use DynamicComponentService to create a component, you are given 6 | backed an `ICreatedComponentInterface` which allows you to do the 7 | following three things: 8 | 9 | - `.next()` - Push new data into the component's Inputs, as well 10 | as provide callbacks for the Outputs. This is how you pass new 11 | down into the created component. 12 | - `.detach()` - Allows you to detach the component from the DOM 13 | as well as destroys the component. This is how you destroy the 14 | created component. 15 | - `.componentRef` - This is a pointer to the created component. 16 | This is of type `ComponentRef`. In other words, this is the 17 | instance of the component that is returned when you call `new` 18 | on the class definition. The `.componentRef` is the `this` of 19 | the component. 20 | 21 | ### How to use it 22 | 23 | #### Installation 24 | 25 | Start by installing it correctly: 26 | 27 | ```bash 28 | npm install @herodevs/dynamic-af 29 | ``` 30 | 31 | #### Inject the Service 32 | 33 | Now you need to inject the service into your component, or into 34 | another service of your own. You do that by adding it to the 35 | constructor of your component/service, like so: 36 | 37 | ```typescript 38 | export class MyCoolComponent { 39 | constructor(private dynamicService: DynamicComponentService) { 40 | // ... 41 | } 42 | } 43 | ``` 44 | 45 | #### Call `createAndAttachComponentSync` 46 | 47 | Now that you have the service, you can call the `createAndAttachComponentSync` 48 | method to create a component and have it attached to the DOM. 49 | Here is an example of what that looks like: 50 | 51 | ```typescript 52 | const ref = dynamicService.createAndAttachComponentSync(FooComponent, { vcr: this.viewContainerRef }); 53 | ``` 54 | 55 | You must pass the `createAndAttachComponentSync` method two 56 | parameters. First, you need to pass the class of the component 57 | that you want to dynamically create. The second is an 58 | object that matches the `CreateComponentOptions` interface: 59 | 60 | ```typescript 61 | interface CreateComponentOptions { 62 | module?: NgModuleRef; 63 | context?: { [key: string]: any }; 64 | vcr?: ViewContainerRef; 65 | } 66 | ``` 67 | 68 | Here are what each of those represents: 69 | 70 | - `vcr (optional, but not really)` - This is the `ViewContainerRef` 71 | where you want to attach the createdComponent. If you don't 72 | provide a `vcr`, the service will have no choice but to attach 73 | your component to the bottom of the `document.body`. So it 74 | is recommended that you DEFINITELY provide a `vcr`. 75 | - `context (optional)` - This is an object that has keys 76 | that match the names of the Inputs/Outputs of the component 77 | being created. If your component being created has an 78 | input named `name`, then you can pass a `context` with 79 | a `name` property to provide a name. Eg: `{name: 'Your Name'}`. 80 | This will pass the value `Your Name` into the Input 81 | of you component. 82 | - `module (optional)` - This is a reference to the module 83 | that the component belongs to. You only need to pass this 84 | if you manually lazily loaded the component and module. 85 | Otherwise you can not pass this. 86 | 87 | #### Updating input/output values 88 | 89 | Once you have the `ref` to your created component, you can 90 | call `next(newContext)` to pass in new values to your 91 | inputs/outputs of your component. Here is an example of 92 | updating an input value one second for a component 93 | that has `@Input() count`: 94 | 95 | ```typescript 96 | const ref = dynamicService.createAndAttachComponentSync(FooComponent, { vcr: this.viewContainerRef }); 97 | 98 | let count = 0; 99 | ref.next({ count: count++ }); 100 | 101 | setInterval(() => { 102 | ref.next({ count: count++ }); 103 | }, 1000); 104 | ``` 105 | 106 | Once a second the created component will get a new `count` 107 | via it's input. 108 | -------------------------------------------------------------------------------- /projects/lazy/README.MD: -------------------------------------------------------------------------------- 1 | # `` for lazy loading in Angular 2 | 3 | Every Angular app is different and has different needs. Yet Angular only provides 4 | one method for lazily loading code: using the `loadChildren` piece in the routes 5 | for any given module. By using the `loadChildren` piece of a route, you are telling 6 | Angular and the Angular CLI to help you out and lazily load that piece of the app 7 | when the associated route is hit. This is a very efficient tool that we should 8 | all be using. 9 | 10 | #### HOWEVER!!! 11 | 12 | Some of us need more flexibility when lazily loading modules. Some modules need 13 | to be triggered to load on events BESIDES route change. Maybe a `click` or a 14 | `mouseover`. Maybe when the user has admin rights, or when they don't have 15 | admin rights. This is why we built ``. Using this component, combined 16 | with an `ngIf`, you can trigger lazy loading of a module for just about any 17 | scenario that you can think of. 18 | 19 | #### How does it work? 20 | 21 | To do this, we utilize the exact same pieces of Angular that `loadChildren` from 22 | routes uses. But we do it in a different way. Let's look at how it works. 23 | 24 | ## Getting Started 25 | 26 | Start by installing the right node module: 27 | 28 | ```bash 29 | npm install @herodevs/hero-loader 30 | ``` 31 | 32 | At this point, we have all that we need to get started. We only need to do some 33 | configuring. We need to do the following: 34 | 35 | 1. Tell Angular to create a separate bundle for the module that we intend to 36 | lazy load. 37 | 2. Import `HeroLoaderModule` where we intend to use this lazy loading. 38 | 3. Tell `` to load that bundle when needed. 39 | 40 | Let's do this one at a time. 41 | 42 | ### Create a separate bundle for our module 43 | 44 | Open your `angular.json` file. In that file, look for the nested property 45 | `projects..architect.build.options` where `` 46 | is the name of your project. Once you have the build options property in 47 | sight, add the `lazyModules` property to the options: 48 | 49 | ```json 50 | "options": { 51 | ... 52 | "lazyModules": [ "src/app/test/test.module" ] 53 | } 54 | ``` 55 | 56 | In the above example, you are telling the Angular CLI to prepare a separate 57 | bundle for `TestModule` in the file `src/app/test/test.module.ts`. You will 58 | notice that this looks a lot like the `loadChildren` syntax for a route. 59 | That's because this `lazyModules` property is doing the same thing that 60 | the `loadChildren` property does in a route. Now the Angular CLI knows to 61 | create a separate bundle for the `TestModule`. 62 | 63 | ### Import `HeroLoaderModule` 64 | 65 | In your app, you need to add `HeroLoaderModule` to the imports of one of your 66 | app's NgModules 67 | 68 | ```typescript 69 | @NgModule({ 70 | imports: [HeroLoaderModule], 71 | }) 72 | export class AppModule {} 73 | ``` 74 | 75 | Now your app knows about the `HeroLoaderModule` and you can use the `` 76 | component to lazy load the `TestModule`. 77 | 78 | ### Use `` in our app 79 | 80 | The following is an example of how to use `` to load our `TestModule`. 81 | 82 | ```html 83 |
Hover to load TestModule
84 | 85 | ``` 86 | 87 | When you hover the `
` above, the `ngIf` will turn on the `` component 88 | which will then load the `TestModule` and it will use whatever component is 89 | listed in the `TestModule.bootstrap` property and attach that component to the 90 | inside of the `` component. 91 | 92 | Consider that `TestModule` looks as follows: 93 | 94 | ```typescript 95 | @NgModule({ 96 | declarations: [TestComponent], 97 | bootstrap: [TestComponent], 98 | }) 99 | export class TestModule {} 100 | ``` 101 | 102 | Using `` to load the `TestModule` will the `TestComponent` inside of the 103 | the `` component that you added to your template. 104 | 105 | ## Question 106 | 107 | - [Report an Issue](https://github.com/herodevs/herodevs-packages/issues) 108 | - [View Past Issues](https://github.com/herodevs/herodevs-packages/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aclosed+) 109 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "main": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": {}, 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/main", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "src/tsconfig.app.json", 21 | "assets": ["src/favicon.ico", "src/assets"], 22 | "styles": ["src/styles.css"], 23 | "scripts": [], 24 | "lazyModules": ["src/app/bar/bar.module"] 25 | }, 26 | "configurations": { 27 | "production": { 28 | "fileReplacements": [ 29 | { 30 | "replace": "src/environments/environment.ts", 31 | "with": "src/environments/environment.prod.ts" 32 | } 33 | ], 34 | "optimization": true, 35 | "outputHashing": "all", 36 | "sourceMap": false, 37 | "extractCss": true, 38 | "namedChunks": false, 39 | "aot": true, 40 | "extractLicenses": true, 41 | "vendorChunk": false, 42 | "buildOptimizer": true 43 | } 44 | } 45 | }, 46 | "serve": { 47 | "builder": "@angular-devkit/build-angular:dev-server", 48 | "options": { 49 | "browserTarget": "main:build" 50 | }, 51 | "configurations": { 52 | "production": { 53 | "browserTarget": "main:build:production" 54 | } 55 | } 56 | }, 57 | "extract-i18n": { 58 | "builder": "@angular-devkit/build-angular:extract-i18n", 59 | "options": { 60 | "browserTarget": "main:build" 61 | } 62 | }, 63 | "test": { 64 | "builder": "@angular-devkit/build-angular:karma", 65 | "options": { 66 | "main": "src/test.ts", 67 | "polyfills": "src/polyfills.ts", 68 | "tsConfig": "src/tsconfig.spec.json", 69 | "karmaConfig": "src/karma.conf.js", 70 | "styles": ["src/styles.css"], 71 | "scripts": [], 72 | "assets": ["src/favicon.ico", "src/assets"] 73 | } 74 | }, 75 | "lint": { 76 | "builder": "@angular-devkit/build-angular:tslint", 77 | "options": { 78 | "tsConfig": ["src/tsconfig.app.json", "src/tsconfig.spec.json"], 79 | "exclude": ["**/node_modules/**"] 80 | } 81 | } 82 | } 83 | }, 84 | "main-e2e": { 85 | "root": "e2e/", 86 | "projectType": "application", 87 | "architect": { 88 | "e2e": { 89 | "builder": "@angular-devkit/build-angular:protractor", 90 | "options": { 91 | "protractorConfig": "e2e/protractor.conf.js", 92 | "devServerTarget": "main:serve" 93 | }, 94 | "configurations": { 95 | "production": { 96 | "devServerTarget": "main:serve:production" 97 | } 98 | } 99 | }, 100 | "lint": { 101 | "builder": "@angular-devkit/build-angular:tslint", 102 | "options": { 103 | "tsConfig": "e2e/tsconfig.e2e.json", 104 | "exclude": ["**/node_modules/**"] 105 | } 106 | } 107 | } 108 | }, 109 | "lazy": { 110 | "root": "projects/lazy", 111 | "sourceRoot": "projects/lazy/src", 112 | "projectType": "library", 113 | "prefix": "hero", 114 | "architect": { 115 | "build": { 116 | "builder": "@angular-devkit/build-ng-packagr:build", 117 | "options": { 118 | "tsConfig": "projects/lazy/tsconfig.lib.json", 119 | "project": "projects/lazy/ng-package.json" 120 | } 121 | }, 122 | "test": { 123 | "builder": "@angular-devkit/build-angular:karma", 124 | "options": { 125 | "main": "projects/lazy/src/test.ts", 126 | "tsConfig": "projects/lazy/tsconfig.spec.json", 127 | "karmaConfig": "projects/lazy/karma.conf.js" 128 | } 129 | }, 130 | "lint": { 131 | "builder": "@angular-devkit/build-angular:tslint", 132 | "options": { 133 | "tsConfig": ["projects/lazy/tsconfig.lib.json", "projects/lazy/tsconfig.spec.json"], 134 | "exclude": ["**/node_modules/**"] 135 | } 136 | } 137 | } 138 | }, 139 | "dynamicService": { 140 | "root": "projects/dynamic-service", 141 | "sourceRoot": "projects/dynamic-service/src", 142 | "projectType": "library", 143 | "prefix": "lib", 144 | "architect": { 145 | "build": { 146 | "builder": "@angular-devkit/build-ng-packagr:build", 147 | "options": { 148 | "tsConfig": "projects/dynamic-service/tsconfig.lib.json", 149 | "project": "projects/dynamic-service/ng-package.json" 150 | } 151 | }, 152 | "test": { 153 | "builder": "@angular-devkit/build-angular:karma", 154 | "options": { 155 | "main": "projects/dynamic-service/src/test.ts", 156 | "tsConfig": "projects/dynamic-service/tsconfig.spec.json", 157 | "karmaConfig": "projects/dynamic-service/karma.conf.js" 158 | } 159 | }, 160 | "lint": { 161 | "builder": "@angular-devkit/build-angular:tslint", 162 | "options": { 163 | "tsConfig": [ 164 | "projects/dynamic-service/tsconfig.lib.json", 165 | "projects/dynamic-service/tsconfig.spec.json" 166 | ], 167 | "exclude": ["**/node_modules/**"] 168 | } 169 | } 170 | } 171 | } 172 | }, 173 | "defaultProject": "lazyloading" 174 | } 175 | -------------------------------------------------------------------------------- /projects/dynamic-service/src/lib/dynamic-component.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApplicationRef, 3 | Compiler, 4 | ComponentFactory, 5 | ComponentFactoryResolver, 6 | ComponentRef, 7 | Injectable, 8 | Injector, 9 | NgModuleFactory, 10 | NgModuleRef, 11 | Type, 12 | ViewContainerRef, 13 | } from '@angular/core'; 14 | import { BehaviorSubject, Subscription, Observable } from 'rxjs'; 15 | 16 | export interface InternalNgModuleRef extends NgModuleRef { 17 | _bootstrapComponents: Type[]; 18 | } 19 | 20 | export interface ICreatedComponentInterface { 21 | next: (data: { [key: string]: any }) => void; 22 | detach: () => void; 23 | componentRef: ComponentRef; 24 | } 25 | 26 | export interface ICreatedModule { 27 | moduleRef: NgModuleRef; 28 | componentRef?: ICreatedComponentInterface; 29 | } 30 | 31 | interface CreateComponentOptions { 32 | module?: NgModuleRef; 33 | context?: { [key: string]: any }; 34 | vcr?: ViewContainerRef; 35 | } 36 | 37 | interface CreateAttachModuleOptions { 38 | context?: { [key: string]: any }; 39 | vcr?: ViewContainerRef; 40 | } 41 | 42 | @Injectable({ 43 | providedIn: 'root', 44 | }) 45 | export class DynamicComponentService { 46 | constructor( 47 | private _compiler: Compiler, 48 | private cfr: ComponentFactoryResolver, 49 | private appRef: ApplicationRef, 50 | private injector: Injector, 51 | ) {} 52 | 53 | /** 54 | * 55 | * @param compiledModule - This is a module that is compiled by the JIT or AOT compiler 56 | * @param injector - An injector that the 57 | */ 58 | createModuleSync(compiledModule: NgModuleFactory, injector: Injector): ICreatedModule { 59 | // Now that the module is loaded and compiled, create an instance of it. 60 | const moduleRef = compiledModule.create(injector) as NgModuleRef; 61 | 62 | return { 63 | moduleRef, 64 | }; 65 | } 66 | 67 | createModuleAsync(compiledModule: NgModuleFactory, injector: Injector): Promise { 68 | return new Promise((res, rej) => { 69 | try { 70 | res(this.createModuleSync(compiledModule, injector)); 71 | } catch { 72 | rej(); 73 | } 74 | }); 75 | } 76 | 77 | createAndAttachModuleSync( 78 | compiledModule: NgModuleFactory, 79 | injector: Injector, 80 | { vcr, context = {} }: CreateAttachModuleOptions = {}, 81 | ): ICreatedModule { 82 | // Create an instance of the module from the moduleFactory 83 | const createdModule = this.createModuleSync(compiledModule, injector); 84 | 85 | // Take the bootstrap component from that module. 86 | // Using any, as in AngularV8 the InternalNgModuleRef no longer gets exported. 87 | const type = (createdModule.moduleRef as any)._bootstrapComponents[0]; 88 | 89 | // The first time they try this and screw up, they will get this warning. This won't happen in prod. 90 | if (!type) { 91 | warn(`Module '${typeof createdModule.moduleRef}' has no bootstrap component. 92 | You must fix this before calling 'dynamicComponentService.createAndAttachModule'.`); 93 | } 94 | 95 | const createdComponent = this.createAndAttachComponentSync(type, { 96 | context, 97 | module: createdModule.moduleRef, 98 | vcr, 99 | }); 100 | 101 | return { 102 | moduleRef: createdModule.moduleRef, 103 | componentRef: createdComponent, 104 | }; 105 | } 106 | 107 | createAndAttachModuleAsync( 108 | compiledModule: NgModuleFactory, 109 | injector: Injector, 110 | { vcr, context = {} }: CreateAttachModuleOptions = {}, 111 | ): Promise { 112 | return new Promise((res, rej) => { 113 | try { 114 | res(this.createAndAttachModuleSync(compiledModule, injector, { vcr, context })); 115 | } catch { 116 | rej('Error created and attaching module async.'); 117 | } 118 | }); 119 | } 120 | 121 | private getComponentFactory(type: any, module?: NgModuleRef): ComponentFactory { 122 | if (module) { 123 | return module.componentFactoryResolver.resolveComponentFactory(type); 124 | } else { 125 | return this.cfr.resolveComponentFactory(type); 126 | } 127 | } 128 | 129 | /** 130 | * 131 | * 132 | @param type - A type of a component that we want to create. 133 | * @param injector - The injector from the parent container component. 134 | * @param vcr - The view container ref from the calling component. 135 | * @param context - An object that has properties that match the Input and Output names from the 136 | * component that is being created. 137 | * @param module - For components that were not lazily loaded, the type existed a build and thus, the 138 | * ViewContainerRef will have access to it's factory. But for those that were 139 | * lazily loaded, we will need to get their factory from the module that they are 140 | * declared in (which module was also lazily loaded and compiled, during which 141 | * process it received access to the components factory). 142 | */ 143 | createAndAttachComponentSync( 144 | type: any, 145 | { context, module, vcr }: CreateComponentOptions = {}, 146 | ): ICreatedComponentInterface { 147 | // Use the module to get the component factory, so that we can create an instance of the component. 148 | const factory = this.getComponentFactory(type, module); 149 | 150 | // Create an instance of the component, and add it to the DOM 151 | let componentRef; 152 | if (vcr) { 153 | // This call to createComponent will create and attach the instance to the vcr (the view element). 154 | componentRef = vcr.createComponent(factory); 155 | } else { 156 | // Manually create an instance of the component 157 | componentRef = factory.create(this.injector); 158 | 159 | // Attach it to the app 160 | this.appRef.attachView(componentRef.hostView); 161 | 162 | // Attach the instance to the end of the body 163 | document.body.appendChild((componentRef.hostView as any).rootNodes[0]); 164 | warn(`Since no 'ViewContainerRef' was provided to 'DynamicComponentService.createAndAttachComponent', 165 | the component is being attached to the root of the . This is not recommended.`); 166 | } 167 | 168 | // Take the context and search for keys that match the names of the outputs 169 | // on the component. Track the 170 | const subscriptions = this._wireOutputs(factory, componentRef, context); 171 | 172 | // Place the incoming context into a stream. This stream will be returned to the caller, 173 | // and the caller can send in a new context at will by calling `.next(newContact)` on 174 | // this BehaviorSubject. 175 | const context$ = new BehaviorSubject(context); 176 | 177 | // Subscribe to the new observable for updated input values 178 | subscriptions.push( 179 | context$.subscribe((_context) => { 180 | // When a new values comes through this stream, match up the key names to the input/output 181 | // names on the component and update those values on the component. 182 | factory.inputs.forEach((i) => { 183 | if (_context[i.propName] !== undefined) { 184 | componentRef.instance[i.propName] = _context[i.propName]; 185 | } 186 | }); 187 | }), 188 | ); 189 | 190 | // This function will be returned to the caller, to be called when their context is destroyed 191 | const detach = () => { 192 | // We only need to manually detach if we didn't get a vcr 193 | if (!vcr) { 194 | this.appRef.detachView(componentRef.hostView); 195 | } 196 | 197 | // Destroy our instance of the component 198 | componentRef.destroy(); 199 | 200 | // Go through each subscription from the context and unsubscribe 201 | subscriptions.map((s: Subscription) => { 202 | if (!s.closed) { 203 | s.unsubscribe(); 204 | } 205 | }); 206 | }; 207 | 208 | // This function will be returned to the caller, to be called when there are new values for the inputs 209 | const next = (data: any) => { 210 | context$.next(data); 211 | }; 212 | 213 | return { 214 | detach, 215 | next, 216 | componentRef, 217 | }; 218 | } 219 | 220 | /** 221 | * Same as `createComponent` function, but wraps call in a promise 222 | */ 223 | createAndAttachComponentAsync( 224 | type: any, 225 | { context, module, vcr }: CreateComponentOptions = {}, 226 | ): Promise { 227 | return new Promise((res, rej) => { 228 | try { 229 | res(this.createAndAttachComponentSync(type, { context, module, vcr })); 230 | } catch { 231 | rej('Error creating component async'); 232 | } 233 | }); 234 | } 235 | 236 | // Internal function to add event emitters for each of the provided outputs 237 | private _wireOutputs( 238 | factory: ComponentFactory, 239 | componentRef: any, 240 | context: { [key: string]: any }, 241 | ): Array { 242 | const subscriptions: Subscription[] = []; 243 | factory.outputs.forEach((o) => { 244 | if (context[o.propName] && context[o.propName] instanceof Function) { 245 | subscriptions.push(componentRef.instance[o.propName].subscribe(context[o.propName])); 246 | } 247 | }); 248 | return subscriptions; 249 | } 250 | } 251 | 252 | function checkNotEmpty(value: any, exportName: string): any { 253 | if (!value) { 254 | throw new Error(`Cannot find '${exportName}'`); 255 | } 256 | return value; 257 | } 258 | 259 | function warn(msg) { 260 | console.warn(msg.replace(/\s{2,}/g, ' ')); 261 | } 262 | --------------------------------------------------------------------------------