Git Product home page Git Product logo

angular-extensions / elements Goto Github PK

View Code? Open in Web Editor NEW
312.0 9.0 40.0 11.28 MB

Lazy load Angular Elements (or any other web components / custom elements ) with ease!

Home Page: https://angular-extensions.github.io/elements/

License: MIT License

JavaScript 1.51% TypeScript 47.50% HTML 41.71% SCSS 9.29%
angular angular-elements web-components lazy-loading custom-elements micro-frontends microfrontends

elements's Introduction

ANGULAR EXTENSIONS ELEMENTS

The easiest way to lazy-load Angular Elements or any other web components in your Angular application!

by @tomastrajan

npm npm npm Build Status codecov Conventional Commits Twitter Follow

Documentation

Quickstart

  1. Install npm i @angular-extensions/elements
  2. Add import { LazyElementsModule } from '@angular-extensions/elements';
  3. Append LazyElementsModule to the imports: [] of your AppModule
  4. Add new schemas: [] property with CUSTOM_ELEMENTS_SCHEMA value to @NgModule decorator of your AppModule
  5. Use *axLazyElement directive on an element you wish to load and pass in the url of the element bundle

Example of module implementation...

import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { LazyElementsModule } from '@angular-extensions/elements';

@NgModule({
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  imports: [BrowserModule, LazyElementsModule],
  declarations: [AppComponent, FeatureComponent],
  bootstrap: [AppComponent],
})
export class AppModule {}

Example of component implementation

import { Component } from '@angular/core';

@Component({
  selector: 'your-org-feature',
  template: `
    <!-- will be lazy loaded and uses standard Angular template bindings -->
    <some-element
      *axLazyElement="elementUrl"
      [data]="data"
      (dataChange)="handleChange($event)"
    >
    </some-element>
  `,
})
export class FeatureComponent {
  elementUrl = 'https://your-org.com/elements/some-element.js';

  data: SomeData;

  handleChange(change: Partial<SomeData>) {
    // ...
  }
}

Supported Angular versions

