Cannot use Angular 1 directive in Angular 2 app using upgradeNg1Component() - javascript

I am trying to make a hybrid angular app using Angular 1 & 2. I have a simple angular 1 directive:
var sample = angular.module('interestApp',[]);
sample.directive('myComp', function() {
return {
scope:{},
restrict: 'E',
template: '<div>My name is </div>'
}
});
I want to use this directive in my angular 2 app. So I convert it using upgradeNg1Component() like this:
const upgradeAdapter: UpgradeAdapter = new UpgradeAdapter();
upgradeAdapter.bootstrap(document.body, ['interestApp']);
#Component({
selector: 'angular2',
template: '<myComp></myComp>',
directives: [upgradeAdapter.upgradeNg1Component('myComp')]
})
I am getting this error when I run it though:
EXCEPTION: No provider for $scope! (function (scope, elementRef)
Can anyone help with this? Or if anyone knows any other way of using angular 1 directive in angular 2 application that would be really helpful.

You code looks good so far, but it seems like there's one part missing. While you upgrade your ng1 component to use it in your Angular 2 component with the selector angular2 (which I would suggest to change), you don't downgrade that Angular 2 component again to actually use it in your bootstrapped Angular 1 application.
That's something you always have to do as long as you're in the process of upgrading your app. In order to get your Angular 2 component instantiated, you need to use it in your A1 as a directive and therefore it needs to be downgraded. So you'd need to do sth. like:
#Component({
selector: 'my-ng2-component',
template: '<myComp></myComp>',
directives: [upgradeAdapter.upgradeNg1Component('myComp')]
})
class MyNg2Component {
}
angular.module('interestApp', [])
.directive('myComponent', {...})
.directive('myNg2Component', upgradeAdapter.downgradeNg2Component(MyNg2Component);
upgradeAdapter.bootstrap(document.body, ['interestApp']);
As soon as you're done with upgrading you can remove all the Angular 1 specifc parts and the downgrading mechanisms.

In your HTML, are you referencing both versions of the component/directive?
For me, this would occur any time that I referenced the Angular2 version of a component that was both:
downgraded to AngularJS
referenced an upgraded AngularJS directive in its template.
In my case, I defined <ng2-component> for NG2 and <downgraded-ng2-component> for NG1 and was referencing them both in my HTML. This resulted in the error.
I found that in my use of Angular 2.0.0-beta.0 that I have to avoid using the original Angular2 component when it met both cases (1) and (2) from above.

Related

Using downgradeModule in conjunction with downgradeInjectable in an angular / angularjs hybrid application results in error

With angular 5.0 the upgrade module now has the option of using downgradeModule which runs angularjs outside of the angular zone. While experimenting with this I have run into a problem with using downgradeInjectable.
I am receiving the error:
Uncaught Error: Trying to get the Angular injector before bootstrapping an Angular module.
Bootstrapping angular in angular js works fine
import 'zone.js/dist/zone.js';
import * as angular from 'angular';
/**
* Angular bootstrapping
*/
import { platformBrowserDynamic } from '#angular/platform-browser-dynamic';
import { decorateModuleRef } from 'src/environment';
import { AppModule } from 'src/app/app.module';
import { downgradeModule } from '#angular/upgrade/static';
export const bootstrapFn = ( extraProviders ) => {
const platformRef = platformBrowserDynamic( extraProviders );
return platformRef
.bootstrapModule( AppModule )
.then( decorateModuleRef );
};
angular.module( 'app.bootstrap', [
downgradeModule( bootstrapFn ),
] );
However...
Since the bootstrapping takes place after angularjs has been initialized I can no longer get the downgrade injectable working.
Service to be downgraded
import { Injectable, Inject, OnInit } from '#angular/core';
#Injectable()
export class MobileService implements OnInit{
constructor(
#Inject( 'angularjsDependency1' ) public angularjsDependency1 : any,
#Inject( 'angularjsDependency2' ) public angularjsDependency2 : any,
) {}
}
Downgrade injectable attempt
import * as angular from 'angular';
import { downgradeInjectable } from '#angular/upgrade/static';
import { MyService } from 'src/services/myService/myService';
export const myServiceDowngraded = angular.module( 'services.mobileService', [
angularjsDependency1,
angularjsDependency2,
] )
.factory(
'mobileService',
downgradeInjectable( MyService ),
).name;
When "downgradeInjectable( MyService ) runs the angular injector is not available yet since angular hasn't been bootstrapped. Hence the error:
Uncaught Error: Trying to get the Angular injector before bootstrapping an Angular module.
Does anyone have an idea how I might fix this?
Answers in this thread helped me find a solution, but none contains the holy grail:
Creating a service-boostrap component aside the app's code does not work, because Angular is loaded asynchronously, unlike AngularJS. This gives the same error Trying to get the Angular injector before bootstrapping an Angular module.
Creating a service-bootstrap component wrapping the AngularJS code kind of worked, but then I experienced issues with change detection inside Angular composants, as described in this issue on github.
In the github issue, someone suggested to edit #angular/upgrade source code to change a false to true to force components to be created in the Zone. But in this case it seems to cause performance issues (it seemed to launch ngZone's code multiple times on user events)
In order for the app to work correctly, I needed :
Not to have ng components containing AngularJS components containing Angular components. We need to only have AngularJS containing Angular components.
Make sure that AngularJS components using Angular services are created after a first angular component, named service-bootstrap
To acheive this, I created a slightly modified service-bootstrap component:
import { Component, Output, EventEmitter, AfterViewInit } from "#angular/core";
#Component({
selector: 'service-bootstrap',
template: ``
})
export class ServiceBootstrapComponent implements AfterViewInit{
#Output()
public initialized: EventEmitter<void> = new EventEmitter();
public ngAfterViewInit(){
this.initialized.emit();
}
}
Declared this component as entryComponent in the Angular module and called downgradeComponent to register it in AngularJS:
import { downgradeModule, downgradeInjectable, downgradeComponent } from '#angular/upgrade/static';
const bootstrapFn = (extraProviders: StaticProvider[]) => {
const platformRef = platformBrowserDynamic(extraProviders);
return platformRef.bootstrapModule(AppModule);
};
const downgradedModule = downgradeModule(bootstrapFn);
const app = angular.module('ngApp', [
downgradedModule,
'app'
]);
app.directive('serviceBootstrap', downgradeComponent({ component: ServiceBootstrapComponent }));
Then (and the magic happens here), I created a new AngularJS component:
angular.module("app")
.directive('ng1App', ng1AppDirective);
function ng1AppDirective(){
return {
template: `
<service-bootstrap (initialized)="onInit()"></service-bootstrap>
<section ng-if="initialized">
<!-- Your previous app's code here -->
</section>
`,
controller: ng1AppController,
scope: {},
};
}
ng1AppController.$inject = ['$scope'];
function ng1AppController($scope){
$scope.onInit = onInit;
$scope.initialized = false;
function onInit(){
$scope.initialized = true;
}
}
Then, my index.html only referenced this component
<body>
<ng1-app></ng1-app>
</body>
With this approach, I'm not nesting AngularJS components inside Angular components (which breaks change detection in Angular components), and still I ensure that a first Angular component is loaded before accessing the Angular providers.
Note: The answer below follows the convention of calling angular 1.x as angularjs and all angular 2+ versions as simply angular.
Expanding on JGoodgive's answer above, basically, if you're using downgradeModule, then angular module is bootstrapped lazily by angularjs when it needs to render the first angular component. Until then, since the angular module isn't initialised, if you are accessing any angular services inside angularjs using downgradeInjectable, those services aren't available too.
The workaround is to force bootstrapping of the angular module as early as possible. For this, a simple component is needed:
import {Component} from '#angular/core';
#Component({
selector: 'service-bootstrap',
template: ''
})
export class ServiceBootstrapComponent {}
This component doesn't do anything. Now, we declare this component in the top level angular module.
#NgModule({
// ...providers, imports etc.
declarations: [
// ... existing declarations
ServiceBootstrapComponent
],
entryComponents: [
// ... existing entry components
ServiceBootstrapComponent
]
})
export class MyAngularModule {}
Next, we also need to add a downgraded version of this component to angularjs module. (I added this to the top level angularjs module I had)
angular.module('MyAngularJSModule', [
// ...existing imports
])
.directive(
'serviceBootstrap',
downgradeComponent({ component: ServiceBootstrapComponent }) as angular.IDirectiveFactory
)
Finally, we throw in this component in our index.html.
<body>
<service-bootstrap></service-bootstrap>
<!-- existing body contents -->
</body>
When angularjs finds that component in the markup, it needs to initialise angular module to be able to render that component. The intended side effect of this is that the providers etc. also get initialised and are available to be used with downgradeInjectable, which can be used normally.
This was pointed out to me in an angular github thread.
https://github.com/angular/angular/issues/16491#issuecomment-343021511
George Kalpakas's response:
Just to be clear:
You can use downgradeInjectable() with downgradeModule(), but there are certain limitations. In particular, you cannot try to inject a downgraded injectable until Angular has been bootstrapped. And Angular is bootstrapped (asynchronously) the first time a downgraded component is being rendered. So, you can only safely use a downgraded service inside a downgraded component (i.e. inside upgraded AngularJS components).
I know this is limiting enough that you might decide to not use downgradeInjectable() at all - just wanted to make it more clear what you can and can't do.
Note that the equivalent limitation is true when using an upgraded injectable with UpgradeModule: You cannot use it until AngularJS has been bootstrapped. This limitation usually goes unnoticed though, because AngularJS is usually bootstrapped in the Angular module's ngDoBootstrap() method and AngularJS (unlike Angular) bootstraps synchronously.
I had the same issue, and the reasons are explained in the above answer.
I fixed this by dynamically injecting the downgraded angular service using $injector.
Steps
Register your downgraded service to angularjs module
angular.module('moduleName', dependencies)
angular.factory('service', downgradeInjectable(Service));
Inject $injector to your controller and use this to get the downgraded service
const service = this.$injector.get('service');
service.method();
I had the same issue and it sucked up several hours before finding this.
My workaround was to create a ServiceBootstrapComponent that does nothing but injects all the services that we need to downgrade.
I then downgrade that component, mark it as en entry in #NgModule and add it to index.html.
Works for me.
I was getting the same error in our hybrid app. We are using the following versions:
AngularJS 1.7.x
Angular 7.3.x
As mentioned in this answer, I also used a dummy component called <ng2-bootstrap> to force boostrapping of Angular. And then, I created an AngularJS service which checks if Angular has been bootstrapped:
// tslint:disable: max-line-length
/**
* This service can be used in cases where Angular fails with following error message:
*
* `Error: Trying to get the Angular injector before bootstrapping the corresponding Angular module.`
*
* Above error occurs because of how `downgradeModule` works.
*/
/*#ngInject*/
export class Ng2BootstrapDetectionService {
private bootstrapDone = false;
constructor(private $q: ng.IQService) {}
public whenBootstrapDone(): ng.IPromise<void> {
if (this.bootstrapDone) {
return this.$q.resolve();
}
const deferred = this.$q.defer<void>();
angular.element(document).ready(() => {
const intervalId = setInterval(() => {
const el = document.querySelector('ng2-bootstrap');
if (el && el.outerHTML.includes('ng-version=')) {
this.bootstrapDone = true;
clearInterval(intervalId);
deferred.resolve();
}
}, 500);
});
return deferred.promise;
}
}
Ng2BootstrapDetectionService can be used like below:
import {NotificationService} from 'ng2-app/notification.service';
// This can be used in cases where you get following error:
// `Error: Trying to get the Angular injector before bootstrapping the corresponding Angular module.`
// You will need access to following
// $injector: AngularJS Injector
// Ng2BootstrapDetectionService: our custom service to check bootsrap completion
this.Ng2BootstrapDetectionService
.whenBootstrapDone()
.then(() => {
const notificationService = this.$injector
.get<NotificationService>('ng2NotificationService');
notificationService.notify('my message!');
});
You can find more details about this solution at the end of this blog post.

