Angular services with browserfy - javascript

I've using the angular fullstack generator for a long time and I recently updated it and noticed it no longer uses bower and that it uses require to get the modules in the client. My issue is that I can't understand how this works at all when I try to declare a service.
if I run, for example, yo angular-fullstack:service example it creates a example.service.js in the client/app/example path with the following code:
'use strict';
const angular = require('angular');
/*#ngInject*/
export function exampleService() {
// AngularJS will instantiate a singleton by calling "new" on this function
}
export default angular.module('sisaApp.example', [])
.service('example', exampleService)
.name;
Let's say that I want to use $http here. Do I pass it as a parameter to the function?
Apart from that, how do I go about injecting this service into a controller?
If I run yo angular-fullstack:route myroute and it generates th following controller file:
'use strict';
const angular = require('angular');
const uiRouter = require('angular-ui-router');
import routes from './myroute.routes';
export class MyrouteComponent {
/*#ngInject*/
constructor() {
this.message = 'Hello';
}
}
export default angular.module('sisaApp.myroute', [uiRouter])
.config(routes)
.component('myroute', {
template: require('./myroute.html'),
controller: MyrouteComponent,
controllerAs: 'myrouteCtrl'
})
.name;
Doing it the old was by passing it as a parameter into the constructor doesn't seem to be working for me.
Any help would be appreciated!
Thanks in advance!

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.

Why is my Angularjs Service not allowing me to call it?

I'm trying to make an Angular Service that houses common functions.
I bundled the code within my MVC app:
bundles.Add(new ScriptBundle("~/bundles/Angular")
.IncludeDirectory("~/app", "*.js", true));
And I checked in Developer Tools if it actually brought in my Common Folder with Common.js :
I added Common to the App :
var app = angular.module('app',
[
'JobCtrl',
'JobSvc',
'WebsiteCtrl',
'WebsiteSvc',
'myClientCtrl',
'ClientSvc',
'MediaCompanyCtrl',
'MediaCompanySvc',
'PageAlertSvc',
'ui.bootstrap',
'ui.bootstrap.tpls',
'Common'
]
);
and to the Controller:
angular.module('app', ['ui.bootstrap', 'ui.bootstrap.tpls'])
.controller('JobCtrl',
[
'JobService',
'WebsiteService',
'MediaCompanyService',
'ProductService',
'$scope',
'$uibModal',
'PageAlertService',
'Common',
function (JobService, WebsiteService, MediaCompanyService,
ProductService, $scope, $uibModal,PageAlertService, Common)
This is what my Common.js file looks like:
angular.module('app')
.service('Common', function () {
this.heyThere = function ()
{
console.log('Just wanted to say hey there')
};
});
Whenever it is called within my JobCtrl I get a Error: $injector:unpr
Unknown Provider.
Could anyone see what I may be doing wrong where it won't recognize my Common.js file? When I move Common.js to the Services folder and try calling it within my controller it works, but not when it is in my Common Folder. Makes no sense!
Thanks in advance!
That is simply because you are defining your app..twice!!!!
angular.module('app', []) // this is where you re-define your app
.service('Common', function () {
this.heyThere = function ()
{
console.log('Just wanted to say hey there')
};
});
should be:
angular.module('app')
.service('Common', function () {
this.heyThere = function ()
{
console.log('Just wanted to say hey there')
};
});
the module function has 2 modes.. with 2 arguments you are setting up your app.. with a single argument you just getting a reference to an existing app (which is already defined before that)
Please be careful when you use the declaration of a module. You are basically reassigning the app module to different instances.
angular.module('app', [dependencies]) //Constructs a module with dependencies
angular.module('app').service(...) //Associates the components (service)
//with the app module.

Calling module constant?

I have the following Angular module. How can i call for example APIHost from one of my controllers?
angular.module('configuration', [])
.constant('APIHost','http://api.com')
.constant('HostUrl','http://example.com')
.constant('SolutionName', 'MySite');
Constant is nothing but one kind of provider recipe.
You need to inject constant dependency inside your controller factory function, that's it.
app.controller('testCtrl', function($scope, APIHost){
console.log(APIHost)
})
Make sure your configuration module has been added to main module as dependency
to get use of constant's provider like below
var app = angular.module('app', ['configuration', 'otherdependency']);
app.controller( ... ) //here you can have configuration constant available
Like this, just like any service or factory.
I have also include structure for industry standard (kind of) from john papa's coding guidelines.
(function() {
'use strict';
angular
.module('configuration')
.controller('ctrlXYZ', ctrlXYZ);
//Just inject as you would inject a service or factory
ctrlXYZ.$inject = ['APIHost'];
/* #ngInject */
function ctrlXYZ(APIHost) {
var vm = this;
activate();
function activate() {
//Go crazy with APIHost
console.log(APIHost);
}
}
})();
Hope the helps!

Overwrite Controller in Angular

I'm trying to overwrite a controller during run-time in Angular 1.4.
I'm working with Angular and Webpack and my ultimate goal is to be able to reload a controller with HMR (hot module replacement). However, my roadblock right now is figuring out how to have the controller update properly.
So in the end if you'd had
angular.module('app').controller('HomeController', function() {
this.message = 'Hello World!';
});
Then you modified your controller while the webpack-dev-server is running to:
angular.module('app').controller('HomeController', function() {
this.message = 'Goodbye World!';
});
The controller within the current state should update appropriately. I do have logic already in place to reload the state ($state.transitionTo)
I am using UI router and Babel, although Babel should be fairly irrelevant since I'm not using half of the benefits (i.e. classes).
Anyone have any ideas?
I'm not sure how the rest of your project is setup, but presumably your webpack config has a single entry point. Therefore it makes sense to import your controllers / services etc into that entry point sort of like an index.
Controller:
// Main.js
export default function HomeController () {
this.message = 'Hello World!'
}
Index:
// entry.js
import angular from 'angular'
import HomeController from './controllers/HomeController.js'
angular
.module('app', [])
.controller('HomeController', HomeController)
My controller changes seem to be updating correctly in this starter project I made https://github.com/alex-wilmer/app-starter/tree/angular, though it's not HMR, just live reloading.
I figured out the solution for y'all that are interested. To make HMR work with controllers in angular, effectively rewriting the body of the controller function, i bound an init function to a long-lasting JavaScript object that is outside the module (aka file).
var mod = angular.module('app').controller('HomeController', function($interval) {
mod.initHomeController(this, $interval);
});
mod.initHomeController = function(vm, $interval) {
vm.message = 'Hello World!';
};

$injector unable to resolve required dependency

I'm trying to get into the habit of structuring my Angular projects following LIFT protocol (Locate, Identify, Flat, Try(Dry)) but I'm having some difficulty resolving dependencies from other files.
I have the following factory:
(function () {
'use strict';
angular
.module('CBPWidget', [])
.factory('apiManufacturers', apiManufacturers);
function apiManufacturers () {
function hello () {
return 'hello';
}
return {
hello: hello
};
}
})();
and the following controller:
(function () {
'use strict';
angular
.module('CBPWidget', [])
.controller('stepOneController', stepOneController);
stepOneController.$inject = ['$scope', 'apiManufacturers'];
function stepOneController ($scope, apiManufacturers) {
$scope.step = 'step1';
console.log(apiManufacturers.hello);
}
})();
and the following error is thrown:
Error: [$injector:unpr] Unknown provider: apiManufacturersProvider <- apiManufacturers <- stepOneController
My factory JS file is placed above the controller JS file in my HTML (which will be minified).
Any advice on where I'm going wrong would be greatly appreciated as I'm new to structuring projects this way.
Here you are creating CBPWidget module two times.
angular.module('CBPWidget',[]) is used for creating module and
angular.module('CBPWidget') is used for getting already created module.
so replace controller code with this :
(function () {
'use strict';
angular
.module('CBPWidget')//now you are getting CBPWidget module
.controller('stepOneController', stepOneController);
stepOneController.$inject = ['$scope', 'apiManufacturers'];
function stepOneController ($scope, apiManufacturers) {
$scope.step = 'step1';
console.log(apiManufacturers.hello);
}
})();
Your angular.module('CBPWidget', []) block code is redefining angular app, which was flushing apiManufacturers service associated with it, & it is defining controller in it. You should never do that, you should use existing module which was already defined.
Code
angular
.module('CBPWidget') //don't overide app here use existing
.controller('stepOneController', stepOneController);
From the documentation for AngularJS, you'll find that
.module('CBPWidget', [])
is different from
.module('CBPWidget')
The latter is what you need to refer to a module, the former is for defining one. In all cases except where you first define it, you should be using the latter form.

Categories