Presentation starts in:

Days

Hours

Minutes

Seconds

Interesting Tidbit

How Diablo was completely Reverse Engineered without Source Code

What is this presentation about?

  • Customizable User Interfaces
  • using Dynamic Components
  • in Angular

John Harvey

John Harvey

Software development consultant at Sparkhound in Baton Rouge, LA.

Received M.S. in Computer Engineering from LSU.

Interested in all things Angular, .NET, and maybe Rust.

github
linkedin
blog

True Fact

Content Management Systems Rule!

Benefits

Easy to modify content

Distributed ownership

Reuseable across projects

Another True Fact

Content Management Systems Are Bad, and they should feel bad!

Problems

Inefficient developer experience.

Bad UI/UX!
(editing is slow or buggy, content gets jumbled on save, styles overwrite the editor, etc...)

Monolithic (more features than needed)

Limited backend choices (PHP)

Do not support new trends well like SPA Frameworks, Mobile, etc.

Existing CMSs

Wordpress

Drupal

Kentico

Sitefinity

Wix

Squarespace

...and many more!

Headless CMS to the rescue?

Much better content management ...

... but they gave up on the UI features!

Modular Productivity Software

AirTable, Coda, Notion, etc...

https://www.slashgear.com/conquer-2019-with-these-new-breed-of-productivity-tools-01559572/

We need a new CMS!

What do we need?

Components!

Dynamic Creation

Visual Layout/Content Editing

Bonus: Lazy Loading and Plugins

Elemental

https://github.com/gradientvector

Components

What is a component?

How do we create them?

How do we share them?

So what is a component exactly?

Legos for programmers

Also called Blocks, Elements, or Widgets for old folks

Features

Inputs

Outputs

Services (Dependency Injection)

Content Projection

Example

Example

Date Picker

https://valor-software.com/ngx-bootstrap/#/datepicker#inline-datepicker

Inputs

<input type="text" value="initial value">

Outputs

<button onclick="console.log(event);">Click Me</button>

Services (Dependency Injection)

@Component({ selector: 'container', templateUrl: './container.component.html', styleUrls: ['./container.component.scss'], }) export class ContainerComponent { constructor(private sanitizer: DomSanitizer) { } }

Content Projection

<div> <button>Click Me</button> </div>

Content Projection in Angular

<my-custom-component> <h1>Title</h1> <button>Click Me</button> </my-custom-component>

Demo

Using Angular Components in a Template

Easy Component Creation

What is a component?

How do we create them?

How do we share them?

Creating an Angular Component

@Component({ selector: 'container', template: ` <p>{{someInput}}</p> <button (click)="someInputChange($event)">Click Me</button> <ng-content select="slot-name"></ng-content> `, styleUrls: ['./container.component.scss'], }) export class ContainerComponent { constructor(private sanitizer: DomSanitizer) { } @Input() someInput: string; @Output() someInputChange = new EventEmitter<string>(); }

Easy Component Creation

What is a component?

How do we create them?

How do we share them?

Sharing is Caring

Angular CLI makes it easy to create a Library!

> ng generate library my-lib

Official Way to Build Libraries

> ng build my-lib --watch > ng serve

Works, but I ran into occasional file lock issues...

Pro Tip!

Configure your tsconfig.json file to reference the library code directly!

{ "compilerOptions": { "paths": { "elemental": [ "projects/elemental/src/public-api" ], "elemental/*": [ "projects/elemental/src/public-api/*" ], // .... } } // .... }

Component Libraries

ngx-bootstrap

md-bootstrap

Angular Material

Web Components

Framework Independent (works with Angular, React, Vue, plain JavaScript, etc.)

Angular Elements

Components Components Components

What is a component?

How do we create them?

How do we share them?

What do we need?

Components!

Dynamic Creation

Visual Layout/Content Editing

Bonus: Lazy Loading and Plugins

How do we create dynamic components in Angular?

Component Factories

<ng-container #host></ng-container> export class SomeComponent { constructor(private componentFactoryResolver: ComponentFactoryResolver) { } @ViewChild('host', {static: true, read: ViewContainerRef }) host: ViewContainerRef; loadComponent() { const componentFactory = this.componentFactoryResolver.resolveComponentFactory(MyComponent); const viewContainerRef = this.host.viewContainerRef; const componentRef = viewContainerRef.createComponent(componentFactory); // You can set Input/Output properties! (<MyComponent>componentRef.instance).property = property; } }

Entry Components

entryComponents: [ HeroJobAdComponent, HeroProfileComponent ],

Creating a Schema to describe components

schema: ElementalBootstrapSchema = { type: "container", props: { fluid: true } };

Mapping String to Component Constructor

export let elementalTypes = { "container": ContainerComponent, "row": RowComponent, "input": InputComponent, "input-text": InputTextComponent, "input-checkbox": InputCheckboxComponent, "input-file": InputFileComponent, };

Make a Component to Handle Dynamic Creation

<elemental [schema]="schema"></elemental>

Nesting Schemas

schema: ElementalBootstrapSchema = { type: "container", props: { fluid: true }, children: [ { type: "row", props: {}, children: [ { type: "input", props: {} }, ] } ] };

TypeScript assistance when typing schema by hand?

export type ElementalBootstrapSchema = { type: "container", props: ContainerComponentData, children?: ElementalBootstrapSchema[], } | { type: "row", props: RowComponentData, children?: ElementalBootstrapSchema[], } | { type: "input-text", props: InputTextComponentData, } | { type: "input-checkbox", props: InputCheckboxComponentData, };

Use Cases

CMS

Dashboards

Lazy Loading

Code Generation

Demo

Create a schema by hand.

What about forms?

Template-driven vs Reactive

Custom Controls? Nested? Projected?

Collecting results into a model

ngx-formly

<formly-form [model]="model" [fields]="fields" [options]="options" [form]="form"> </formly-form>
fields: FormlyFieldConfig[] = [ { key: 'text', type: 'input', templateOptions: { label: 'Text', placeholder: 'Formly is terrific!', required: true, }, }, ];

Control Value Accessor

Interface for custom controls

interface ControlValueAccessor { writeValue(obj: any): void registerOnChange(fn: any): void registerOnTouched(fn: any): void setDisabledState(isDisabled: boolean)?: void }

Angular Form Patterns

Explains how to handle validation as well!

Using ngModel

Allows us to use the dynamic componet just like any other control

<elemental [schema]="schema" [(ngModel)]="model"></elemental>

What do we need?

Components!

Dynamic Creation

Visual Layout/Content Editing

Bonus: Lazy Loading and Plugins

Visual Layout/Content Editor

Many users would like to move things around visually!

End-users

Designers

Grandma

... and even developers!

Example

Wordpress (Gutenberg, Bakery, etc...)

Visual Layout/Content Editor

Dragon-drop?

Demo

Angular CDK drag-and-drop

https://material.angular.io/cdk/drag-drop/overview

What do we need?

Components!

Dynamic Creation

Visual Layout/Content Editing

Bonus: Lazy Loading and Plugins

Lazy Loading and Plugins

https://blog.angularindepth.com/building-extensible-dynamic-pluggable-enterprise-application-with-angular

ngx-build-plus

Webpack Externals

package.json

> ng build --project elemental-bootstrap-bundle --prod --output-hashing none --single-bundle true --extraWebpackConfig webpack.extra.js

webpack.extra.js

module.exports = { output: { library: 'elemental-bundle', libraryTarget: 'umd' }, "externals": { "rxjs": "rxjs", "@angular/core": "ng.core", "@angular/common": "ng.common", "@angular/platform-browser": "ng.platformBrowser", "@angular/platform-browser-dynamic": "ng.platformBrowserDynamic", "@angular/elements": "ng.elements" } }

main.ts

export * from '../../elemental-bootstrap/src/lib/elemental-bootstrap.module'; // NOTE: // Dont worry if Angular Language Service shows errors when importing/exporting ngfactory. // It should still compile correctly. export * from '../../elemental-bootstrap/src/lib/elemental-bootstrap.module.ngfactory'; import { ElementalBootstrapModuleNgFactory } from '../../elemental-bootstrap/src/lib/elemental-bootstrap.module.ngfactory'; export default ElementalBootstrapModuleNgFactory;

Gather the Exports

let exports = {}; let module = {exports: exports}; let modules = { 'ng.core': core, 'ng.common': common, 'ng.cdk': cdk, 'ng.platformBrowser': platformBrowser, 'ng.platformBrowserDynamic': platformBrowserDynamic, 'ngxLogger': ngxLoader, "rxjs": rxjs, };

Load the Plugin and "Run" It

let url = `http://localhost:4200/${dynamicModule.url}`; let response = await this.http.get(url, { responseType: "text" }).toPromise(); // shim 'require' and eval let require: any = (module) => modules[module]; let func = Function("exports, module, require", response); let result = func(exports, module, require);

Instantiate the new Module and Component

let plugin = exports['ElementalBootstrapModuleFactory']; let module = plugin.ngModuleFactory.create(this.injector); let component = plugin.componentFactories[0].create(this.injector, null, null, module);

What do we need?

Components!

Dynamic Creation

Visual Layout/Content Editing

Bonus: Lazy Loading and Plugins

Recap

Components Rule

They can be Dynamic

Make your own CMS

Discover the elements! 🌎🌀🌊🔥❤️

Your feedback is valuable!

Session Evaluations are ONLINE ONLY

Created by John Harvey