How to use CachedResourceLoader as an equivalent mechanism to $templateCache in Angular2?

I know there are 2 similar questions about this but with no solution for any of them.
So I found this issue in Angular repo where they ask for the same, i.e an alternative for templateCache in Angular 2 but they close it saying you can use CachedResourceLoader.
So my question is how to use this CachedResourceLoader to replace the templateCache, I have been searching on google about this but couldn't find any related content so maybe I am not pointing to the right direction or I missed something.
The answer to this question could be a valid answer for the other 2 similar questions.
Code example for the functionality that templateCache provided in AngularJS:
Adding:
var myApp = angular.module('myApp', []);
myApp.run(function($templateCache) {
$templateCache.put('templateId.html', 'This is the content of the template');
});
Retrieval via $templateCache:
$templateCache.get('templateId.html')
Or retrieval:
myApp.component('myComponent', {
templateUrl: 'templateId.html'
});
CachedResourceLoader is existing yet undocumented Angular 2+ substitution for AngularJS $templateCache:
An implementation of ResourceLoader that uses a template cache to
avoid doing an actual ResourceLoader.
The template cache
needs to be built and loaded into window.$templateCache via a
separate mechanism.
It is supposed to work by providing ResourceLoader provider:
{provide: ResourceLoader, useClass: CachedResourceLoader}
Which was already defined in existing RESOURCE_CACHE_PROVIDER export.
And window.$templateCache is supposed to contain pairs of URLs and responses.
Since ResourceLoader should be specified before compilation, it should be provided not in application module but in compiler options.
Here is an example:
import {RESOURCE_CACHE_PROVIDER} from '#angular/platform-browser-dynamic';
import {COMPILER_OPTIONS} from '#angular/core';
window['$templateCache'] = { 'app.html': `...`};
platformBrowserDynamic({
provide: COMPILER_OPTIONS,
useValue: { providers: [RESOURCE_CACHE_PROVIDER] },
multi: true
}).bootstrapModule(AppModule)
As opposed to AngularJS $templateCache, CachedResourceLoader doesn't allow to make requests for missing templates. This is desirable behaviour most of the time. If it has to be changed, a custom implementation that extends default ResourceLoader implementation can be used instead.