Library was tested with the following versions of Angular and is meant to be used with the corresponding major version ("@angular/core"": "^15.0.0" with "@angular-extensions/elements": "^15.0.0" ).

  • 9.x (full IVY support, using renderers so careful with SSR)
  • 8.x (partial IVY support, axLazyElement works but axLazyElementDynamic does NOT work with IVY)
  • 7.x
  • 6.x (eg npm i @angular-extensions/elements@^6.0.0)

Internal dep graph

Internal dep graph

Become a contributor

Missing a feature, found bug or typo in docs?

Please, feel free to open an issue or submit a pull request to make this project better for everyone! πŸ€—

Tomas Trajan
Tomas Trajan

πŸ’» 🎨 πŸ’‘ πŸ“– πŸ€” πŸš‡ ⚠️
Artur Androsovych
Artur Androsovych

πŸ’» πŸ›
Wayne Maurer
Wayne Maurer

πŸ› πŸ’»
Santosh Yadav
Santosh Yadav

πŸ’» πŸ“¦
David Dal Busco
David Dal Busco

πŸ’» πŸ’‘
Zama Khan Mohammed
Zama Khan Mohammed

πŸ’» πŸ€” ⚠️
Angel Fraga Parodi
Angel Fraga Parodi

πŸ’‘ πŸ€”
ye3i
ye3i

πŸ’» πŸ€”
Heorhi Shakanau
Heorhi Shakanau

πŸ’» πŸ€”
Felipe Plets
Felipe Plets

πŸ’» 🎨 πŸ’‘ πŸ“– πŸ€” ⚠️
jkubiszewski
jkubiszewski

πŸ’»
Heorhi Shakanau
Heorhi Shakanau

πŸ’»
NagornovAlex
NagornovAlex

πŸ’»
Joseph Davis
Joseph Davis

πŸ’»
Arooba Shahoor
Arooba Shahoor

πŸ’»
Maximilian Wright
Maximilian Wright

πŸ“–
Jakub Stawiarski
Jakub Stawiarski

πŸ’»
PaweΕ‚ Kubiak
PaweΕ‚ Kubiak

πŸ’»
Egor Volvachev
Egor Volvachev

πŸ’»
Daniel Bou
Daniel Bou

πŸ’»

Sponsors

Are you currently working in an enterprise polyrepo environment with many applications and found yourself thinking you could provide so much more value only if you had better overview to plan, track progress and just get things done?

Try Omniboard, the best tool for lead software engineers and architects that helps them to get an overview to drive change in enterprise polyrepo environments by querying and tracking all their code bases!

The free plan let's you get a full overview of all your projects with your first dashboard, tracking up to 3 different things!

Omniboard.dev - getting started in less than 5 minutes

elements's People

Contributors

9kubczas4 avatar angelfraga avatar arooba-git avatar arturovt avatar danielbou99 avatar dependabot[bot] avatar felipeplets avatar gshokanov avatar jkubiszewski avatar mohammedzamakhan avatar nagornovalex avatar nerrdii avatar peterpeterparker avatar santoshyadavdev avatar tomastrajan avatar twixthehero avatar volvachev avatar wmaurer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

elements's Issues

Add hooks support

Hi,
I have a use case where adding hooks support to @angular-extensions/elements would be beneficial. This idea is somewhat similar to #30, so maybe they could be implemented at the same time.

Problem

Let's take an application with several Angular elements packaged as web components. Naturally, they may have several common dependencies that we may want to load only once per page (for example core Angular packages or ngrx). In order to improve UX and time to first meaningful paint, we postpone loading these dependencies until the components are requested by the current view.

With the current API of @angular-extensions/elements I don't see a way this could be easily achieved.

Proposal

Add hooks to ElementConfig:

{
  url: 'https://my-awesome-component.com',
  hooks: {
    beforeLoad: () => resolveDependencies('my-component')
  }
}

The library will execute beforeLoad hook before inserting script with the component's JS bundle. If the hook returns a Promise (which is necessary in this particular case), the library will wait for this Promise to resolve before proceeding with the request. If the Promise fails, the corresponding error component is shown instead.

Do you think this API makes sense for @angular-extensions/elements?

Suggestions for local development.

First off, thank you for this package. It has made working with Elements so much more enjoyable. I'm trying to find a way to work locally without having to build every time. I'm currently setup to create Elements as sub-app's or widgets within a larger Angular project. I was following some of the examples on the ngx-build-plus repo where there are build scripts that concatenate all the js of the element, then copy it into the larger application's assets directory. Then I setup the LazyModuleConfigs to point the tag to the corresponding location in the assets folder. This works perfectly. I can work on a change in the Element, then build, then run the main app on Localhost and see if the change functions as it should. However I can't figure out how to have the element running on say localhost:4201, and the main app on 4200, and have the main app point to the localhost:4201 url for the element. This live-reload option would make development of the Element much more fluid. On the corresponding blog post there was a proxy config mentioned, but I can't seem to get that to work either. Any help would be appreciated.

Create schematics package and add "ng add" support

WOuld be cool if it would be possible to ng add @angular-extension/schematics which will install the package and add LazyElementsModule.forRoot() with some basic config,... could be also customized using prompts if user wants default configuration or just plain import...

A platform with a different configuration has been created. Please destroy it first.

My use case is below

There are multiple angular elements being served from different hosts.
Another app(main) which consumes these elements.
All of them are using same version of libs.
I used externals in all the paces including main app so that the additional scripts file and polyfills would be served from main.
From the elements just included the main bundle file.
It all works fine until I use second element from second bundle.
Once I load the second bundle it errors out with the title and would not load it. Stack trace is as below. Any help to resolve this issue would be appreciated.

image

Not sure if this is something to do with ngx-build-plus

my externals config is as below

module.exports = {
output: {
jsonpFunction: 'wpJsonpTimeline****'
},
"externals": {
"rxjs": "rxjs",
"@angular/core": "ng.core",
"@angular/common": "ng.common",
"@angular/common/http": "ng.common.http",
"@angular/platform-browser": "ng.platformBrowser",
"@angular/platform-browser-dynamic": "ng.platformBrowserDynamic",
"@angular/compiler": "ng.compiler",
"@angular/elements": "ng.elements",
"@angular/router": "ng.router",
"@angular/forms": "ng.forms"
}
}

Loading WebComponents, consisting of multiple files

When using @angular/cli to create WebComponents one ends up with at least 2 files for each WebComponent: main.js and styles.js.
But the elementConfig only supports a single URL per element. Sure, I can concatenate the files builded into a single file, this works for production, but not when I want to debug the app locally via ng serve :(

axLazyElement works poorly with OnPush components

Currently, the directive works poorly with OnPush components, since change detection isn't triggered on an element's load. I believe the fix is as simple as calling ChangeDetectorRef.markForCheck() after the element has been loaded and inserted back into the component's view.

Runtime loading of element based on user action

I have an angular app which has two apps and one shell app.

Micro app : home and aboutus
Shell app : cz-app

In shell app I have one page (contactus) where I have a drop-down with two values (home and aboutus). Now I want to load an element just below drop-down based on selection value.

I tried to use axLazyElementDynamic and updated configuration on valueChange, but it didn't work.

I tried manually triggering change detection by "this.cdr.detectChanges()". It didn't help me.

Here is my github repo.

Could anyone please check given repo.

Note - both microapps are working fine on separate route.

[FEATURE]: add abilty to reload all/explicit lazy laoded files

Hi

i have angular elements with own ngrx states.

if the parent app use logout, the lazy laoded files doest not clean their own states.
I cannot clear just onDestroy in own apps because this would reload data each time the user change the route but always logged In.

Ist there a way to archieve this?

getting reference to the new lazy loaded element

hi,
i am using *axLazyElementDynamic to lazy load dynamic component.
very similar to the dynamic demo in the docs. the configuration is an Array retrieved from the server.

the problem i am facing:
i need to grab a hold on the created element in my class, in order to set some attributed, and register to event emitters.

i've tried using ViewChildren, but cannot get a reference to the element. i've tried various lifecycle hooks (oninit, aftercontentini, afterviewinit) but the viewchildren variable is always undefined.

you're help is appriciated.

Add support for dynamically resolved URLs

Hi πŸ‘‹
I have encountered the following issue with this package: it is impossible to dynamically resolve URLs at runtime while still compiling using AOT.
I have a use case where the URL is resolved dynamically at runtime in a sync way. This URL may be configured by the running sub-application or it may be configured by one of web-components.
The API looks somewhat like this:

const lazyElementConfig: LazyElementModuleOptions = {
  elementConfigs: [
    { tag: 'ba-menu', url: resolveUrl('ba-menu') }
  ]
}

My team uses a custom package to store some data on the window object to share it between micro-frontends. This data is resolved before the application's bootstrap.
But during the compilation, Angular aggressively optimizes this value to undefined, which obviously breaks the application.
Currently, the workaround is to manually provide URLs to the directive in templates.
I think it could be fixed if elementConfigs could accept url as a function that resolves to URL value.
Could you please look into it? I'd be happy to contribute if you need any help.

Reloading capability

We need to have reloading capability that could be called whenever a URL fails to load (maybe because of the network issues).

Zone already loaded.

I am trying to load an angular element in another angular application
Lcally everything looks good.have problems running in production build.

Zone already loaded.

Cache problems

I have a shell application that loads all the Angular Elements using your library.
On my last update, I figured out cache problems, so the version of the elements was the previous.
In order to solve the problem I had to empty the browser cache.

I know that the cache is managed using the ETag of the file so how can check what's happening?

Angular 10 support

Does this package works with angular version "^10.0.0"?

I tried it and I have got below error.

core.umd.js:4484 ERROR Error: The selector "mf-home" did not match any elements
    at DefaultDomRenderer2.selectRootElement (platform-browser.umd.js:987)
    at locateHostElement (core.umd.js:7980)
    at ComponentFactory.create (core.umd.js:22844)
    at ApplicationRef.bootstrap (core.umd.js:29036)
    at core.umd.js:28736
    at Array.forEach (<anonymous>)
    at PlatformRef._moduleDoBootstrap (core.umd.js:28736)
    at core.umd.js:28704
    at ZoneDelegate.invoke (zone-evergreen.js:364)
    at Object.onInvoke (core.umd.js:28133)

Here is my package.json


"dependencies": {
    "@angular-extensions/elements": "^10.0.2",
    "@angular/animations": "^10.0.0",
    "@angular/cdk": "^10.1.3",
    "@angular/common": "^10.0.0",
    "@angular/compiler": "^10.0.0",
    "@angular/core": "^10.0.0",
    "@angular/elements": "^10.0.14",
    "@angular/flex-layout": "^10.0.0-beta.32",
    "@angular/forms": "^10.0.0",
    "@angular/material": "^10.1.3",
    "@angular/platform-browser": "^10.0.0",
    "@angular/platform-browser-dynamic": "^10.0.0",
    "@angular/router": "^10.0.0",
    "@nrwl/angular": "10.0.12",
    "@webcomponents/custom-elements": "^1.4.2",
    "@webcomponents/webcomponentsjs": "^2.4.4",
    "ngx-build-plus": "^10.1.1",
    "rxjs": "~6.5.5",
    "zone.js": "^0.10.2"
  },

my app.component.html

<mf-home *axLazyElement="elementUrl"> </mf-home>

my app.component.ts

elementUrl = 'assets/bundles/main.f0aedbe8f4f67ea36480.js';

this main.js file is created with help of ngx-build-plus package. This works if I load this js file using script tag.

I can provide complete code if it is required.

Multiple Configs are lost

If you make multiple calls to LazyElementsModule.forFeature(options), ... you will only get the last configuration and lose references to other configs.

angular-extension bug

In LazyElementModule constructor:

    if (elementConfigsMultiProvider && elementConfigsMultiProvider.length) {
      const lastAddedConfigs =
        elementConfigsMultiProvider[elementConfigsMultiProvider.length - 1];
      lazyElementsLoaderService.addConfigs(lastAddedConfigs);
    }

This should be:

const configs = [].concat.apply([], elementConfigsMultiProvider);
lazyElementsLoaderService.addConfigs(configs);

DOMException: CustomElementRegistry.define 'ust-frontend' has already been defined as a custom element

Hello everybody,

i have a problem with "multiple different dynamic elements I would like to load 2 web components. The second compnent always uses the js-file of the first component. Therefore the error 'ust-frontend' has already been defined as a custom element. I have built the web components according to the following pattern.

I hope someone can help me.

Thanks

<< ----------------------------------->>
web component

import { AppRoutingModule } from './app-routing.module';
import { HttpClientModule } from '@angular/common/http';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, Injector } from '@angular/core';
import { Router } from '@angular/router';
import { Location} from '@angular/common';
import { createCustomElement } from '@angular/elements';

import { AppComponent } from './app.component';
import { EcbrateComponent } from './component/ecbrate/ecbrate.component';

import { FormsModule } from '@angular/forms';

@NgModule({
declarations: [
AppComponent,
EcbrateComponent
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule,
HttpClientModule
],
providers: [],
entryComponents: [AppComponent]
})
export class AppModule {
constructor(private injector: Injector, private router: Router, private location: Location ) {
const appElement = createCustomElement(AppComponent, {
injector: this.injector
});
console.log('define ecb-frontend');
customElements.define('ecb-frontend', appElement);
console.log('defined ecb-frontend');
console.log(this.location);
this.router.navigateByUrl(this.location.path(true));
console.log(this.router);
this.location.subscribe(data => {
console.log(data);
this.router.navigateByUrl(data.url);
});
}
// tslint:disable-next-line: typedef
ngDoBootstrap() {}
}

<<---------------------------------------------------->>

lazy-element component

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { url } from 'inspector';

@component({
selector: 'app-lazy-element2',
template: <ng-container *ngFor="let c of dynamicConfigs"> {{c.url}} <ax-lazy-element *axLazyElementDynamic= "c.tag, url: c.url; module: true" raised> </ax-lazy-element> </ng-container>
})
export class LazyElement2Component implements OnInit {

elementUrl = '';
elementTag = '';
onDestroy: any;
dynamicConfigs = [];

constructor(readonly activatedRoute: ActivatedRoute) {
/*
this.onDestroy = this.activatedRoute;
// tslint:disable-next-line: deprecation
let componantName = '';
this.onDestroy.url.forEach( item => {
item.forEach( pathItem => {
console.log(pathItem.path);
componantName = pathItem.path;
});
});
this.elementTag = '';
this.elementUrl = 'http://localhost:4201/component/' + componantName + '/' + componantName + '.js';
console.log(this.elementUrl);
this.elementTag = componantName;
*/
this.dynamicConfigs = [
{url: 'http://localhost:4201/component/ust-frontend/ust-frontend.js', tag: 'ust-frontend'},
{url: 'http://localhost:4201/component/ecb-frontend/ecb-frontend.js', tag: 'ecb-frontend'}
];
}

ngOnInit(): void {}

// tslint:disable-next-line: use-lifecycle-interface
ngOnDestroy() {
// this.onDestroy.unsubscribe();
}
}

<< --------------------------- >>
app.module

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { LazyElementsModule } from '@angular-extensions/elements';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HeaderComponent } from './components/header/header.component';
import { HomeComponent } from './components/home/home.component';
import { DynamicLayoutComponent } from './components/dynamic-layout/dynamic-layout.component';
import { LazyElementComponent } from './components/lazy-element/lazy-element.component';
import { MainLayoutComponent } from './components/dynamic-layout/main-layout.component';
import { LoginComponent } from './components/login/login.component';
import { ReactiveFormsModule } from '@angular/forms';
import { LazyElement2Component } from './components/lazy-element2/lazy-element2.component';

