reset myApp into body by angular.bootstrap - angularjs

I have a problem.
I wants design multiple modules, each module will config route for itself. And before login I just wants load 3 modules (with 3 route), and after login I continue load 3 module (with 3 route). So I need re-config myApp to add new routes, then re-set into body to apply myApp.
And my solution is:
I create mainModule is myApp, and i config for it then I set into body by angular.bootstrap
angular.element(document).ready(function() {
angular.bootstrap(document.body, ["myApp"]);
});
Then, I need re-config for myApp and I set again.
angular.element(document).ready(function() {
angular.bootstrap(document.body, ["myApp"], true);
});
An error occurs
[ng:btstrpd] App Already Bootstrapped with this Element '<body cz-shortcut-listen="true" class="ng-scope">'(…)
How can I reset myApp into body or any solution ???
Thanks,

While lazy loading is not among listed features in Angular, some of the things which are intrinsic to config phase can be performed at run-time (while they cannot be recommended and belong to 'use at your own risk' category).
When provider is being used to configure the service before its instantiation, most likely (it depends on service implementation) it can be used to configure it after it was instantiated, e.g.
app.config(function ($routeProvider, $provide) {
// now $routeProvider is available for injection during both config and run phases
$provide.constant('$routeProvider', $routeProvider);
});
app.run(function ($routeProvider, $location) {
$routeProvider.when('/brand-new-route', { ... });
$location.path('/brand-new-route');
});
More of this injector trick here.
This method isn't forbidden but relies on current service implementation and undocumented behaviour, so it has to be either tested thoroughly or should be avoided at all.

Related

Set app.config within a controller

So, I have my app.controller and inside of it I have a request. When request is successful, I want to set retrieved data to some $provider property, i.e.
app.controller('someCtrl', function(){
app.config(function($someProvider){
$someProvider.property = response.data;
$someProvider.function(response.data.status);
})
})
I'm trying to set this within controller, but it does nothing. Any tips, guys? :)
if default injecting doesn't do the job
maybe store your provider at config time to some other variable and use this var in controller later
but angular says:
During application bootstrap, before Angular goes off creating all services, it configures and instantiates all providers. We call this
the configuration phase of the application life-cycle. During this
phase, services aren't accessible because they haven't been created
yet.
Once the configuration phase is over, interaction with providers is
disallowed and the process of creating services starts. We call this
part of the application life-cycle the run phase.
so i don't know if this will work
Newly added config blocks aren't executed after the ending of config phase, the same applies to other app methods because they result in additional config blocks internally.
It is possible with:
app.config(($provide, $someProvider) => {
$provide.value('$someProvider', $someProvider);
});
app.controller('someCtrl', ($someProvider) => {
$someProvider...
});
This voids the warranty and may indicate XY problem that should be solved the other way, depending on what $someProvider is and how it functions.
If $some is injected and used before setting $someProvider, this will result in race condition. If $some was changed at some point to not watch for $someProvider properties after instantiation, this will result in breaking changes without notice that will make the tests fail.
It is acceptable for badly designed third-party services that don't cause ill effects when being treated like that. And as any other hack, it should be thoroughly covered with tests.
Your question comes down to how can I postpone angular's bootstrap process until I have some async data.
For the async part, you can use (or do) whatever you want, it doesn't really matter. Fun fact: You can even use $http before bootstrapping angular just by getting it via angular.injector(["ng"]).get("$http");.
This being said, when you have all your async data, all that's left to do is to bootstrap angular. This can be achieved via angular.bootstrap - source.
Here's a working example in which I asynchronously attach a controller (remember, you can do whatever you want: attach various constants, config blocks, etc). I've used setTimeout for simplicity's sake.
// Initial Angular Code
angular.module('myApp', []);
// Async function. I've used setTimeout for simplicity's sake
setTimeout(function() {
angular
.module('myApp')
.controller('MyCtrl', ['$scope',
function($scope) {
$scope.value = 'Angular has started!';
}
]);
// Boostrap AngularJS
angular.bootstrap(document.getElementById('boostrap-me'), ['myApp']);
}, 1000);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>
<div id="boostrap-me" ng-controller="MyCtrl">
<div ng-bind="value">Angular hasn't yet bootstrapped as we're waiting for some async data!</div>
</div>

How to inject '$urlRouterProvider' into factory?

