DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
The Latest "Software Integration: The Intersection of APIs, Microservices, and Cloud-Based Systems" Trend Report
Get the report
  1. DZone
  2. Coding
  3. Frameworks
  4. Using Angular 2 Components in a Non-Angular App

Using Angular 2 Components in a Non-Angular App

Follow along as we make a simple ToDo list application using Angular 2 components to bootstrap a non-Angular application.

Shridharan Chandramouli user avatar by
Shridharan Chandramouli
·
Jun. 08, 17 · Tutorial
Like (10)
Save
Tweet
Share
17.16K Views

Join the DZone community and get the full member experience.

Join For Free

Lucidpress is a large application—hundreds of thousands of lines of handwritten JavaScript. After seeing the success Lucidchart had in modernizing its UI with Angular, we wanted to follow suit, but we didn’t have the resources to do a wholesale rewrite all at once. We wanted to write some new Angular 2 components and even reuse several big components from Lucidchart, but we needed to be able to fire up individual components scattered around the application, without having one big Angular app controlling them all.

This use case isn’t one that’s well-documented by the Angular team, but it turns out that it can be done cleanly and without a whole lot of boilerplate code for bootstrapping each component. In this blog post, I’ll walk you through the process of setting up a few Angular 2 components in a simple non-Angular JavaScript application.

(Note: Checkout this post if you are looking for ways to load a component dynamically from within Angular)

An Example App

Say we have a simple to-do app that maintains a list of things to do. It lets you add items to the list and lets you mark them as done. An Angular implementation in Typescript might look something like this:

@Component({
  selector: 'check-list',
  template: `
    <div>
      <h2>My Checklist in angular 2</h2>
      <input type="text" #itemInput/>
      <button (click)="addToList(itemInput.value)"> add new item </button>
      <div class="flex-container" *ngFor="let item of items">
        <check-list-item [value]="item"> </check-list-item>
      </div>
    </div>
  `,
  styles:[`
    flex-container { display:flex; flex-direction: column }
  `]
})
export class CheckList {
  items: string[] = [];
  constructor() {}
  addToList(item: string) {
    this.items.push(item);
  }
}

@Component({
  selector: 'check-list-item',
  template: `
    <input type="checkbox"/>
    <label>{{value}}</label>
  `,
  styles:[`
    input[type=checkbox]:checked + label {text-decoration: line-through;}
  `]
})
export class CheckListItem {
  @Input() value: string = "";
  constructor(){}
}


Screenshot showing a simple to-do app

Our example to-do app

Each entry in the checklist is represented by a CheckListItem component, and the CheckListapp maintains the list of items in the checklist. We can then use this in our angular app simply by adding <check-list></check-list> before bootstrapping the module.

Loading Components From Outside Angular Dynamically

The method above works well when the <check-list></check-list> tags are already on the HTML page before the Angular app is bootstrapped. Then, as long as CheckList is a bootstrap component, Angular will load the components at the tag on bootstrap. What if we want to do this dynamically from outside Angular? Looking at the Angular source code for ApplicationRef, we see the following:

 /**
   * Attaches a view so that it will be dirty checked.
   * The view will be automatically detached when it is destroyed.
   * This will throw if the view is already attached to a ViewContainer.
   */
  abstract attachView(view: ViewRef): void;


So if we have some way of providing the component’s ViewRef, we should be able to dynamically load components. In fact, this is exactly what Angular does as part of its bootstrapping. We will do this by creating a DynamicNg2Loader class that looks like this:

import {Type, ApplicationRef, ComponentFactoryResolver, Component, ComponentRef, Injector, NgZone} from '@angular/core';


export class DynamicNg2Loader {
    private appRef: ApplicationRef;
    private componentFactoryResolver: ComponentFactoryResolver;
    private zone:NgZone;

    constructor(private injector:Injector) {
        this.appRef = injector.get(ApplicationRef);
        this.zone = injector.get(NgZone);
        this.componentFactoryResolver = injector.get(ComponentFactoryResolver);
    }

