How does Angular's injector obtain its omniscience? - angularjs

The following example is lifted from the angularJS docs http://docs.angularjs.org/guide/directive
In this example somehow the injector knows $timeout by name and dateFilter by name, even though javascript doesn't have named parameters (like Python). So I added a debugger statement, to see how it manages to pull this magic off, and walking back 10 so stack frames, I come to the conclusion I'm completely lost!
I see this all over the place in angular, this magical injector that somehow manages to get the right pieces marshalled together for function calls. I just don't understand how they do it. My directive could as easily have $location, or something else as the first argument to the function and it would get the right object so that it would work. How does the magic work????
<!doctype html>
<html ng-app="docsTimeDirective">
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.3/angular.js"></script>
<script type="text/javascript">
debugger;
angular.module('docsTimeDirective', [])
.controller('Ctrl2', function($scope) {
$scope.format = 'M/d/yy h:mm:ss a';
})
.directive('myCurrentTime', function($timeout, dateFilter) {
debugger;
...
</script>
</head>
<body>
<div ng-controller="Ctrl2">
Date format: <input ng-model="format"> <hr/>
Current time is: <span my-current-time="format"></span>
</div>
</body>
</html>

It all happens in the following piece of code, which basically uses toString() on the function you pass as a factory to angular, and which extracts the argument names from this string representation of the function using regular expressions:
var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var $injectorMinErr = minErr('$injector');
function annotate(fn) {
var $inject,
fnText,
argDecl,
last;
if (typeof fn == 'function') {
if (!($inject = fn.$inject)) {
$inject = [];
if (fn.length) {
fnText = fn.toString().replace(STRIP_COMMENTS, '');
argDecl = fnText.match(FN_ARGS);
forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
arg.replace(FN_ARG, function(all, underscore, name){
$inject.push(name);
});
});
}
fn.$inject = $inject;
}
} else if (isArray(fn)) {
last = fn.length - 1;
assertArgFn(fn[last], 'fn');
$inject = fn.slice(0, last);
} else {
assertArgFn(fn, 'fn', true);
}
return $inject;
}

Related

How can i use $http in a constant decleration