Downgarde angular 2 directive or at least being able to use it in angular js

I'm trying to use my ng2 directive in angularjs, but cannot get it to work. I've seen that downgradeComponent uses restric: 'E' for the angularjs directive, which means it's restricted to elements. Anyone knows how to deal with this problem?
I've tried simply using my directive in angular js, and it didn't work.
I'm using UpgradeModule for hybrid bootstraping.
this is my directive.
#Directive({
selector: '[test-directive]'
})
export class TestDirective implements OnInit {
#Input("test-directive") testDirective: string;
constructor(private el: ElementRef, private renderer: Renderer) {
}
ngOnInit() {
console.log("color is = " + this.testDirective);
this.renderer.setElementStyle(this.el.nativeElement, 'background-color', this.testDirective);
}
}
I've tried using in AngularJS as an attribute (trying using it as [test-directive] and as test-directive.
Am I missing something here? Because I've searched the web and didn't found a lot of information regarding this problem.
As I can see, in upgradeAdapter gives you only downgradeNg2Provider and downgradeNg2Component and indeed, downgradeNg2Component always uses "E". (https://github.com/angular/angular/blob/master/packages/upgrade/src/common/downgrade_component.ts#L70)
Take a look on Downgrade NG2 Directive to AngularJS. I haven't tested it, but author of the question says, that there is no need to downgrade directive and you can use it out of the box.

Angular 2 lazy loading techniques

I already wrote a big application with Angular 1 and requireJS with AMD for lazy loading and structuring. The application don't use routes but parts of the application like HTML, css and Javascript (angular modules) are lazy loaded.
Now I want to change to Angular 2 and I am looking for the best lazy loading technique for HTML, css and JS (angular) content which doesn't depends on routes and which doesn't depends on thousands of different javascript frameworks.
So lazy loading route components seems to be quite simple:
http://blog.mgechev.com/2015/09/30/lazy-loading-components-routes-services-router-angular-2
but how would you accomplish that scenario without routes?
Would you recommend something like webpack, or should I keep requireJS? Is there something like OClazyload for angular 2? Or does it work somehow with Angular 2 even without any frameworks?
I'm a friend of "keep it simple" and I really would like to keep it as small and simple as possible.
Thanks!
Angular 2 is based on web components. The easiest way ("keep it simple" as you said) is using routes and components.
You can also load components simply by using directives in your html. for example:
#Component({
selector: 'my-component', // directive name
templateUrl: './app/my.component.html',
directives: []
})
export class MyComponent {}
#Component({
selector: 'root-component', // directive name
directives: [MyComponent],
template: '<my-component></my-component>',
})
export class myComponent {}
if you modify your template to include <my-component> dynamically it will load the component dynamically. This is not a best practice.
there is a dynamic component loader for angular 2, but that is not as simple as using routes or directives. it will create an instance of a Component and attache it to a View Container located inside of the Component View of another Component instance.
with it you can use:
#Component({
selector: 'child-component',
template: 'Child'
})
class ChildComponent {
}
#Component({
selector: 'my-app',
template: 'Parent (<child id="child"></child>)'
})
class MyApp {
constructor(dcl: DynamicComponentLoader, injector: Injector) {
dcl.loadAsRoot(ChildComponent, '#child', injector);
}
}
bootstrap(MyApp);
The resulting DOM:
<my-app>
Parent (
<child id="child">Child</child>
)
</my-app>
There is another option (look at angular2 link above) in which you can optionally provide providers to configure the Injector provisioned for this Component Instance.
Hope this helps.
With angular 2 Latest version we can use loadchildren property to perform lazy loading.
For example :
{
path: 'Customer',
loadChildren: './customer.module#Customer2Module?chunkName=Customer'
},
In this above example I am using webpack Bundling(angular 2 router loader) + Anguar 2 Routing to lazy load the modules.
https://medium.com/#daviddentoom/angular-2-lazy-loading-with-webpack-d25fe71c29c1#.582uw0wac
Lets assume we have two pages in our application, “home” and “about”. Some people might never reach the about page, so it makes sense to only serve the load of the about page to people that actually need it. This is where we will use lazy loading.