@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [
AppComponent,
HeaderComponent,
HomeComponent,
DynamicLayoutComponent,
LazyElementComponent,
LazyElement2Component,
MainLayoutComponent,
LoginComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
LazyElementsModule,
CommonModule,
ReactiveFormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {}

Fix module configuration for Angular 7.x

Currently, the application fails with the following error when using the module configuration:

ERROR TypeError: Cannot read property 'replace' of undefined

The following code produces the error:

const lazyElementConfig: LazyElementModuleOptions = {
    elementConfigs: [
        { tag: 'ba-layout', url: 'http://localhost:4000' }
    ]
};

@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [
        BrowserModule,
        LazyElementsModule.forRoot(lazyElementConfig)
    ],
    bootstrap: [
        AppComponent
    ],
    schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule {}

In AppComponent:

@Component({
    selector: 'app-root',
    template: `
        <ba-layout *axLazyElement>
        </ba-layout>
    `
})
export class AppComponent {}

I checked the source code and it seems that the directive doesn't look up the config on Angular 7.x branch. Could you please fix it? Or I could submit a PR myself.

Issue with the element content being empty from array

Hi There,

Here is our use case, we are using gridster2 and axLazyLoad combination for our implementation.
We want to implement the undo functionality.
As part of this we wanted to take advantage of already rendered elements by keeping in an array and embedding the same back on undo considering the time it takes for re rendering the whole element.
However, somehow the content of the lazy loaded element is becoming empty.
Any help to resolve this issue is appreciated.

Env configuration

Hi, is there any possibility to add multiple URL's for one package?
For example in dev env, I want to use the package from source A and in test/prod env from source B.

Getting "net::ERR_ABORTED 404 (Not Found)" while dynamically loading web component

I have compiled a couple of Angular web elements and am trying to load them dynamically via the ax-lazy-element component like so:
<ax-lazy-element *axLazyElementDynamic="tag, url: url; module: true; errorTemplate: error" [(node)]="node">
</ax-lazy-element>
<ng-template #error>Loading of {{url}} failed...</ng-template>

In the component, I have
public url = 'projects/elements/dist/components/type/item.js';
public tag = 'oyl-type-item'

Just before the error message I am getting a warning
sockjs.js:2999 WebSocket connection to 'ws://localhost:8100/sockjs-node/983/5jt2rk1u/websocket' failed: WebSocket is closed before the connection is established
followed by the error
angular-extensions-elements.js:210 GET http://localhost:8100/projects/elements/dist/components/type/item.js net::ERR_ABORTED 404 (Not Found)

I can do an import with the very same path, aka
const module = await import('projects/elements/dist/components/type/item.js'); and all is good.
Does the localhost:8100 that is apparently added by the ax-lazy-element component change things here?

However, when I pass the very same URL as a variable to import() as in
path = 'projects/elements/dist/components/type/item.js';
import(path),
I get the error
Warnings while compiling ... Critical dependency: the request of a dependency is an expression
Which is why I looked for an alternative in the first place and gave your library a spin. I'm just mentioning it because maybe those things are connected?

ax-lazy-element *axLazyElementDynamic in *ngFor

We have a use case similar to the one outlined here: #5

We have a container that knows which features the logged in user has and will send back an array of modules said user has access to. We then loop over these items and render them on the page.

There seems to be a race condition or zone issue (??) with the dynamic directive when trying to render out multiple web components on to the page. What seems to happen is that it'll render out only the last web component multiple times. The source looks suspect as well:
image

Here is a stackblitz that demonstrates the issue: https://stackblitz.com/edit/axlazy-in-ngfor

FYI, stackblitz seems to hotload components and the CustomElementRegistry will not get wiped out on update so if you edit any of the code it'll complain. Workaround is to just refresh the entire page circa 2003.

You can also modify the existing view to have some type of event handler outside of the ngFor that will render out the second element but it seems that if these elements are rendered in an ngFor they are not rendering appropriately.

Strategy for using skeleton loading

My objective is to display skeleton loading for my web-component. This skeleton loading should be part of the DOM until the first data request from the web-component emits. To accomplish the last part I have added an "intialLoadDone" to my web-component, and it works ok. Something like this:

<!-- Some fancy skeleton loading build with tailwind-->
<div *ngIf="!isDoneLoading">
</div>


<web-cmpt [hidden]="!isDoneLoading" *axLazyElement="URL" (intialLoadDone)="doneLoading()">
</web-cmpt>

The problem is when there is an error loading the web cmpt. How can I make the component aware of this? Is it possible to get notified if there is a problem loading the web component? I hope my question makes sense 😊

Error Hook with details

Hi,

is there a possibility to get more details about the error which occurred while loading the script?
Especially the HTTP Status Code + message would be interesting.
(In our case we want to decide between missing js and auth problems)

In the code it looks like that there is just a hook if we could successfully load the script file.

Cheers,
Patrick

Preload using Service

Once we have the ElementConfig's support added, we need to support preloading all web components, even before they are shown in the browser using service

Loaded Elements Replace All Content

Angular v 8.6.0
@angular-extensions/elements v 8.6.0

I noticed this when registering the element alongside other html content in the App Component. The entire content is replaced by the loaded element, not just the dom element with the *axLazyElement directive.

I moved the *axLazyElement into a feature module/component, and still noticed that the entire app content was replaced with the loaded element.

I should note that I'm using the module configuration method, instead of the directive Input.

Lazy loading Angular libraries

Hello guys!
I'm really new in Angular world so sorry about my questions!

I have some Angular libraries (developed by my colleagues) that are not on npm and I would like to load components that are exported by these.
My libraries are stored in a local folder and I'm trying to lazy load these libraries without success.

<ng-template #loading>Loading...</ng-template>
<ng-template #error>Error loading component</ng-template>
<mycomponent *axLazyElement="this.elementUrl; loadingTemplate:loading; errorTemplate:error; module: true"></mycomponent >

where
elementUrl = "./libraries/mylibrary.js";

So I bring your "basic usage" example that uses mwc-icon.js and I saved the module url locally
https://unpkg.com/@material/[email protected]/mwc-icon.js?module

Using the remote URL all is working as described.
Switching to the local path (./libraries/mwc-icon.js) I must change the internal import of the js but everything worked properly!

Now my question is? Is it possible to lazy load my libraries with your @angular-extensions/elements or I need to do some "transformation" to my libraries/components?

Thank you very much!

Suggestion Extending `LazyElementModuleOptions`

Suggestion Extending to extend LazyElementModuleOptions with defaultURL

Suggestion To Implement a new option defaultURL

When I load elements from different sources and let's say one source is a bundle of 10 web components and another one is a bundle of one web component I could save some configuration by having a default URL.

If the tag to load is not referenced in elementConfigs it automatically falls back to the configured default URL.

This could save in the above-mentioned example 10 lines of configuration.

Actual Behaviour

app.module.ts

const options: LazyElementModuleOptions = {
  elementConfigs: [
    { tag: 'elements-one', url: 'https://your-org.com/elements/many-elements.js' },
    { tag: 'elements-two', url: 'https://your-org.com/elements/many-elements.js' },
    { tag: 'elements-three', url: 'https://your-org.com/elements/many-elements.js' },
    { tag: 'spacial-element', url: 'https://your-org.com/elements/spacial-element.js' }
  ]
};

@NgModule({
   ...
  imports: [ LazyElementsModule.forFeature(options)]
})
...

app.component.ts

@Component({
  selector: 'your-org-feature',
  template: `
    <element-one *axLazyElement></element-one>
    <element-two *axLazyElement></element-two>
    <element-three *axLazyElement></element-three>
    <special-element *axLazyElement></special-element>
  `
})
export class FeatureComponent {}

Suggested Behaviour

app.module.ts

const options: LazyElementModuleOptions = {
  defaultURL: 'https://your-org.com/elements/many-elements.js',
  elementConfigs: [
    { tag: 'spacial-element', url: 'https://your-org.com/elements/spacial-element.js' }
  ]
};

@NgModule({
   ...
  imports: [ LazyElementsModule.forFeature(options)]
})
...

app.component.ts

@Component({
  selector: 'your-org-feature',
  template: `
    <element-one *axLazyElement></element-one>
    <element-two *axLazyElement></element-two>
    <element-three *axLazyElement></element-three>
    <special-element *axLazyElement></special-element>
  `
})
export class FeatureComponent {}

Component not rendering

I'm creating a POC in an Nx monorepo using @angular-architects/ddd and have an issue where using a web-component inside one the libs is not working as expected. I have one component loaded in at the global level that works as expected. However, the second component inside the feature is not rendering. I can see the script tag being added to the DOM and there are no console errors but the component itself is not rendering. This is all that is added to the DOM where the component should be

<!--bindings={
  "ng-reflect-url": "http://localhost:3333/api/publ"
}-->

Further Testing: I added a loading template and the loading template is showing as expected but is never disappearing. I know the URL is serving the component and that the JS for the component is valid as well.

Event binding not working

Hello @tomastrajan,

I'm trying to have the Angular component instance using bindings with an ax-lazy-element.
Here a test project https://github.com/BossOz/test-aee-bindings

In my angular element DesktopSystemEmptyElementComponent I emit the event componentReady that, unfortunately, is not fired.

here the code of the html

<ax-lazy-element *axLazyElementDynamic="'vdr-empty-component', url: url; module: true"
                 (componentReady)="onComponentReady($event)">
</ax-lazy-element>

Why this event is not fired?
Is because that the emit is in the ngOnInit()?

Thank you!

Add tag as input for allowing dynamic web elements loading

Hi, great job.

You save my day πŸ₯‡ but I think a new feature can be added.

Correct me if I am wrong but I think there is a case the library is not covering.
If we are in a shell app, and we want to load several micro-apps (web-elements) dynamically based on an incoming config, the shell shouldn't know about future tag elements.

For example, having this config as available micro-apps/widgets:

[
  { "tag": "micro-app1", "url": "/micro-apps/micro-app1/main.js" },
  { "tag": "micro-app2", "url": "/micro-apps/micro-app2/main.js" }
 ...
  { "tag": "micro-appN", "url": "/micro-apps/micro-appN/main.js" }
]

If we add a new input (tag) to the lazy-element.directive.ts, we could replace the original tag by the giving tag.

const elementTag = (this.template as any)._def.element.template.nodes[0]

The usage could look like this:

<div *axLazyElement="'/micro-apps/micro-app1/main.js'; tag: 'micro-app1'"></div>

Being converted to

<micro-app1 *axLazyElement="'/micro-apps/micro-app1/main.js'; tag: 'micro-app1'"></micro-app1>

At least, in my case, the shell app code doesn't know about the incoming config and tag elements and therefore I still have to inject the micro-apps using script elements.

Feature: pass attributes to Web component with binding

PROBLEM

Currently I believe that you can not set webcomponents attributes/properties via angular data binding.

SOLUTION

It would be nice if the directive acts as a wrapper, giving support for properties to be passed to the webcomponent.

PROPOSED TECHINCAL SOLUTION

Add another input to the directives for data to pass to the webcomponent @Input(axLazyElementsDataBinding) dataBinding

using ngOnUpdate detect updates.

Using the angular renderer2 service set the webcomponent's attribute with the updated data

Do you think this would be in scope of this library?

Using axLazyElement with differential builds

Hi There,

This is more of a doubt rather an issue.

Does the lib support loading of a element which is built using differential build? If yes, is there an example which we can refer to ?

Multiple components in single file

It more of a suggestion than a issue. We have a scenario where we have grouped multiple web components in a single file. But while specifying the respective file url in the elementConfig, its working only if we add multiple entries for the each component with the same file url.

const plugins: LazyElementModuleOptions = {
  elementConfigs:[
    { tag: component1', url: 'assets/component-file.js'},
    { tag: 'component2', url: 'assets/component-file.js'},
   { tag: 'component3', url: 'assets/component-file.js'},
  ]
}

If a solution exists for the above, please let me know.

If solution doesn't exists, my suggestions would be:

  1. Making the tag as a pattern match
    { tag: 'component-*', url: 'assets/component-file.js'}
  2. Match the element also with a attribute instead of the element-tag
    <component-one tag-attr *axLazyElement></component-one>
    { attribute-tag: 'tag-attr', url: 'assets/component-file.js'}

Property binding[] is not working when we call angular element from parent project

  1. I have an element named element-setpassword
  2. i have place it in the assests of route application after build
  3. when i try to send data from parent to element it is failing if i am using property binding. example below
    <element-setPassword id="element-set-password" *axLazyElement="elementUrl" [usernamevalue]="name"> --> failing

<element-setPassword id="element-set-password" *axLazyElement="elementUrl" usernamevalue="subham"> --> working

Feature - Add web element for sharing state

The motivation of this feature is providing a generic way for sharing the state of the registry of loaded web elements across web elements/micro-apps.

The idea is simple, starting from the point we have the main app (aka shell-UI) which is already using angular-extensions/elements, we should be able to enable that feature via a module configuration flag.

Once this flag is enabled, the main module of this extension will create a web element (root-axLazyElement) that can be used instead of the directive or the current axLazyElement component.

Then even a lazy-loaded web components/micro-apps could be benefited from the existing state in the root-axLazyElement element created by the main app.

During the root-axLazyElement generation process, we could check if that component is already there or not in order to prevent creating another instance of the root element.

Not able to see custom component in Angular 12.2.0

Hi there,
I'm trying to use @angular-extensions/elements in my project but not able to see anything.

"@angular/core": "~12.2.0",
"@angular-extensions/elements": "^12.6.0"

Sample page

HTML

<ng-template #error>Loading failed...</ng-template>
<blue-ui *axLazyElement="url; errorTemplate: error;" ></blue-ui>

Component

@Component({
  selector: 'app-sample-page',
  templateUrl: './sample-page.component.html',
  styleUrls: ['./sample-page.component.scss']
})
export class SamplePageComponent implements OnInit {

  url = 'http://localhost:4201/main.js'

  constructor() { }

  ngOnInit(): void {
   
  }

}

AppModule

@NgModule({
  schemas:[
    CUSTOM_ELEMENTS_SCHEMA
  ],
  declarations: [
    AppComponent,
    SamplePageComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    LazyElementsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Result
image

Is there something wrong in my example?

Edgar

onLoad hook

how to notify component that element is loaded?

Support for load Angular Elements in a Angular Elements

Hi, brilliant job!

Correct me if I am wrong but I think there is a case the library is not covering.
If we are in one of a micro-apps (angular-elements) loaded in shell app, and we want to load other angular elements dynamically, for example widget shared beetwen micro-apps.
When we import LazyElementsModule in Angular Elements, a new instance of LazyElementsLoaderService will be created without knowledge of already registered items.

It would be great if your library covered such a case.

Is there a way to access the child component (angular element as web component)?

Hi There,

My use case is as below.

User will drop the component on UI.
We then load angular element as web component at run time via axLazyElementDynamic directive.
We will then initialise the component through typescript in the parent app.
we used a wrapper class in the parent app to load the actual web component considering we need the reference to be able to access public properties.
Though we use the wrapper, the reference is limited to the wrapper but not to the actual component loaded dynamically.
Any insights on how we would be able to access public properties of the dynamically loaded web element using below?

<ax-lazy-element *axLazyElementDynamic="'comp-selector', url: url;">

where the comp-selector is the actual component

pass property to *azLazyElementDynamic

<ax-lazy-element *axLazyElementDynamic="elementName, url: elementUrl; module: true;"
text2="{{elementText}}"
(send)="onSendReceived($event)"
(navigate)="onNavigate($event)">

i am trying to pass component property : elementText to the custom element.
i can pass strings : text2="my text"
but cannot manage to pass the "elementText" variable.
i've tried:
[text2]="elementText"
text2="{{elementText}}"

but my custom component gets 'null' as the value.

using *axLazyElement the input variables gets passed correctly to the same custom element i've used in the question above.

i've read a similar problem with #50

i've tried the 10-alpha version, still not working. with disabling ivy - not working.

then i've checked again, and it seems that the value was not available during the
connectedCallback event in the web-component. only afterwards.

so i've wrapped the code inside my connectedCallback function with setTimeout and now it works.

Property does not exist on type 'HTMLElement'.

Hi guys!

I've been trying to load 2 of my angular elements using the library. You can find them here and here

Here is the repo for the main app: click

I have a 3rd project setup like this:

const options: LazyElementModuleOptions = {
  elementConfigs: [
    { tag: 'edo-button', url: 'https://kmanev073.github.io/EdoButton/dist/main-es2015.js' },
    { tag: 'edo-input', url: 'https://kmanev073.github.io/EdoInput/dist/main-es2015.js' }
  ]
};

Then I use my components as:

<edo-button *axLazyElement
></edo-button>

<edo-input *axLazyElement
></edo-input>

<edo-button *axLazyElement
></edo-button>

It all works and you just need to add some extra scripts to your index.html:

<script src="https://kmanev073.github.io/EdoButton/dist/polyfills-es2015.js"></script>
<script src="https://kmanev073.github.io/EdoButton/dist/scripts.js" defer></script>
<script src="https://kmanev073.github.io/EdoButton/dist/polyfill-webcomp.js" defer></script>

Now the problem...
The EdoButton and the EdoInput both have Inputs and Outputs. When I try to use them like this:


<edo-button *axLazyElement
  [edoValue]="'-'"
  (edoClick)="onMinusClick($event)"
></edo-button>

<edo-input *axLazyElement
  [edoValue]="value"
  (edoChange)="onInputChange($event)"
></edo-input>

<edo-button *axLazyElement
  [edoValue]="'+'"
  (edoClick)="onPlusClick($event)"
></edo-button>

I get an error saying:

ERROR in src/app/app.component.html(2,3): Property 'edoValue' does not exist on type 'HTMLElement'.
src/app/app.component.html(7,3): Property 'edoValue' does not exist on type 'HTMLElement'.
src/app/app.component.html(12,3): Property 'edoValue' does not exist on type 'HTMLElement'.

Any ideas on how to enable inputs and outputs?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.