Explaining the problem:
So in the current app we have a couple of constant configuration declarations that connects the app to either the production or development environment, and we comment one out whenever we want to switch which doesn't seem like the ideal scenario to me. So what I was after is having a configuration external json file that contains the values and have that file separately from the changing code and get values from there into my constant.
The actual question:
In this piece of code:
application.constant('servicesConfig', (function() {
var con = 'appdev';
//var con = 'appprod';
return {
host: con+'.appdomain.com'
}
}
As you can see I have to modify the 'con' variable manually in order to switch between the dev and prod environments, instead, I want to do the following:
application.constant('servicesConfig', (function() {
var deferred = $q.defer();
var configLocation = 'config/server.json';
var configurations = $http.get(configLocation)
return {
host: configurations.con+'.appdomain.com'
}
}
My question is how can I get the $http or other angular services injected?
You can manually bootstrap angular after receive data from server.
Example on jsfiddle:
var app = angular.module("myApp", []);
app.controller("myController", function($scope, servicesConfig) {
console.log(servicesConfig);
$scope.model = servicesConfig.host;
$scope.reset = function() {
$scope.model = '';
};
});
angular.element(document).ready(function() {
//Simulate AJAX call to server
setTimeout(function() {
app.constant('servicesConfig', {
host: "appdev"
});
angular.bootstrap(document, ['myApp']);
}, 2000);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-cloak>
<div ng-controller="myController">
<input type="text" ng-model="model" /> {{model}}
<button ng-click="reset()">Reset</button>
</div>
</div>

How to synchronously bootstrap an angularjs app

How to synchronously bootstrap an angularjs app
I define a couple constant values on my app object. Some of these values need to be set via a call to a service and these calls need to complete before any of my controllers are instantiated.
In the example below, I define an array of values on an object named config. I need to set the value named PostIndexMenuID prior to any of my controllers being instantiated. How do I do that?
I have tried manually bootstrapping (removing ng-app from the html). I am not using routing.
Ideally I will not have to learn, download, install, configure, test, and maintain another framework to accomplish this.
(function()
{
angular.module('app', []);
var app = angular.module('app');
app.controller('controller', ['$scope', 'config', '$http', controller]);
app.service('menusService', ['$http', 'config', menusService]);
// Create config object. Some values are set in app.run
app.value('config', {
siteID: 100,
webRoot: '',
apiRoot: '/api',
imageRoot: '/Content/images',
PostIndexMenuID: 0
});
app.run(['$http', 'config','menusService', function ($http, config, menusService) {
menusService.GetMenuIDByName("PostIndex", function (data) {
config.PostIndexMenuID = data; // Need to complete this BEFORE GetPosts on the controller is called
});
}]);
function controller($scope, config, $http) {
var vm = this;
vm.Posts = 0;
function GetPosts() {
// In prod we call a service here get posts based on config.PostIndexMenuID
// for this example just return PostIndexMenuID.
vm.Posts = config.PostIndexMenuID;
};
GetPosts(); // need to delay calling this until AFTER PostIndexMenuID is set
};
function menusService($http, config) {
this.GetMenuIDByName = function (menuName, callBack) {
var uri = config.apiRoot + '/menu/GetMenuByName?menuName=' + menuName + '&siteID=' + config.siteID;
// use a timeout to simulate a slow service for this example and return a dummy value
var menuID = 99;
setTimeout(function () {
callBack(menuID);
}, 2000);
};
};
})()
// html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" ng-app="app" >
<head>
<title></title>
</head>
<body >
<div ng-controller = "controller as vm">
<p>PostIndexMenuId is {{ vm.Posts }}</p>
</div>
<script src="Scripts/jquery-1.8.2.js"></script>
<script src="Scripts/angular.js"></script>
<script src="Scripts/angular-route.js"></script>
<script src="app/app.js"></script>
</body>
</html>
There is quite a nifty trick in Angular.js whereby you can defer the changing of a route until all promises have been resolved. You may have to restructure your application a little bit to cater for this approach, but I use it myself and it works like a treat!
$rootScope.$on('$routeChangeStart', function $routeChangeStart(event, next) {
// Extend the resolution of the route promise to wait for the user authentication
// process to determine if there's a valid session available.
next.resolve = angular.extend( next.resolve || {}, {
authenticating: api.isSession
});
});
By using the next.resolve you're extending Angular's sequence of promises that are resolved before a route change is considered a success. In the above case, as long as api.isSession returns a promise then everything will work wonderfully.
As soon as the api.isSession promise has been resolved, then the route change will succeed, and in your case, your controller will be loaded.

Uncaught object- anonymous function Angular JS

OK I give up! I need some help on this one. I have spent a very long time reading all of the similar questions that have been asked on S/O, and nothing has been able to help my problem.
I'm trying to add a factory to my angularFire application.
I am getting the 'Uncaught Object (anonymous function)' error - Through Chrome inspector I have ascertained more specific error information: https://docs.angularjs.org/error/$injector/nomod?p0=ngLocale%22
here is my HTML Script information:
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.18/angular.js"></script>
<script src="js/angular-route.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers.js"></script>
<script src="js/factories.js"></script>
<script src='https://cdn.firebase.com/js/client/1.0.17/firebase.js'></script>
<script src='https://cdn.firebase.com/libs/angularfire/0.7.1/angularfire.min.js'></script>
here is my controller.js:
legalmvc.controller('FormCtrl','FBRetrieve', function FormCtrl($scope, $location, FBRetrieve, $firebase) {
var url = 'https://legalcitator.firebaseio.com';
var fireRef = new Firebase(url);
$scope.addCase = function () {
$scope.FireBaseCases = new Firebase('https://legalcitator.firebaseio.com/case');
$scope.caseData = {};
var newAuthor = $scope.newAuthor;
var newJournal = $scope.newJournal;
var newArticleName = $scope.newArticleName;
var newYear = $scope.newYear;
if (!newAuthor.length || !newArticleName.length || !newJournal.length || newYear === null ) {
return;
}
newAuthor = $scope.newAuthor.trim();
newJournal = $scope.newJournal.trim();
newArticleName = $scope.newArticleName.trim();
var newCaseRow = $scope.FireBaseCases.child(newAuthor);
newCaseRow.set({
type: 'Case',
author: newAuthor,
journal: newJournal,
articleName: newArticleName,
year: newYear }
);
$scope.newAuthor = '';
$scope.newJournal ='';
$scope.newYear =null;
$scope.newArticleName= '';
};
});
here is my factory.js :
legalmvc.factory('FBRetrieve', function(type){
var biblioRef = new Firebase('https://legalcitator.firebaseio.com/'+type);
biblioRef.on('value', function(data) {
if (data.val() === null) {
return;
}
return data;
});
});
and here is the app.js
var legalmvc = angular.module('legalmvc', ['FBRetrieve','ngRoute']);
Will be deeply, deeply grateful if someone could give me a hand, this thing is giving me an aneurysm!!
Sam
EDIT:
The problem was that i was trying to inject my factory 'FBRetrieve' and an external module. I removed this, and then the problem that I was facing was in this code
legalmvc.factory('FBRetrieve', function(type){
var biblioRef = new Firebase('https://legalcitator.firebaseio.com/'+type);
.....
it seems that you cannot pass 'type' in as a parameter on factory function which will allow you to use this same function on different URLs. Can't say i understand exactly why.
Thanks guys!!
Imho the issue is in: var legalmvc = angular.module('legalmvc', ['FBRetrieve','ngRoute']); remove the FBRetrieveentry. It's not an external module it's your factory.
You most likely wanted to use:
var legalmvc = angular.module('legalmvc', ['firebase','ngRoute']);
And you should inject $firebase instead creating new objects new Firebase, the rationale behind this is explained here: https://docs.angularjs.org/guide/unit-testing
You should not inject your factory on the creation of your module as the factory is registered on the module it already knows it.
Correct me if I'm wrong but module declaration only accept injection of other modules.
So from your code you only have to remove the FBRetrieve injection from the module creation.
It becomes :
var legalmvc = angular.module('legalmvc', ['ngRoute']);
As your factory and your controller are on the same module, it will resolve it when creating your controller.
If your factory was on another module, then you would have :
var factoryModule = angular.module("factoryModule");
factoryModule.factory('FBRetrieve', function(type){
...
}
var legalmvc = angular.module('legalmvc',['factoryModule', 'ngRoute']);
legalmvc.controller('controller',['FBRetrieve', '$scope',
function(FBRetrieve, $scope){
...
}]);
Hope it helps.

angularjs and google chrome extension

I have a simpe google-chrome extension app. I'm using bookmarks and it present in manifest file. Firstly, i use chrome bookmarks api in controller, and it works well. But i decided use factory for clear code and best practices.
My index.html file
<!DOCTYPE html>
<html lang="en" ng-app="BookmarksSharer" ng-csp>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>bokmarks sharer</title>
<link rel="stylesheet" href="/css/main.css"/>
<script src="/javascript/jquery-2.1.1.min.js"></script>
<script src="/javascript/angular.min.js"></script>
<script src="/javascript/bookmark_sharer.js"></script>
<script src="/javascript/MainController.js"></script>
<script src="/javascript/BookmarkService.js"></script>
</head>
<body>
<div id="main_popup" ng-controller="MainController">
<p>Bookmarks</p>
<ul>
<li ng-repeat="bookmark_folder in bookmarks_folders" id="{{bookmark_folder.title}}">
{{bookmark_folder.title}}
<ul ng-repeat="bookmarks in bookmark_folder">
<li ng-repeat="bookmark in bookmarks | filter">{{bookmark.title}}</li>
</ul>
</li>
</ul>
</div>
</body>
</html>
bookmark_sharer.js is simple
var app = angular.module("BookmarksSharer", []);
MainController.js very simple too.
app.controller('MainController',['$scope', 'bookmarkFactory', function($scope, bookmarkFactory){
$scope.boormarks_folders = bookmarkFactory.folders;
}]);
And my factory
app.factory('bookmarkFactory', function () {
var bookmarks_folders;
function allBookmarksFolders(){
chrome.bookmarks.getTree(function(bookmarks_tree){
bookmarks_folders = bookmarks_tree[0].children;
});
return bookmarks_folders;
}
return {
folders: allBookmarksFolders()
};
});
Why $scope.bookmarks_folders is undefined?
ExpertSystem your code not working too. Simple solution is
app.controller('MainController',['$scope', function($scope){
chrome.bookmarks.getTree(function(nodes){
$scope.bookmarks_folders = nodes[0].children;
$scope.$apply();
})
}]);
But i want organize my code using factories or services.
chrome.bookmarks.getTree() is asynchronous, so by the time the getAllBookmarks() function returns, bookmarks_folders is undefined.
Your service exposes a property (folders) which is bound to the result of allBookmarksFolders(), alas undefined.
Since this operation is asynchronous, your service should return a promise instead, so the controller can use that promise and get the actual data when it is returnd:
// The service
app.factory('bookmarkFactory', function ($q) {
function retrieveAllBookmarksFolders() {
var deferred = $q.defer();
chrome.bookmarks.getTree(function (bookmarks_tree) {
deferred.resolve(bookmarks_tree[0].children);
});
return deferred.promise;
}
return {
foldersPromise: retrieveAllBookmarksFolders()
};
});
// The controller
app.controller('MainController', function($scope, bookmarkFactory){
bookmarkFactory.foldersPromise.then(function (bookmarks_folders) {
$scope.boormarks_folders = bookmarks_folders;
});
});
The main problem arises from the fact that (in your implementation) you return the value bound to bookmarks_folders at that point and later on re-assign bookmarks_folders to hold a reference to a different object (bookmarks_tree[0].children).
The flow of events inside your service is somewhat like this:
bookmarks_folders is declared (and initialized to undefined).
allBookmarksFolders() is executed and returns (the still undefined) bookmarks_folders.
folders is assigned a reference to the object currently referenced by bookmarks_folders (i.e. undefined).
The getTree() callback executes and assigns to bookmarks_folders a reference to a different object (bookmarks_tree[0].children). At that point folders knows nothing about it and continues to reference to the previous value (undefined).
An alternative approach (one that is used by the $resource btw), is to not assign a new reference to bookmarks_folders, but to modify the already referenced object.
// The service
app.factory('bookmarkFactory', function () {
var bookmarks_folders = []; // initialize to an empty array
function allBookmarksFolders(){
chrome.bookmarks.getTree(function(bookmarks_tree){
// Here we need to modify the object (array)
// already referenced by `bookmarks_folders`
// Let's empty it first (just in case)
bookmarks_folders.splice(0, bookmarks_folders.length);
// Let's push the new data
bookmarks_tree[0].children.forEach(function (child) {
bookmarks_folders.push(child);
});
});
return bookmarks_folders;
}
return {
folders: allBookmarksFolders()
};
});

AngularJS access scope from outside js function

I'm trying to see if there's a simple way to access the internal scope of a controller through an external javascript function (completely irrelevant to the target controller)
I've seen on a couple of other questions here that
angular.element("#scope").scope();
would retrieve the scope from a DOM element, but my attempts are currently yielding no proper results.
Here's the jsfiddle: http://jsfiddle.net/sXkjc/5/
I'm currently going through a transition from plain JS to Angular. The main reason I'm trying to achieve this is to keep my original library code intact as much as possible; saving the need for me to add each function to the controller.
Any ideas on how I could go about achieving this? Comments on the above fiddle are also welcome.
You need to use $scope.$apply() if you want to make any changes to a scope value from outside the control of angularjs like a jquery/javascript event handler.
function change() {
alert("a");
var scope = angular.element($("#outer")).scope();
scope.$apply(function(){
scope.msg = 'Superhero';
})
}
Demo: Fiddle
It's been a while since I posted this question, but considering the views this still seems to get, here's another solution I've come upon during these last few months:
$scope.safeApply = function( fn ) {
var phase = this.$root.$$phase;
if(phase == '$apply' || phase == '$digest') {
if(fn) {
fn();
}
} else {
this.$apply(fn);
}
};
The above code basically creates a function called safeApply that calles the $apply function (as stated in Arun's answer) if and only Angular currently isn't going through the $digest stage. On the other hand, if Angular is currently digesting things, it will just execute the function as it is, since that will be enough to signal to Angular to make the changes.
Numerous errors occur when trying to use the $apply function while AngularJs is currently in its $digest stage. The safeApply code above is a safe wrapper to prevent such errors.
(note: I personally like to chuck in safeApply as a function of $rootScope for convenience purposes)
Example:
function change() {
alert("a");
var scope = angular.element($("#outer")).scope();
scope.safeApply(function(){
scope.msg = 'Superhero';
})
}
Demo: http://jsfiddle.net/sXkjc/227/
Another way to do that is:
var extScope;
var app = angular.module('myApp', []);
app.controller('myController',function($scope, $http){
extScope = $scope;
})
//below you do what you want to do with $scope as extScope
extScope.$apply(function(){
extScope.test = 'Hello world';
})
we can call it after loaded
http://jsfiddle.net/gentletech/s3qtv/3/
<div id="wrap" ng-controller="Ctrl">
{{message}}<br>
{{info}}
</div>
<a onClick="hi()">click me </a>
function Ctrl($scope) {
$scope.message = "hi robi";
$scope.updateMessage = function(_s){
$scope.message = _s;
};
}
function hi(){
var scope = angular.element(document.getElementById("wrap")).scope();
scope.$apply(function() {
scope.info = "nami";
scope.updateMessage("i am new fans like nami");
});
}
It's been a long time since I asked this question, but here's an answer that doesn't require jquery:
function change() {
var scope = angular.element(document.querySelector('#outside')).scope();
scope.$apply(function(){
scope.msg = 'Superhero';
})
}
Here's a reusable solution: http://jsfiddle.net/flobar/r28b0gmq/
function accessScope(node, func) {
var scope = angular.element(document.querySelector(node)).scope();
scope.$apply(func);
}
window.onload = function () {
accessScope('#outer', function (scope) {
// change any property inside the scope
scope.name = 'John';
scope.sname = 'Doe';
scope.msg = 'Superhero';
});
};
You can also try:
function change() {
var scope = angular.element( document.getElementById('outer') ).scope();
scope.$apply(function(){
scope.msg = 'Superhero';
})
}
The accepted answer is great. I wanted to look at what happens to the Angular scope in the context of ng-repeat. The thing is, Angular will create a sub-scope for each repeated item. When calling into a method defined on the original $scope, that retains its original value (due to javascript closure). However, the this refers the calling scope/object. This works out well, so long as you're clear on when $scope and this are the same and when they are different. hth
Here is a fiddle that illustrates the difference: https://jsfiddle.net/creitzel/oxsxjcyc/
I'm newbie, so sorry if is a bad practice. Based on the chosen answer, I did this function:
function x_apply(selector, variable, value) {
var scope = angular.element( $(selector) ).scope();
scope.$apply(function(){
scope[variable] = value;
});
}
I'm using it this way:
x_apply('#fileuploader', 'thereisfiles', true);
By the way, sorry for my english
<input type="text" class="form-control timepicker2" ng-model='programRow.StationAuxiliaryTime.ST88' />
accessing scope value
assume that programRow.StationAuxiliaryTime is an array of object
$('.timepicker2').on('click', function ()
{
var currentElement = $(this);
var scopeValues = angular.element(currentElement).scope();
var model = currentElement.attr('ng-model');
var stationNumber = model.split('.')[2];
var val = '';
if (model.indexOf("StationWaterTime") > 0) {
val = scopeValues.programRow.StationWaterTime[stationNumber];
}
else {
val = scopeValues.programRow.StationAuxiliaryTime[stationNumber];
}
currentElement.timepicker('setTime', val);
});
We need to use Angular Js built in function $apply to acsess scope variables or functions outside the controller function.
This can be done in two ways :
|*| Method 1 : Using Id :
<div id="nameNgsDivUid" ng-app="">
<a onclick="actNgsFnc()"> Activate Angular Scope</a><br><br>
{{ nameNgsVar }}
</div>
<script type="text/javascript">
var nameNgsDivVar = document.getElementById('nameNgsDivUid')
function actNgsFnc()
{
var scopeNgsVar = angular.element(nameNgsDivVar).scope();
scopeNgsVar.$apply(function()
{
scopeNgsVar.nameNgsVar = "Tst Txt";
})
}
</script>
|*| Method 2 : Using init of ng-controller :
<div ng-app="nameNgsApp" ng-controller="nameNgsCtl">
<a onclick="actNgsFnc()"> Activate Angular Scope</a><br><br>
{{ nameNgsVar }}
</div>
<script type="text/javascript">
var scopeNgsVar;
var nameNgsAppVar=angular.module("nameNgsApp",[])
nameNgsAppVar.controller("nameNgsCtl",function($scope)
{
scopeNgsVar=$scope;
})
function actNgsFnc()
{
scopeNgsVar.$apply(function()
{
scopeNgsVar.nameNgsVar = "Tst Txt";
})
}
</script>
This is how I did for my CRUDManager class initialized in Angular controller, which later passed over to jQuery button-click event defined outside the controller:
In Angular Controller:
// Note that I can even pass over the $scope to my CRUDManager's constructor.
var crudManager = new CRUDManager($scope, contextData, opMode);
crudManager.initialize()
.then(() => {
crudManager.dataBind();
$scope.crudManager = crudManager;
$scope.$apply();
})
.catch(error => {
alert(error);
});
In jQuery Save button click event outside the controller:
$(document).on("click", "#ElementWithNgControllerDefined #btnSave", function () {
var ngScope = angular.element($("#ElementWithNgControllerDefined")).scope();
var crudManager = ngScope.crudManager;
crudManager.saveData()
.then(finalData => {
alert("Successfully saved!");
})
.catch(error => {
alert("Failed to save.");
});
});
This is particularly important and useful when your jQuery events need to be placed OUTSIDE OF CONTROLLER in order to prevent it from firing twice.

Resources