Changing state is causing multiple instances of babel/polyfill?

I'm using Meteor, Angular and Ionic. I'm having a hard time changing from state A to state B as I keep getting the error 'only one instance of babel/polyfill is allowed'. I tried cleaning up the following code as much as possible. Note that I can change to other states successfully.
Here's my routes.js file:
.state('create-group', {
url:'/create-group:/:buddyId',
templateUrl: 'client/templates/create-group.ng.html',
controller: 'createGroupCtrl'
})
Here's my State A template:
<ion-item ng-repeat="buddy in buddies" ng-click="createGroup({{buddy}})">
</ion-item>
Here's my State A controller:
angular
.module('app')
.controller('whosDownCtrl', whosDownCtrl);
function whosDownCtrl ($scope, $state, $ionicScrollDelegate, $timeout, $meteor) {
$scope.createGroup = createGroup;
function createGroup(buddy) {
$state.go('create-group', { buddyId: buddy._id});
}
};
Here's my State B controller:
angular
.module('app')
.controller('createGroupCtrl', createGroupCtrl);
function createGroupCtrl ($scope, $stateParams) {
var clickedUser = $stateParams.buddyId;
}
Can anyone see what i'm doing wrong?
Did you manually add a babel/ecmascript package to meteor?
Check your .meteor/packages file and your .meteor/versions file because that error means there are multiple conflicting versions of babel added to your project.
And since all babel related meteor packages are independent wrappers, it is very likely to get such conflict.
Remove the "excess" babel packages and you'll be good to go.
I know this is very old question but I landed here while analysing my issue. Posting here my learning as it may help others.
I was also getting the same error in my project. It was happening because there were issues with correct template and controller resolution.
Here is a related github issue that helped in clarifying: https://github.com/Urigo/angular-meteor/issues/870
Use hardcoded template instead of templateUrl to analyse the issue properly.

Categories