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()
};
});
Related
I am trying to make a basic function that adds 1 to the variable 'wood' every second.
In javascript, a simple
setInterval(function(){
wood++;
}, 1000);
would do the trick.
In Angular, I've been shown
app.controller('RandomCtrl', function($interval){
this.wood = 0;
$interval(function(){
this.wood++;
}, 1000);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script>
var app = angular.module('app', []);
app.controller('RandomCtrl', function($interval){
this.wood = 0;
$interval(function(){
this.wood++;
document.getElementById('p').innerHTML = this.wood;
}, 1000);
});
</script>
<div ng-app='app' ng-controller='RandomCtrl as rand'>
Wood: {{ rand.wood }}
<br><br>Wood's value straight from the $interval:<p id='p'></p>
So, the interval is fine, but the variable is undefined inside it, which is the whole point of me using this interval.
<br><br>Also, I want this.wood to hold the value, nothing else.
</div>
However, the code above for some reason doesn't work.
It treats this.wood+1 as 'NaN' and this.wood as 'undefined'
Here's the snippet:
From http://ryanmorr.com/understanding-scope-and-context-in-javascript/ :
Context is most often determined by how a function is invoked. When a
function is called as a method of an object, this is set to the object
the method is called on
When called as an unbound function, this will default to the global
context or window object in the browser. However, if the function is
executed in strict mode, the context will default to undefined.
Just use angulars $scope or a variable declared in the outer function scope:
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script>
var app = angular.module('app', []);
app.controller('RandomCtrl', function($interval, $scope){
var self = this;
self.wood = 0;
$scope.wood = 0;
$interval(function(){
$scope.wood++;
self.wood++;
}, 1000);
});
</script>
<div ng-app='app' ng-controller='RandomCtrl as rand'>
Wood: {{ wood }} {{ rand.wood }}
<br><br>Wood's value straight from the $interval:<p id='p'></p>
So, the interval is fine, but the variable is undefined inside it, which is the whole point of me using this interval.
<br><br>Also, I want this.wood to hold the value, nothing else.
</div>
The problem here is that you are trying to access your scope in a new context, where 'this' no longer refers to your angular controller.
In addition, Angular allows variables to be accessible through your controller's scope. If you want to use your variable wood in your template, you have to assign it on the scope object.
Your code should read:
app.controller('RandomCtrl', function($interval, $scope){
$scope.wood = 0;
$interval(function(){
$scope.wood++;
}, 1000);
});
The reason why this fails silently is that this.TEST++ returns NaN and not an error.
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.
I am relatively new to JavaScript & Angular.
So this may be a dumb question, but here goes ...
I need to execute a function that will perform data transformation on incoming data and create arrays & objects that the page will consume. I can only process my page after this function is executed.
Please note that this function will not be used directly by any angular artifact, but its output will.
Please advise. Thanks
$scope.prepped_data = function (data) {
// code to generate new data structures //
$scope.data1 = {};
$scope.data2 = {};
};
Just create a factory like this:
angular.module('yourAppModule').factory('yourFactory', function ($http) {
return {
getData: function() {
var url = 'http://get-your-data.com/data';
return $http({
method: 'GET',
url: url
});
},
}
And afterwards, in the controller:
angular.module('yourAppModule').controller('yourCtrl', function ($scope, yourFactory) {
yourFactory.getData().success(function (data) {
// process your data here
$scope.data = dataProcessed;
}
});
You can then use "data" in your view to display it as you wish, there are also filtering capabilities in Angular templates. Take a look.
If you need a custom function to be used in the view, you may pass it to the $scope in your controller, kinnda like you did.
$scope.myFunc = function(data){
//code here
}
And then in your view, you may use:
<!doctype html>
<html xmlns:ng="http://angularjs.org" ng-app="yourAppModule" id="ng-app">
<head>
<script src="bower_components/angular/angular.js"></script>
</head>
<body>
<div ng-controller="yourCtrl">
{{ myFunc() }}
</div>
</body>
</html>
Hope it helps,
Best.
In the service
public method1(){
var defer = this.$q.defer();
//Show some loader
this.$http({
method: 'GET',
url: "www.xyz.com"
}).success((result) => {
//Hide loader
defer.resolve(result);
}).error((result) => {
defer.reject(result);
});
In the controller
method1().then(function(data){//Do your processing})
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;
}
Let me start by saying that what I am trying to do is probably not considered good practice. However, I need to do something like this in order to migrate a large web app to AngularJs in small incremental steps.
I tried doing
$scope.$watch(function () { return myVar; }, function (n, old) {
alert(n + ' ' + old);
});
Where myVar is a global variable (defined on window)
And then changing myVar from the console.
But it only fires when first setting up the watcher.
It works if I update myVar from within the controller (see http://jsfiddle.net/rasmusvhansen/vsDXz/3/, but not if it is updated from some legacy javascript
Is there any way to achieve this?
Update
I like Anders' answer if the legacy code is completely off limits. However, at the moment I am looking at this approach which seems to work and does not include a timer firing every second:
// In legacy code when changing stuff
$('.angular-component').each(function () {
$(this).scope().$broadcast('changed');
});
// In angular
$scope.$on('changed', function () {
$scope.reactToChange();
});
I am awarding points to Anders even though I will go with another solution, since his solution correctly solves the problem stated.
The issue here is probably that you're modifying myVar from outside of the Angular world. Angular doesn't run digest cycles/dirty checks all the time, only when things happen in an application that should trigger a digest, such as DOM events that Angular knows about. So even if myVar has changed, Angular sees no reason to start a new digest cycle, since nothing has happened (at least that Angular knows about).
So in order to fire your watch, you need to force Angular to run a digest when you change myVar. But that would be a bit cumbersome, I think you would be better of to create a global observable object, something like this:
<!doctype html>
<html ng-app="myApp">
<head>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="http://code.angularjs.org/1.0.5/angular.min.js"></script>
<script>
// Outside of Angular
window.myVar = {prop: "value1"};
var myVarWatch = (function() {
var watches = {};
return {
watch: function(callback) {
var id = Math.random().toString();
watches[id] = callback;
// Return a function that removes the listener
return function() {
watches[id] = null;
delete watches[id];
}
},
trigger: function() {
for (var k in watches) {
watches[k](window.myVar);
}
}
}
})();
setTimeout(function() {
window.myVar.prop = "new value";
myVarWatch.trigger();
}, 1000);
// Inside of Angular
angular.module('myApp', []).controller('Ctrl', function($scope) {
var unbind = myVarWatch.watch(function(newVal) {
console.log("the value changed!", newVal);
});
// Unbind the listener when the scope is destroyed
$scope.$on('$destroy', unbind);
});
</script>
</head>
<body ng-controller="Ctrl">
</body>
</html>