    loadComponentAtDom<T>(component:Type<T>, dom:Element, onInit?: (Component:T) => void): ComponentRef<T> {
        let componentRef;
        this.zone.run(() => {
            try {
                let componentFactory = this.componentFactoryResolver.resolveComponentFactory(component);
                componentRef = componentFactory.create(this.injector, [], dom);
                onInit && onInit(componentRef.instance);
                this.appRef.attachView(componentRef.hostView);

            } catch (e) {
                console.error("Unable to load component", component, "at", dom);
                throw e;
            }
        });
        return componentRef;
    }
}

There is quite a bit going on here, so let’s break it down. The constructor takes an Ng2ModuleInjector. We will explain how to get the injector for your Ng2Module in a minute, but assuming you have an injector, we can then get a reference to the ApplicationRef, NgZone, and ComponentFactoryResolver for the loaded module. The loadComponentAtDom function takes a reference to the Component that we want to load (in our case the CheckListItem), an Element that is already part of the DOM, and an onInit function that is called once the component is loaded. We can initialize and push values to the components directly through the component reference passed in through the onInit function. The dom element that is passed into loadComponentAtDom is the location where our Angular 2 component will be loaded.

Let’s step through the function to see what each line does.

let componentFactory = this.componentFactoryResolver.resolveComponentFactory(component);
componentRef = componentFactory.create(this.injector, [], dom); this.appRef.attachView(componentRef.hostView);

The first line gets the component factory for the component that we want to load. Remember that the component has to be an entry component for this to work. The second line creates a new instance of the component and returns a ComponentRef. The third line then calls the onInitfunction if it’s passed in with an instance of the Component type. Finally, the last line attaches the componentRef’s ViewRef to the ApplicationRef so that Angular can then perform change detection and other lifecycle events on the component. All of these need to happen inside Angular’s zone.

Coming back to the constructor, we can get the Angular module’s injector when bootstrapping the module like so:

platformBrowserDynamic().bootstrapModule(AppModule).then(function(ng2ModuleInjector){
     console.log(“I have a reference to the injector : “, ng2ModuleInjector);
     let ng2Loader = new DynamicNg2Loader(ng2ModuleInjector);
});

Putting It All Together for Our Example

Let’s make our CheckListItem component an EntryComponent, bootstrap the module, and create an instance of the DynamicNg2Loader class.

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ CheckList, CheckListItem ],
  entryComponents: [ CheckList, CheckListItem ],
})
export class AppModule {
     ngDoBootstrap() {}
}

And finally, we can use the loader to load the component when required.

let loadedComponentReferences: ComponentRef = [];

platformBrowserDynamic().bootstrapModule(AppModule).then(function(ng2ModuleInjector){
  let ng2Loader = new DynamicNg2Loader(ng2ModuleInjector);
  let container = document.getElementById('angular-container');
  document.getElementById('non-angular').hidden = false;

  let count = 0;
  document.getElementById('add-component').onclick = function() {
    let parent = document.createElement('app-parent');
    container.appendChild(parent);
    let compRef = ng2Loader.loadComponentAtDom(CheckListItem, parent, (instance) => {
      instance.value = document.getElementById('text-input').value;
    });
    loadedComponentReferences.push(compRef);
  };
  document.getElementById('remove-components').onclick = function () {
    loadedComponentReferences.forEach(compRef => {
      compRef.destroy();
    });
  }
});


Screenshot showing the final to-do app in JS

A to-do app that dynamically loads Angular components

You can see the plunkr in action here.

AngularJS app

Published at DZone with permission of Shridharan Chandramouli, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Microservices 101: Transactional Outbox and Inbox
  • 10 Things to Know When Using SHACL With GraphDB
  • The Power of Docker Images: A Comprehensive Guide to Building From Scratch
  • What Is JavaScript Slice? Practical Examples and Guide

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: