AngularJS - Accessing ng-init variables from run method - angularjs

1) I have variables initialized in ng-init
Eg -
ng-init="password='Mightybear'";
2) I want to access it from the .run method.
Eg -
anguar.module("ngApp", [])
.run(function() {
//Access password here
});
Below scenarios I have tried, and did not work -
1) angular.module("ngApp", [])
.run(function($rootScope) {
console.log($rootScope.password) //undefined!!!
});
2) angular.module("ngApp", [])
.run(function($rootScope, $timeout) {
$(timeout(function() {
console.log($rootScope.password) //undefined!!!
});
});

You can not get ng-init value inside your run block
Angular LifeCycle
Config Phase (app.config) ($rootScope will not available here)
Run Phase (app.run) ($rootScope will available here)
Directive Gets Compile()
Then controller, directive link function, filter, etc gets executed.(ng-init is here)
If you want to get initialize value in run phase then you need to set that value in config phase.
If You want to set the value in config then you can take use of app.constant/provider which available in configuration phase, don't use $rootScope that is considered as bad pattern in AngularJS.
Code
var app = angular.module('app', []);
app.constant('settings', {
title: 'My Title'
})
app.config(function(settings) {
setting.title = 'Changed My Title';
//here can you do other configurarion setting like route & init some variable
})
app.run(function(settings) {
console.log(settings.title);
})

I'll let you a walkthrough, of how angular loads
angular module ---> .config ----> .run ----> controllers(ng-init)
now you can clear your approach.

Related

Why can't I inject $scope into a factory in Angular?

I have a factory that needs to listen for a broadcast event. I injected $scope into the factory so I could use $scope.$on. But as soon as I add $scope to the parameter list I get an injector error.
This works fine:
angular.module('MyWebApp.services')
.factory('ValidationMatrixFactory', ['$rootScope', function($rootScope) {
var ValidationMatrixFactory = {};
return ValidationMatrixFactory;
}]);
This throws an injector error:
angular.module('MyWebApp.services')
.factory('ValidationMatrixFactory', ['$scope', '$rootScope', function($scope, $rootScope) {
var ValidationMatrixFactory = {};
return ValidationMatrixFactory;
}]);
Why can't I inject $scope into a factory? And if I can't, do I have any way of listening for events other than using $rootScope?
Because $scope is used for connecting controllers to view, factories are not really meant to use $scope.
How ever you can broadcast to rootScope.
$rootScope.$on()
Even though you can't use $scope in services, you can use the service as a 'store'. I use the following approach inspired on AltJS / Redux while developing apps on ReactJS.
I have a Controller with a scope which the view is bound to. That controller has a $scope.state variable that gets its value from a Service which has this.state = {}. The service is the only component "allowed" (by you, the developer, this a rule we should follow ourselves) to touch the 'state'.
An example could make this point a bit more clear
(function () {
'use strict';
angular.module('app', ['app.accounts']);
// my module...
// it can be defined in a separate file like `app.accounts.module.js`
angular.module('app.accounts', []);
angular.module('app.accounts')
.service('AccountsSrv', [function () {
var self = this;
self.state = {
user: false
};
self.getAccountInfo = function(){
var userData = {name: 'John'}; // here you can get the user data from an endpoint
self.state.user = userData; // update the state once you got the data
};
}]);
// my controller, bound to the state of the service
// it can be defined in a separate file like `app.accounts.controller.js`
angular.module('app.accounts')
.controller('AccountsCtrl', ['$scope', 'AccountsSrv', function ($scope, AccountsSrv) {
$scope.state = AccountsSrv.state;
$scope.getAccountInfo = function(){
// ... do some logic here
// ... and then call the service which will
AccountsSrv.getAccountInfo();
}
}]);
})();
<script src="https://code.angularjs.org/1.3.15/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="AccountsCtrl">
Username: {{state.user.name ? state.user.name : 'user info not available yet. Click below...'}}<br/><br/>
Get account info
</div>
</div>
The benefit of this approach is you don't have to set $watch or $on on multiple places, or tediously call $scope.$apply(function(){ /* update state here */ }) every time you need to update the controller's state. Also, you can have multiple controllers talk to services, since the relationship between components and services is one controller can talk to one or many services, the decision is yours. This approach focus on keeping a single source of truth.
I've used this approach on large scale apps... it has worked like a charm.
I hope it helps clarify a bit about where to keep the state and how to update it.

How can I load my controllers at application Bootstrap

I need to initialize the controller, that I use in another view from my start view.For this purpose I want to call my defined setup() method from that controller.
I can't do that because it is uninitialized until the user goes in that particular view defined by the $stateProvider.
How can I load the controller on the application startup, in order for my setup function from that controller to get initialized?
.controller('StartupCtrl', function($scope,$rootScope) {
vm=this;
var ctrl1=$rootScope.ctrl;
var config={};
// DO SOMETHING and create the config
ctrl1.setup(config);})
.controller('Ctrl1', function($scope,$rootScope) {
vm=this;
$rootScope.ctrl1=this;
vm.state="";
vm.setup(config){
vm.state=config.state
}
});
Module has a method run(), which can be used on the initial load of an app. You can define your setup method in there, or you can just set variables o the rootscope:
//... initialize myModule before this somewhere
myModule.run(function($rootScope) {
$rootScope.config = {}
//...
})
More information about module methods in the doc (look at run and configuration blocks sections) https://docs.angularjs.org/guide/module
Please try like this:
.controller('StartupCtrl', function($scope,$rootScope) {
vm=this;
var ctrl1=$rootScope.ctrl;
var config={};
// DO SOMETHING and create the config
ctrl1.setup(config);})
.controller('Ctrl1', function($scope,$rootScope, StartupCtrl) {
vm=this;
$rootScope.ctrl1=this;
vm.state="";
vm.setup(config){
vm.state=config.state
}
});

How to Lazyload controller and template in one request using angular-ui-router

I'm trying to lazy-load components. The component is an html fragment with an embedded script tag that contains the controller.
<script>
... controller code .....
</script>
<div>
... template ....
</div>
The fragment is generated in ONE html request so I cannot use templateUrl AND componentURL in the state definition.
I have tried to use the templateProvider to get the component, than extract the script code for the function and register it using a reference to the controllerProvider.
I'm sure there must be a better way to do this than the ugly solution I have come up with. I make a global reference to the controllerpovider, then I read the component thru the templateProvide using a getComponent service. Next I extract the script and evaluate it, which also registers the controller.
See the plunker for the way I'm trying to solve this.
.factory('getComponent', function($http, $q) {
return function (params) {
var d = $q.defer();
// optional parameters
$http.get('myComponent.html').then(function (html) {
// the component contains a script tag and the rest of the template.
// the script tags contain the controller code.
// first i extract the two parts
var parser = new window.DOMParser();
var doc = parser.parseFromString(html.data, 'text/html');
var script = doc.querySelector('script');
// Here is my problem. I now need to instantiate and register the controller.
// It is now done using an eval which of cours is not the way to go
eval(script.textContent);
// return the htm which contains the template
var html = doc.querySelector('body').innerHTML;
d.resolve(html);
});
return d.promise;
};
})
Maybe it could be done using a templateProvider AND a controllerProvider but I'm not sure how to resolve both with one http request. Any help / ideas would be greatly appreciated.
Here's a working plunkr
You do not have access to $controllerProvider at runtime, so you cannot register a named controller.
UI-Router doesn't require named/registered controller functions. { controller: function() {} } is perfectly valid in a state definition
However, if you need the controller to be registered, you could use a tool like ocLazyLoad to register it.
UI-Router links the controller to the template, so there's no need for ng-controllersprinkled in the html.
Here's how I hacked this together. getController factory now keeps a cache of "promises for components". I retained your eval and template parsing to split the response into the two pieces. I resolved the promise with an object containing the ctrl/template.
component factory
.factory('getComponent', function($http, $q) {
var components = {};
return function (name, params) {
if (components[name])
return components[name];
return components[name] = $http.get(name + '.html').then(extractComponent);
function extractComponent(html) {
var parser = new window.DOMParser();
var doc = parser.parseFromString(html.data, 'text/html');
var script = doc.querySelector('script');
// returns a function from the <script> tag
var ctrl = eval(script.textContent);
// return the htm which contains the template
var tpl = doc.querySelector('body').innerHTML;
// resolve the promise with this "component"
return {ctrl: ctrl, tpl: tpl};
}
};
})
myComponent.html
<script>
var controller = function($scope) {
$scope.sayHi = function() {
alert(' Hi from My controller')
}
};
// This statement is assigned to a variable after the eval()
controller;
</script>
// No ng-controller
<div>
Lazyloaded template with controller in one pass.
<button ng-click="sayHi()">Click me to see that the controller actually works.</button>
</div>
In the state definition:
I created a resolve called 'component'. That resolve asks the getComponent factory to fetch the component called 'myComponent' (hint: 'myComponent' could be a route parameter)
When it's ready, the resolve is exposed to the UI-Router state subtree.
The state is activated
The view is initialized
The controller provider and template provider inject the resolved component, and return the controller/template to UI-Router.
Smell test
I should mention that fetching a controller and template in a single html file and manually parsing smells wrong to me.
Some variations you could pursue:
Improve the getComponent factory; Split the component into html/js and fetch each separately. Use $q.all to wait for both fetches.
Use ocLazyLoad to register the controller globally with $controllerProvider
Fetch and store the template in the $templateCache.

Execute $http call in angular .run() finished after controller init

I am trying to make an $http call in order to store a JSON into $rootScope in the .run() function in an Angular app but the call is done after the controllers are loaded and I cannot use any of the data from the call. Can anyone help me with this issue?
Also tried to see what happens with console.log, the output is "b" then "a" and for $rootScope.xml is undefined
var app = angular.module('lobby', ['ngRoute']);
app.run(['$rootScope','$http', function($rootScope, $http) {
$http.get(Main.constants.BASEURL+'x.xml').success(function(xml) {
$rootScope.xml = $.xml2json(xml.data);
console.log('a');
});
}]);
app.controller('CategoriesController', function($rootScope) {
console.log($rootScope.xml);
console.log('b');
});
You can try placing a $watch on $rootScope.xml and listen for a new value, which should occur when the $http call resolves & xml scope variable value gets changed.
Code
app.controller('CategoriesController', function($rootScope) {
$rootScope.$watch('xml', function(newValue, oldValue) {
if($rootScope.xml) {
console.log($rootScope.xml);
}
});
});

Manually bootstrapping AngularJS and then getting the module

Generally, I'd do the following and there would be an ng-app in my HTML:
var myApp = angular.module("myApp", []);
myApp.controller("AttributeCtrl", function ($scope) {
$scope.master = {
name: "some name"
};
});
However, I need to manually bootstrap angular because I'm only using it in a part of my app that is loaded via jQuery's $.load(). If I do the following:
main.js - this is where the page I want to use angular on is being pulled in
$("#form").load(contextPath + url, params, function() {
angular.bootstrap($("#angularApp"));
});
And then the page being pulled in has it's own javascript:
function AttributeCtrl($scope) {
$scope.master = { name: "some name"};
}
This works, however, ideally, I'd like my controllers to be scoped at the module level. So I modified the above code like so
main.js
$("#form").load(contextPath + url, params, function() {
angular.bootstrap($("#angularApp", ["myApp"]));
});
and then...
var app = angular.module("myApp"); // retrieve a module
app.controller("AttributeCtrl", function($scope) {
$scope.master = { name: "some name"};
});
Retrieving the module this way doesn't seem to work, though. Am I doing something wrong?
You cannot create a controller after you've bootstrapped the app. See the documentation for angular.bootstrap.
You should call angular.bootstrap() after you've loaded or defined your modules. You cannot add controllers, services, directives, etc after an application bootstraps.
I don't know if this is just in the example code you have here but:
angular.bootstrap($("#angularApp", ["myApp"]));
should be
angular.bootstrap($("#angularApp"), ["myApp"]);
Your code for retrieving the module should work.
Updated
They updated the documentation and now it reads like this
Each item in the array should be the name of a predefined module or a
(DI annotated) function that will be invoked by the injector as a run
block. See: {#link angular.module modules}
It seems a bug.
The way you implemented to retrieve the module is correct. Just quote it from the doc to make it clear since it may not be well-known.
When passed two or more arguments, a new module is created. If passed
only one argument, an existing module (the name passed as the first
argument to module) is retrieved.
For the problem you mentioned, long story short...
The bootstrap function calls createInjector with the module list ['ng', ['ngLocale', function(){...}] , 'myApp'] (the last one is the module you passed in)
function bootstrap(element, modules) {
...
var injector = createInjector(modules);
Inside createInjector(), it calls loadModules for each module passed in
function createInjector(modulesToLoad) {
forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); });
And loadModules calls angularModule, which is initialized as angularModule = setupModuleLoader(window);, which creates the object window.angular.module
function loadModules(modulesToLoad){
....
var moduleFn = angularModule(module); // triggers the error
The the error occurs, since angularModule takes 2nd parameter as requires. Without it, it will throws an exception on this line (line 1148) throw Error('No module: ' + name);
Reported: https://github.com/angular/angular.js/issues/3692
Not sure if this counts as a bug or an implementation decision (albeit a seemingly poor one). Adding an empty array solves the undefined require problem that you were having and should solve your problem overall.
var app = angular.module("myApp", []); // create a module
app.controller("AttributeCtrl", function($scope) {
$scope.master = { name: "some name"};
});`
Also, in your fiddle you call {{name}} which won't render. You should be calling {{master.name}}
Edit
Thank you all for the downvotes .. Here's a working example. Good luck!
http://plnkr.co/edit/UowJpWYc1UDryLLlC3Be?p=preview

Resources