No provider for $scope! error when using a angular 1 directive in an angular 2 app - angularjs

I have an angular 2 App built with angular-cli and I need to use an angular 1 directive in one of my components (to re-use it from a different application). I followed the steps from:
https://angular.io/docs/ts/latest/guide/upgrade.html#!#using-angular-1-component-directives-from-angular-2-code
But now I got to this error and cannot get past it. I am using angular2.0.2 (I managed to build a hybrid app in the past with the beta version but it was an angular1 app and I used angular 2 components with downgrade function of the adapter).
In my app.module.ts I have:
import { UpgradeAdapter } from '#angular/upgrade';
const upgradeAdapter = new UpgradeAdapter(forwardRef(() => AppModule));
const HeroDetail = upgradeAdapter.upgradeNg1Component('heroDetail');
#NgModule({
imports: [
BrowserModule,
...
],
declarations: [
...
HeroDetail
]
})
export class AppModule { }
and my hero-detail.component.ts looks like this:
export const heroDetail = {
templateUrl: 'hero-detail.html',
controller: function() {
}
};
and my hero-detail.html looks like this:
<h2>Windstorm details!</h2>
I try to use the directive in another angular 2 component simply by adding in the template:
When I run ng serve, the application compiles fine but when I try to load the page I get the mentioned error.
Any suggestions on how I can move forward with this?

It seems you have incorrect bootstrap logic.
It's actually not quite obvious, make sure that:
you don't bootstrap any ng2 component with #NgModule({bootstrap:[ ... ]}). Instead, you should have empty ngDoBootstrap() { } method in your main module.
root template is ng1 template. I.e. in your index.html you should have only ng1 components or downgraded ng2 components. You can have ng2 component as a root, but you need to downgrade it first.
Official upgrade guide contains an example of DOM structure:
... which ensures that ng2 injectors have all required providers from ng1.

Related

Downgrade Angular 6 directive to Angular 1.7

I would like to downgrade an angular 6 directive defined something like this
#Directive({
selector: '[ad-host]',
})
export class AdDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}
to angular 1.7 but angular https://angular.io/api/upgrade/static module contains downgradeComponent only.
any suggestion how to do so.
the main problem that I am trying to solve is to achieve parent-child communication for children rendered using by the parent and as per https://blog.angular-university.io/angular-ng-content/ blog I can achieve by having inputRef directive.

Kylo UI - Create new typescript module

I have a doubt about the way to work with Kylo and UI structure.
According to the site http://kylo.readthedocs.io/en/v0.8.3/developer-guides/KyloDeveloperGuide.html?highlight=angular2 it says:
Most of the Kylo UI depends on AngularJS and AngularJS Material but a few parts have been upgraded to Angular 2 and Covalent. New plugins should be written in Typescript and use Angular 2 for future compatibility.
It says that new plugins should be written in angular2 and typescript, but all the examples and the core components are written in AngularJS.
One example is this one: https://github.com/Teradata/kylo/tree/master/samples/plugins/example-module/example-module-ui
I would like to know the Typescript and Angular2 alternative for that module, including routing, is there any live example ?
I've tested that Angular 2 does work if the code is added directly to the kylo-ui-app project but I don't think Kylo's plugin system supports Angular 2 right now.
The only difference from standard Angular 2 is that Kylo uses UI-Router instead of the Angular Router, but the syntax is very similar. From memory I think these are the steps I used:
1) Add a new route to routes.js:
{
name: 'contacts.**',
url: '/contacts',
loadChildren: './contacts/contacts.module#ContactsModule'
}
2) Add the child states to your module:
#NgModule({
imports: [ UIRouterModule.forChild({ states: CONTACTS_STATES }), /* ... and any other modules */ ],
declarations: [ ContactComponent, /* ... and any other components */ ],
providers: [ ContactsDataService ],
})
export class ContactsModule { }
3) The component for each state should be specified under views.content:
{
name: 'contacts',
url: '/contacts',
views: {
"content": {
component: ContactsComponent,
}
}
}

Enabling Hot Module Replacement with Angular 2 Upgraded App

