Angular DI in a hybrid boostrapped application - javascript

I have a sizable AngularJS (aka Angular 1) application that is written in Typescript. I've decided that new features should be written in Angular (aka Angular 4) and have taken the steps to boostrap the app in "hybrid mode".
This seems to work (all the AngularJS bits work fine) and I've added a single Angular component and that renders fine.
The problem comes when I try to add an Angular service to my Angular (so all Angular 4 so far). Angular fails to work, spitting out the seemlying ubiquitous
Can't resolve all parameters for... error.
However, if I mark the type in the components constructor with #Inject then it works ok.
All the documentation points to the fact I should not need to use #Inject routinely, so why is this failing?
My medium term need to is inject AngularJS services into the Angular components and services, and this situation isn't filling me with confidence.
app.module.ts:
// All AngualarJS definitions appear before this section.
#NgModule({
imports: [
BrowserModule,
UpgradeModule,
],
declarations: [
AppComponent,
OrgSummaryComponent
],
providers: [
OrgDetailsService,
],
bootstrap: [
AppComponent,
],
})
export class AppModule {
ngDoBootstrap() {
// Does nothing by design.
// This is to facilitate "hybrid bootstrapping"
}
}
platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => {
const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule;
upgrade.bootstrap(document.body, [AppModuleName], {strictDi: true});
});
org-details.service.ts:
import {Injectable} from "#angular/core";
export class OrgDetails {
public orgName: string;
}
#Injectable()
export class OrgDetailsService {
getOrgDetails () : OrgDetails {
const od = new OrgDetails();
od.orgName = "Some Org Name from a REST service";
return od;
}
}
org-summary.component.ts:
import {Component, Inject, OnInit} from "#angular/core";
import {OrgDetailsService} from "./org-details.service";
#Component ({
selector: "orgsummary",
template: "<h2>{{OrgName}}</h2>",
})
export class OrgSummaryComponent implements OnInit {
constructor (
/*#Inject(OrgDetailsService)*/ private orgs: OrgDetailsService, // Only works if I uncomment the #Inject
) {
}
public OrgName: string;
ngOnInit(): void {
this.OrgName = `${this.orgs.getOrgDetails().orgName}`;
}
}
tsconfig.json:
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"sourceMap": true,
"noImplicitAny": true,
"declaration": true,
"experimentalDecorators": true
},
"exclude": [
"node_modules",
"**/*.spec.ts"
],
"include" : [
"app/**/*.ts"
]
}
Many thanks,
Jeff

Just add
"emitDecoratorMetadata": true
to your tsconfig.json

Related

Ag-grid custom tool tip component doesn't work

My custom tooltip is pretty simple
custom.ts file
import { Component } from '#angular/core';
import {ITooltipAngularComp} from 'ag-grid-angular';
#Component({
selector: 'app-custom-columnheader-tooltip-component',
templateUrl: './custom-columnheader-tooltip-component.html',
styleUrls: ['./custom-columnheader-tooltip-component.css']
})
export class CustomColumnheaderTooltipComponent implements ITooltipAngularComp {
private params: any;
agInit(params): void {
console.log("test tooltip");
this.params = params;
}
}
custom.html file:
{{params.value}}
I imported the component in app.module.ts file and added it as part of declarations and withComponents
app.module.ts file:
import {CustomColumnheaderTooltipComponent} from '...';
declarations: [
CustomColumnheaderTooltipComponent],
imports: [AgGridModule.withComponents([CustomColumnheaderTooltipComponent])]
my main component which calls this custom tooltip is as follows:
main.ts
frameworkComponents = {
customTooltip: CustomColumnheaderTooltipComponent}
IN my default col Definitions:
defaultColDef: ColDef = {
enableRowGroup: true,
filter: true,
resizable: true,
sortable: true,
autoHeight: true,
valueFormatter: this.cellValueFormatter.bind(this), // numeric formatting
cellRenderer: this.cellRenderer,
headerTooltip: 'hi',
tooltipComponent:'customTooltip'
};
I was hoping the header tooltip string will be picked up by the tool tip component and it is displayed as a part of that component as 'header name: hi' but my application just displays 'hi' as the tool tip. I cant see the console logging within the agInit() , basically it doesn't connect to that component for the tool tip. Any suggestions are highly appreciated.
Looks like you have problem in registering your custom tooltip
In my app.module.ts
I would do something like below -
imports: [
BrowserModule,
FormsModule,
AgGridModule.withComponents(
[CustomColumnheaderTooltipComponent]
)
],
declarations: [
............,
CustomColumnheaderTooltipComponent,
...........
],
Check this github example from official ag grid doc