I have followed this post How inject $stateProvider in angular application? and I have managed to figure out how to use '$stateprovider' but now I have issue with the '$urlRouterProvider'.
Does that mean I can't inject '$urlRouterProvider' into controller either and it should be only injected into config?
I highly appreciate any help any help on this issue.
There is a very narrowed snippet of the $urlRouterProvider code:
$UrlRouterProvider.$inject = ['$locationProvider', '$urlMatcherFactoryProvider'];
function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
// I. config
// these are CONFIGURATION methods
// we can access in .config() phase
....
this.rule = function (rule) {
...
}
...
this.when = function (what, handler) {
...
}
...
// II. application run
// this is the service/factory/configured provider
// injected in the .run() phase via IoC
this.$get = $get;
$get.$inject = ['$location', '$rootScope', '$injector', '$browser'];
function $get( $location, $rootScope, $injector, $browser) {
...
return {
sync: function() {
...
},
listen: function() {
...
}
And this is the answer. In the config we do have access to configuration methods of the provider $urlRouterProvider. While later, in phase of application run, our services/factories are via IoC provided with the result of the configured $get()
For more details, see:
Configuring Providers
You may be wondering why anyone would bother to set up a full-fledged provider with the provide method if factory, value, etc. are so much easier. The answer is that providers allow a lot of configuration. We've already mentioned that when you create a service via the provider (or any of the shortcuts Angular gives you), you create a new provider that defines how that service is constructed. What I didn't mention is that these providers can be injected into config sections of your application so you can interact with them!
First, Angular runs your application in two-phases--the config and run phases. The config phase, as we've seen, is where you can set up any providers as necessary. This is also where directives, controllers, filters, and the like get set up. The run phase, as you might guess, is where Angular actually compiles your DOM and starts up your app.
In case, you need to access $stateProvider or $routeProvider configuration even later... there is a way... Check this plunker. Can hardly say that this is the angular way... but it is working. Check it here
AngularJS - UI-router - How to configure dynamic views
http://plnkr.co/edit/I4AGHa3xgfv8WUMotxNL?p=preview

registerModule with dependencies

I'm building an app with MEAN.JS and I'm trying to use a controller from another module. I've found that I can do this with dependency injection:
angular.module(‘MyModule’, [‘Dependency’]);
But the way modules are created in MEAN.JS is:
ApplicationConfiguration.registerModule('MyModule');
And I can't just pass a second parameter to registerModule. So, how should I do this? Do I have to use both methods? Am I doing it wrong?
Example
I want to add a new model: Campaign. Campaigns are created by admins only, but can be seen by the "campaign owner" (another User). The create campaign form should list available Users, so the admin can select the one that's going to be the "owner" of that Campaign.
The problem is, the create campaign form is controlled by CampaignsController, how can I list Users? I've used another controller (UsersController) and thats the problem, it is undefined because we are in the Campaign module.
EDIT:
The problem was grunt autorestarting the app incorrectly:
I moved the controller from one module (folder) to another, but grunt was still trying to load it from the old path, and thus failing: Controller not found. I thought the problem was dependency injection, but you only have to close grunt (Ctrl+C) and run it again. Problem solved.
Anyways, thanks for the answer #gilBirman cause it is correct: there is no need to inject anything with MEANJS.
MEAN.js makes all the modules registered via registerModule available to all other modules in your app by adding it as a dependency to the main app called mean. Here's the part of the MEAN.js source code that does this:
var applicationModuleName = 'mean';
....
// Add a new vertical module
var registerModule = function(moduleName) {
// Create angular module
angular.module(moduleName, []);
// Add the module to the AngularJS configuration file
angular.module(applicationModuleName).requires.push(moduleName);
};
So you're on the right track, however it sounds like you are trying to inject a controller. However, in angular, controllers are not injectable. You can inject services, factories, values, and constants.
First create your own module for example:
angular.module('app.controllers', []) - angular module with controllers.
then add controller to that module:
angular.module('app.controllers', [])
.controller('dashboard.admin.account.controller', ['$scope', ($scope) { .... }]);
then create global module which will bind to your markup:
angular.module('app', [
'app.controllers'
'ui.router',
'ngAnimate'
]);
then bootstrap your global module to markup:
domReady(function () {
angular.bootstrap(document, ['app']);
});
Now you can use your controller.

Loading Modules in Angular

I'm new to AngularJS. In my efforts to learn, I've relied on the AngularJS tutorials. Now, I'm trying to build an app using the AngularSeed project template. I'm trying to make my project as modular as possible. For that reason, my controllers are broken out into several files. Currently, I have the following:
/app
index.html
login.html
home.html
javascript
app.js
loginCtrl.js
homeCtrl.js
my app.js file has the following:
'use strict';
var app = angular.module('site', ['ngRoute']);
app.config(function($routeProvider, $locationProvider) {
$locationProvider.html5Mode(true);
$routeProvider.when('/login', {templateUrl: 'app/login.html', controller:'loginCtrl'});
$routeProvider.when('/home', {templateUrl: 'app/home.html', controller:'homeCtrl'});
$routeProvider.otherwise({redirectTo: '/login'});
});
my loginCtrl.js file is very basic at the moment. It only has:
'use strict';
app.controller('loginCtrl',
function loginCtrl($scope) {
}
);
My homeCtrl.js is almost the same, except for the name. It looks like the following:
'use strict';
app.controller('homeCtrl',
function homeCtrl($scope) {
}
);
My index.html file is the angularSeed index-async.html file. However, when I load the dependencies, I have the following:
// load all of the dependencies asynchronously.
$script([
'http://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.3/angular.min.js',
'http://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.3/angular-route.min.js',
'javascript/app.js',
'javascript/loginCtrl.js',
'javascript/homeCtrl.js'
], function() {
// when all is done, execute bootstrap angular application
angular.bootstrap(document, ['site']);
});
My problem is, sometimes my app works and sometimes it doesn't. It's almost like something gets loaded before something else.
Occasionally, I receive this error. Other times, I get an error in the console window that says: 'Uncaught ReferenceError: app is not defined' in loginCtrl.js. Sometimes it happens with homeCtrl.js.
What am I doing wrong? It feels like I need to have my controllers in a module and pass that module in my app.config in the app.js file. However, a) I'm not sure if that allowed and b) I'm not sure how to do it.
angular-seed has had problems with an out of date loader.js (which handles $script) (issue: https://github.com/angular/angular-seed/pull/111). And this causes exactly the problem you're seeing.
Try getting the most recent loader.js (https://github.com/angular/angular.js/blob/master/src/loader.js)
And grab prefix https://github.com/angular/angular.js/blob/master/src/loader.prefix
and suffix https://github.com/angular/angular.js/blob/master/src/loader.suffix
Or you could use the regular <script> tag approach and just make sure everything is included in the right order (as #Chandermani details)
Well I am not sure how $script works, but if it's job is to load the js files async, then you cannot determine the order in which the files are loaded and executed.
In your case you some scripts have to be loaded before others. So the angular* scripts need to be loaded and executed before your app scripts are loaded. I think the order should be
Angular.min.js
angular-route.min.js
Then your script app.js
Then your controller\directives\services scripts in any order.

re-open and add dependencies to an already bootstrapped application

Is there a way to inject a late dependency to an already bootstrapped angular module? Here's what I mean:
Say that I have a site-wide angular app, defined as:
// in app.js
var App = angular.module("App", []);
And in every page:
<html ng-app="App">
Later on, I'm reopening the app to add logic based on the needs of the current page:
// in reports.js
var App = angular.module("App")
App.controller("ReportsController", ['$scope', function($scope) {
// .. reports controller code
}])
Now, say that one of those on-demand bits of logic also requires their own dependencies (like ngTouch, ngAnimate, ngResource, etc). How can I attach them to the base App? This doesn't seem to work:
// in reports.js
var App = angular.module("App", ['ui.event', 'ngResource']); // <-- raise error when App was already bootstrapped
I realize I can do everything in advance, i.e -
// in app.js
var App = angular.module("App", ['ui.event', 'ngResource', 'ngAnimate', ...]);
Or define every module on its own and then inject everything into the main app (see here for more):
// in reports.js
angular.module("Reports", ['ui.event', 'ngResource'])
.controller("ReportsController", ['$scope', function($scope) {
// .. reports controller code
}])
// in home.js
angular.module("Home", ['ngAnimate'])
.controller("HomeController", ['$scope', '$http', function($scope, $http){
// ...
}])
// in app.js, loaded last into the page (different for every page that varies in dependencies)
var App = angular.module("App", ['Reports', 'Home'])
But this will require I initialize the App everytime with the current page's dependencies.
I prefer to include the basic app.js in every page and simply introduce the required extensions to each page (reports.js, home.js, etc), without having to revise the bootstrapping logic everytime I add or remove something.
Is there a way to introduce dependencies when the App is already bootstrapped? What is considered the idiomatic way (or ways) to do this? I'm leaning towards the latter solution, but wanted to see if the way I described could also be done. thanks.
I solved it like this:
reference the app again:
var app = angular.module('app');
then push your new requirements to the requirements array:
app.requires.push('newDependency');
Simple...
Get an instance of the module using the getter like this:
var app = angular.module("App");
Then add to the "requires" collection like this:
app.requires[app.requires.length] = "ngResource";
Anyway, this worked for me. GOOD LUCK!
According to this proposal on the Angular JS google group this functionality does not exist as of this moment. Hopefully the core team decides to add this functionality, could use it myself.
If you wish to add multiple dependencies at once, you can pass them in push as follows:
<script>
var app = angular.module('appName');
app.requires.push('dependencyCtrl1', 'dependencyService1');
</script>
I realize that this is an old question, however, no working answer has yet been provided, so I decided to share how I solved it.
The solution requires forking Angular, so you can't use CDN anymore. However the modification is very small, so I am surprised why this feature doesn't exist in Angular.
I followed the link to google groups that was provided in one of the other answers to this question. There I found the following code, which solved the issue:
instanceInjector.loadNewModules = function (mods) {
forEach(loadModules(mods), function(fn) { instanceInjector.invoke(fn || noop); });
};
When I added this code to line 4414 in the angular 1.5.0 source code (inside the createInjector function, before the return instanceInjector; statement), it enabled me to add dependencies after bootstrapping like this $injector.loadNewModules(['ngCookies']);.
Since version 1.6.7 it is now possible to lazy load modules after the app has been bootstrapped using $injector.loadNewModules([modules]). Below is an example taken from AngularJS documentation:
app.factory('loadModule', function($injector) {
return function loadModule(moduleName, bundleUrl) {
return getScript(bundleUrl).then(function() { $injector.loadNewModules([moduleName]); });
};
})
Please read full documentation about loadNewModules as there are some gotchas around it.
There's also a very good sample app by omkadiri using it with ui-router.

Resources