We're using the upgrade adapter to setup an Angular 1/2 Hybrid app. We're bootstrapping the app like this (As described in the Angular docs https://angular.io/guide/upgrade):
class AppModule {
constructor(private upgrade: UpgradeModule) { }
ngDoBootstrap() {
this.upgrade.bootstrap(document.body, ['ngApp'], { strictDi: true });
}
}
For our HMR setup, we're using Angular CLI and following these instructions https://github.com/angular/angular-cli/blob/master/docs/documentation/stories/configure-hmr.md
The problem is that when HMR runs and tries running that bootstrap code again, it attempts to rebootstrap the Angular 1 app, which results in an error that the Angular 1 module has already been bootstrapped.
Trying to only conditionally run that code if angular 1 hasn't been bootstrapped also won't work, because while the component does refresh, it doesn't reload with the new updates.
We've resorted to something that seems to work, but is less than ideal, which is removing he angular 1 app element, and recreating it, so that the bootstrap doesn't error.
Something like this:
ngDoBootstrap() {
// reinitialize angular 1 app
var element = document.getElementById('ng-app');
let windowRef = <any>window;
if(!windowRef.appContents) {
windowRef.appContents = element.innerHTML;
} else {
document.body.removeChild(element);
element = document.createElement('div');
element.id = 'ng-app';
element.innerHTML = windowRef.appContents;
document.body.appendChild(element);
}
this.upgrade.bootstrap(element, ['ngApp'], { strictDi: true });
}
While a bit hacky, that at least seems to get the HMR working. However, because it is causing the entire angular 1 app to rebootstrap, rather than just refreshing the module that changed, this is only marginally faster than a simple livereload.
Is there any better way to rebootstrap a hybrid app to allow for HMR to work?

DefinitelyTyped: What does 'export = _;' means

I'm trying to use angular-material with a ng-metadata project and I run into some issues.
I use DefinitelyTyped for angular material and the first lines are:
declare module 'angular-material' {
var _: string;
export = _;
}
In my main.ts I try to import { ngMaterial } from 'angular-material';
then bootstrap( AppComponent, [ 'ngMaterial' ] ); but all I got is:
Error:(3, 10) TS2305: Module ''angular-material'' has no exported member 'ngMaterial'.
I don't know what I am doing wrong
When being used through ES6 or TypeScript, a common pattern that Angular modules follow is that they'll use their name as the default export. For example, one of the modules in my application looks like this:
const session = angular.module("smSession", [])
.service("session", SessionService)
.component("smLogin", Login)
.config(routes)
.run(loginRedirect);
export default session.name;
The reasoning behind this is that it makes the syntax for declaring an Angular module's dependencies cleaner; for example:
import angular from "angular";
import ngAnimate from "angular-animate";
import ngMaterial from "angular-material";
import uiRouter from "angular-ui-router";
let module = angular.module("myApp", [ ngAnimate, ngMaterial, uiRouter ]);
If they instead exported the entire module, you'd have to do this:
let module = angular.module("myApp", [ ngAnimate.name, ngMaterial.name, uiRouter.name ]);
So this is why the main module declaration for angular-material looks like it does - they're simply representing the fact that all you can import from the package is that one string representing the module's name. The rest of the type definitions are ambient - you can just use the angular.material namespace anywhere in your application without having to do an import.
EDIT: To clarify, here's the actual source of the file that gets imported when you import ngMaterial:
// Should already be required, here for clarity
require('angular');
// Load Angular and dependent libs
require('angular-animate');
require('angular-aria');
// Now load Angular Material
require('./angular-material');
// Export namespace
module.exports = 'ngMaterial';
Notice that require('./angular-material') doesn't return anything - that import effectively just runs the file that sets up the Angular module behind the scenes (effectively the same sort of code as in my examples). The only thing being exported from the module is the name.

How to inject upgraded Angular 1 service/factory to Angular 2 component in ES5?

I have an Angular1 service with name, say, 'myService'
I then upgraded it using the Angular2 UpgradeAdapter like this:
var upgradeAdapter = new ng.upgrade.UpgradeAdapter();
upgradeAdapter.upgradeNg1Provider('myService');
Now I need to inject it to the angular2 component. Official upgrade guide only has typescript version for now. In typescript you use #Inject anotation with the service name for it like so:
export class MyComponent {
constructor(#Inject('myService') service:MyService) {
...
}
}
What is the syntax for injecting a service by name using ES5?
To complete the pixelbits' answer, you need also define my service within the providers list either:
At the application level
document.addEventListener('DOMContentLoaded', function() {
ng.platform.browser.bootstrap(Cmp, [MyService]);
});
At the component level
var Cmp = ng.core.
Component({
selector: 'cmp',
providers: [ MyService ]
}).
(...)
Class({
constructor: [MyService, function(service) {
}]
});
It depends on what you want to do: share your service instance for all elements in the Angular2 application or per component.
This answers could give more details on dependency injection in Angular2 with ES5: Dependency Injection in Angular 2 with ES5.
Edit
In fact, there is a subtlety. IN the case of upgrade you need not to bootstrap using the ng.platform.browser.bootstrap function but the one from the upgrade object.
upgrade.bootstrap(document.body, ['heroApp']);
Where heroApp is the Angular1 module containing services and factories I want to use in the Angular2 application.
In fact, when you call the upgradeNg1Provider method on the upgrade, corresponding providers are registered within its associated injector. This means that you don't need to specify them at described above.
So you simply need to do that where you want to inject:
(...)
Class({
constructor: [ ng.core.Inject('customService'),
ng.core.Inject('customFactory'),
function(cService, cFactory) {
}]
});
Miško Hevery provides a great plunkr for this: http://plnkr.co/edit/yMjghOFhFWuY8G1fVIEg?p=preview.
Hope it helps you,
Thierry
Use the array notation for constructor DI of services:
.Class({
constructor: [MyService, function (service) {
...
}],

Resources