Angular 7 Creating dynamic modules with compileModuleAndAllComponentsAsync aot build issue

Problem :
I have to render a dynamic html which generates at run-time as a string, so for that purpose I've used dynamic components approach as innerHTML just not cater angular attributues like *ngIf, formGroup, router-link etc, but getting following error after deploying on local server.
Error :
Error: Unexpected value 'function(){}' imported by the module 'function(){}'. Please add a #NgModule annotation.
I tried creating the dynamic module but was getting error when making aot build because it was inside a non lazy-loaded module and according to a solution I found that aot will not work for dynamic components generating in a lazy-loaded module.
Now when I have moved it in a non lazy-loaded module I am not getting any error locally when generating aot build but getting this error after deployment however deployment is successful and nothing is breaking there.
DYNAMIC MODULE
export function createCompiler(compilerFactory: CompilerFactory) {
return compilerFactory.createCompiler();
}
#NgModule({
declarations: [StepComponent],
imports: [
CommonModule
],
exports: [StepComponent],
entryComponents: [StepComponent],
providers: [
{ provide: COMPILER_OPTIONS, useValue: {}, multi: true },
{ provide: CompilerFactory, useClass: JitCompilerFactory, deps: [COMPILER_OPTIONS] },
{ provide: Compiler, useFactory: createCompiler, deps: [CompilerFactory] }
]
})
export class DynamicStepModule { }
STEP COMPONENT
export class StepComponent implements OnInit, OnChanges {
#Input() step: Steps;
#ViewChild('dynamicComponent', { read: ViewContainerRef }) container: ViewContainerRef;
constructor(private compiler: Compiler) { }
ngOnInit() { }
ngOnChanges(change: SimpleChanges) {
if (this.step && change.step.currentValue !== change.step.previousValue) {
this.addComponent(this.step);
}
}
addComponent(step: Steps) {
const cmp = Component({ template: step.formattedText })(class DynamicComponent {
stepFormGroup: FormGroup;
constructor(
) {
// Some Component related code
}
});
const meta = NgModule({
imports: [
CommonModule,
FormsModule,
MaterialModule,
ReactiveFormsModule,
LibSharedModule,
BrowserModule,
BrowserAnimationsModule
],
declarations: [cmp]
})(class DynamicModule {
});
this.compiler
.compileModuleAndAllComponentsAsync(meta)
.then(factories => {
const factory = factories.componentFactories.find(component => component.componentType === cmp);
this.container.remove();
this.container.createComponent(factory);
});
}
}
Locally when generating aot build not getting error but after deploying on a local server getting this issue:
ERROR Error: Unexpected value 'function(){}' imported by the module 'function(){}'. Please add a #NgModule annotation.

Error when using AngularJS component inside Angular app: "Error: Trying to get the AngularJS injector before it being set."

I'm working through Angular's upgrade guide to learn how to embed AngularJS components in an Angular app. I've created a bare-bones Angular app using the Angular CLI and added a simple AngularJS module as a dependency.
When I run ng serve, the application compiles with no errors. However, at runtime, I get this message in the console:
Error: Trying to get the AngularJS injector before it being set.
What is causing this error, and how can I avoid it? I haven't deviated from the steps detailed in the upgrade guide.
Here's how I'm upgrading my AngularJS component inside my Angular app:
// example.directive.ts
import { Directive, ElementRef, Injector } from '#angular/core';
import { UpgradeComponent } from '#angular/upgrade/static';
// this is the npm module that contains the AngularJS component
import { MyComponent } from '#my-company/module-test';
#Directive({
selector: 'my-upgraded-component'
})
export class ExampleDirective extends UpgradeComponent {
constructor(elementRef: ElementRef, injector: Injector) {
// the .injectionName property is the component's selector
// string; "my-component" in this case.
super(MyComponent.injectionName, elementRef, injector);
}
}
And here's my app.module.ts:
// app.module.ts
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { UpgradeModule } from '#angular/upgrade/static';
import { ExampleDirective } from './example.directive';
import { myModuleName } from '#my-company/module-test';
#NgModule({
declarations: [AppComponent, ExampleDirective],
imports: [BrowserModule, AppRoutingModule, UpgradeModule],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {
constructor(private upgrade: UpgradeModule) {}
ngDoBootstrap() {
this.upgrade.bootstrap(document.body, [myModuleName], {
strictDi: true
});
}
}
I'm using Angular 5.2.0.
I faced the same issue, and finally solved it. There are some steps to follow before bootstrap an hybrid Angular/angularjs application.
Install the UpgradeModule npm install #angular/upgrade
Wrap your "CompanyModule" (the module where all your company components are registered) into a new angularjs module (for instance: Ng1Shared). If you not have a module for your company components, it must be created. Than downgrade AppComponent as shown below.
const MyCompanyModule = angular
.module('MyCompanyModule', [])
.component('myComponent', MyComponent)
.name;
const Ng1Shared = angular
.module('Ng1Shared', [MyCompanyModule])
.directive('appRoot', downgradeComponent({ component: AppComponent }))
.name;
Configure AppModule with basic imports (BrowserModule, CommonModule, UpgradeModule). Provide the angularjs' Injector to Angular; declare an "entryComponent" and remove the default bootstrap for AppComponent.
#NgModule({
imports: [BrowserModule, CommonModule, UpgradeModule],
declarations: [AppComponent],
providers: [{provide: '$scope', useExisting: '$rootScope'}], // REQUIRED
entryComponents: [AppComponent], // ADD AN ENTRY COMPONENT
// bootstrap: [AppComponent] MUST BE REMOVED
})
Set angularjs globally with a function provided by UpgradeModule itself and manually bootstrap Angular with DoBootstrap method provided by #angular/core.
export class AppModule implements DoBootstrap {
constructor(private upgrade: UpgradeModule) { }
public ngDoBootstrap(app: any): void {
setAngularJSGlobal(angular);
this.upgrade.bootstrap(document.body, [Ng1Shared], { strictDi: false });
app.bootstrap(AppComponent);
}
}
Create a wrapper directive for every angularjs component and add it to AppModule's declaration array.
#Directive({
selector: 'my-component'
})
export class MyComponentWrapper extends UpgradeComponent {
#Input() title: string;
constructor(elementRef: ElementRef, injector: Injector) {
super('myComponent', elementRef, injector);
}
}
I wrote a simple example available on stackblitz.
For example purposes I added angularjs MyCompanyModule to another angularjs module, called Ng1Module. As you can see also property binding between angularjs and angular component works fine.
I hope it can be useful.
https://github.com/angular/angular/issues/23141#issuecomment-379493753
you cannot directly bootstrap an Angular component that contains
upgraded components before bootstrapping AngularJS. Instead, you can
downgrade AppComponent and let it be bootstrapped as part of the
AngularJS part of the app:
https://stackblitz.com/edit/angular-djb5bu?file=app%2Fapp.module.ts
try to add an entryComponents to your AppModule like this :
...
#NgModule({
declarations: [AppComponent, ExampleDirective],
imports: [BrowserModule, AppRoutingModule, UpgradeModule],
entryComponents: [
AppComponent // Don't forget this!!!
],
providers: [],
// bootstrap: [AppComponent] // Delete or comment this line
})
...

Injection Token in Angular for Cesium.js

I'm trying to use the cesium.js library the angular way so that I can still code in typescript, I have seen some 3rd party lib tutorials, and I'm trying to inject the dependency with a dependencty token. right now, my code is not compiling, and I'm getting the error:
ERROR in ....../node_modules/cesium/Build/Cesium/Cesium.js (557,23102): Unreachable code detected.
ERROR in ....../node_modules/cesium/Build/Cesium/Cesium.js (557,24518): Unreachable code detected.
ERROR in ....../node_modules/cesium/Build/Cesium/Cesium.js (559,26990): Unreachable code detected.
I have a stack blitz with all the necessary code, but stack blitz can't handle all the dependencies to run and get the above error, so feel free to copy the code into your own environment.
my stack blitz 'demo' and here is the tutorial I followed
here are the raw files for reference as well.
This project is build with angular-cli
I started by running npm install --save cesium
cesium.lib.ts
import { InjectionToken } from '#angular/core';
import * as cesiumLib from '../../../node_modules/cesium/Build/Cesium/Cesium.js';
export const cesiumToken = new InjectionToken('cesium');
export const cesium = cesiumLib;
export type Cesium = typeof cesiumLib;
export * from 'cesium';
gobe.module.ts
import { GlobeComponent } from './globe.component';
import { CommonModule } from '#angular/common';
import { NgModule } from '#angular/core';
import * as cesiumLib from './cesium.lib';
#NgModule({
declarations: [
GlobeComponent
],
imports: [
],
providers: [
{ provide: cesiumLib.cesiumToken, useValue: cesiumLib.cesium}
]
})
export class GlobeModule {}
globe.component.ts
import { cesium, cesiumToken } from './cesium.lib';
import { Component, Inject, OnInit, ViewEncapsulation, AfterViewInit, ViewChild, ElementRef } from '#angular/core';
#Component({
selector: 'app-globe',
templateUrl: './globe.component.html',
styleUrls: ['./globe.component.css'],
encapsulation: ViewEncapsulation.None
})
export class GlobeComponent implements OnInit, AfterViewInit {
constructor( #Inject(cesiumToken) private cesium: any) {
}
ngAfterViewInit() {
this.viewer = new Cesium.viewer('cesiumContainer')
}
}
globe.component.html
<div id="cesiumContainer"></div>
globe.component.css
#import url(../Build/Cesium/Widgets/widgets.css);
#cesiumContainer {
width: 100%;
height: 720px;
margin: 0; padding: 0;
overflow: hidden;
}
tsconfig.json
{
"compileOnSave": false,
"compilerOptions": {
"allowJs": true,
"outDir": "./dist/out-tsc",
"baseUrl": "src",
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es5",
"typeRoots": [
"node_modules/#types"
],
"lib": [
"es2016",
"dom"
]
}
}
.angular-cli.json snippet
"styles": [
"../node_modules/bootstrap/dist/css/bootstrap.min.css",
"../node_modules/bootstrap-switch/dist/css/bootstrap3/bootstrap-switch.min.css",
"styles.css",
"../node_modules/font-awesome/css/font-awesome.css",
"../node_modules/cesium/Build/Cesium/Widgets/widgets.css"
],
"scripts": [
"../node_modules/jquery/dist/jquery.min.js",
"../node_modules/bootstrap-switch/dist/js/bootstrap-switch.min.js",
"../node_modules/cesium/Build/Cesium/Cesium.js"
___UPDATE______
after making changes from #Justin Shwartzenberger
the compiler is successful but the page loads continuously until if fails... ideas? this occurs once I add the script and css to the .angular-cli.json file
In the .angular-cli.json file under the apps array first object add:
(to bring in the 3rd party css and js files needed)
"styles": [
"styles.css",
"../node_modules/cesium/Build/Cesium/Widgets/widgets.css"
],
"scripts": [
"../node_modules/cesium/Build/Cesium/Cesium.js"
],
In your cesium.lib.ts file just have:
(to create the InjectionToken only, everything else is global from that Cesium lib as it doesn't appear to be able to be imported)
import { InjectionToken } from '#angular/core';
export const cesiumToken = new InjectionToken('cesium');
In your typings.d.ts add:
(so TypeScript knows about the Cesium thing)
declare var Cesium: any;
In your app.module.ts set up:
(only need to bring in the token and then set its value to the Cesium global that the 3rd party lib creates)
import {cesiumToken} from './cesium.lib';
. . .
providers: [
{ provide: cesiumToken, useValue: Cesium }
],
. . .
And finally, in your app.component.ts:
(just need to get that token value injected into a class field and then use the API of it from there)
. . .
constructor( #Inject(cesiumToken) private cesium: any) {}
. . .
ngAfterViewInit() {
this.viewer = new this.cesium.Viewer('cesiumContainer');
}
. . .
You can go further with creating typings for the Cesium lib and encapsulating more from here, but this should get you up and working.

Call Angular Component method from an imported library

Here is my Angular component:
import { basketModule } from './wind'
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
constructor(){
}
ngOnInit(){
basketModule.init()
}
public dataMain(){
alert('hi')
}
}
Here is the wind.js file which is imported above.
export var basketModule = (function () {
return {
init: function(){
dataMain()
}
}
})();
When I run the above code, it returns an error that
core.es5.js:1020 ERROR ReferenceError: dataMain is not defined
How to access the Angular component method dataMain from the imported library ?
If you're using AngularCLI, you will need to add that file to the scripts section of the angular-cli.json file.
"scripts": [
// path to script here in quotes
],
And regardless of whether you're using angular cli, make sure the 'allowJs' flag in your tsconfig.json file is set to true.
{
"compilerOptions": {
"target": "es5",
"sourceMap": true,
"allowJS": true // this one
}
}
Then try importing the library in your component
import * as wind from './path/to/lib/wind.js'
Now you should be able to access that libraries functions using the 'wind' name
wind.dataMain();
If you want an A file methode in a B file so you should import A in B
But you should put your methode in a service and then import service in wind.js

Categories