Accessing factory properties in AngularJS + Chrome Apps APIs - javascript

I'm attempting to build a Chrome App using AngularJS, and one of the abilities I need is to monitor the available network interfaces through chrome.system.network.getNetworkInterfaces. Currently, I am trying to store this data in a factory and inject it into the view controller:
(pared down as much as possible)
Factory:
exampleApp.factory('exampleFactory', ['$http', function ($http) {
var service = {};
service.network_interfaces = 'Detecting network connections...';
chrome.system.network.getNetworkInterfaces(function(interfaces){
service.network_interfaces = interfaces;
})
// This outputs the expected array containing interface data
// in service.network_interfaces
console.log(service)
return service;
}]);
Controller:
exampleApp.controller('MainCtrl', ['$scope', '$http', 'exampleFactory', function($scope, $http, exampleFactory) {
// This outputs the expected array in network_interfaces, identical
// to the console output within the factory
console.log(exampleFactory)
// But then this outputs the initial 'Detecting network connections...'
// string set in the factory
console.log(exampleFactory.network_interfaces)
// And {{network_interfaces}} and {{factory_state}} in the view keep
// 'Detecting network connections...' as the value for network_interfaces
// permanently
$scope.factory_state = exampleFactory;
$scope.network_interfaces = exampleFactory.network_interfaces;
}]);
So:
The factory seems to be returning a good service object, but I'm not sure why exampleFactory and exampleFactory.network_interfaces would have the different states they do between the controller and factory, and especially within the controller itself (regardless of the order they're called in).
I've attempted a lot of different solutions with the hypothesis that it's an asynch issue, but I would think there'd be no appreciable latency on the getNetworkInterfaces method and if there were that everything is set up correctly for Angular to update the {{network_interfaces}} and {{factory_state}} view bindings once data is returned.
I've also tried wrapping various functions in the factory with $rootScope.$apply as a shot in the dark, but with the same results as above.
I've searched around a lot to discover whatever concept it is I've obviously missed, but I think I'm overlooking something fundamental. How to I get the getNetworkInterfaces() data into my controller in a useful state?

Your assumption in #2 is the problem. There will always be a spin of the JavaScript event loop in between a call to an asynchronous method and the invocation of its callback. If there weren't, all sorts of things would subtly break in clients calling the method. This means you are encountering a common problem in Angular development: that you don't get notified that something changed, because the change didn't happen within the context of Angular's digest cycle.
To fix this: try setting up a watch, then calling $scope.apply() within the getNetworkInterfaces() callback. The apply() is guaranteed to happen outside the digest cycle so you shouldn't get an apply-in-apply error.
Alternatively, post a message to yourself when then callback is done. This is better if you're a student of the "if you're using apply() your code is broken" school of thought.
Finally, consider a Promise that you call after the callback. This doesn't quite fit how you've set up your code.
(Also, why call the async method at all in the factory?)

Try watching on network_interfaces to know when it's modified
exampleApp.controller('MainCtrl', ['$scope', '$http', 'exampleFactory', function($scope, $http, exampleFactory) {
// This outputs the expected array in network_interfaces, identical
// to the console output within the factory
console.log(exampleFactory)
// But then this outputs the initial 'Detecting network connections...'
// string set in the factory
console.log(exampleFactory.network_interfaces)
// And {{network_interfaces}} and {{factory_state}} in the view keep
// 'Detecting network connections...' as the value for network_interfaces
// permanently
$scope.factory_state = exampleFactory;
$scope.network_interfaces = exampleFactory.network_interfaces;
$scope.$watch('factory_state.network_interfaces', function() {
console.log($scope.factory_state)
});
}]);

Related

Angularjs basic parameter access (beginner) [duplicate]

I'm using angular-translate for i18n in an AngularJS application.
For every application view, there is a dedicated controller. In the controllers below, I set the value to be shown as the page title.
Code
HTML
<h1>{{ pageTitle }}</h1>
JavaScript
.controller('FirstPageCtrl', ['$scope', '$filter', function ($scope, $filter) {
$scope.pageTitle = $filter('translate')('HELLO_WORLD');
}])
.controller('SecondPageCtrl', ['$scope', '$filter', function ($scope, $filter) {
$scope.pageTitle = 'Second page title';
}])
I'm loading the translation files using the angular-translate-loader-url extension.
Problem
On the initial page load, the translation key is shown instead of the translation for that key. The translation is Hello, World!, but I'm seeing HELLO_WORLD.
The second time I go to the page, all is well and the translated version is shown.
I assume the issue has to do with the fact that maybe the translation file is not yet loaded when the controller is assigning the value to $scope.pageTitle.
Remark
When using <h1>{{ pageTitle | translate }}</h1> and $scope.pageTitle = 'HELLO_WORLD';, the translation works perfect from the first time. The problem with this is that I don't always want to use translations (eg. for the second controller I just want to pass a raw string).
Question
Is this a known issue / limitation? How can this be solved?
Recommended: don't translate in the controller, translate in your view
I'd recommend to keep your controller free from translation logic and translate your strings directly inside your view like this:
<h1>{{ 'TITLE.HELLO_WORLD' | translate }}</h1>
Using the provided service
Angular Translate provides the $translate service which you can use in your Controllers.
An example usage of the $translate service can be:
.controller('TranslateMe', ['$scope', '$translate', function ($scope, $translate) {
$translate('PAGE.TITLE')
.then(function (translatedValue) {
$scope.pageTitle = translatedValue;
});
});
The translate service also has a method for directly translating strings without the need to handle a promise, using $translate.instant():
.controller('TranslateMe', ['$scope', '$translate', function ($scope, $translate) {
$scope.pageTitle = $translate.instant('TITLE.DASHBOARD'); // Assuming TITLE.DASHBOARD is defined
});
The downside with using $translate.instant() could be that the language file isn't loaded yet if you are loading it async.
Using the provided filter
This is my preferred way since I don't have to handle promises this way. The output of the filter can be directly set to a scope variable.
.controller('TranslateMe', ['$scope', '$filter', function ($scope, $filter) {
var $translate = $filter('translate');
$scope.pageTitle = $translate('TITLE.DASHBOARD'); // Assuming TITLE.DASHBOARD is defined
});
Using the provided directive
Since #PascalPrecht is the creator of this awesome library, I'd recommend going with his advise (see his answer below) and use the provided directive which seems to handle translations very intelligent.
The directive takes care of asynchronous execution and is also clever enough to unwatch translation ids on the scope if the translation has no dynamic values.
Actually, you should use the translate directive for such stuff instead.
<h1 translate="{{pageTitle}}"></h1>
The directive takes care of asynchronous execution and is also clever enough to unwatch translation ids on the scope if the translation has no dynamic values.
However, if there's no way around and you really have to use $translate service in the controller, you should wrap the call in a $translateChangeSuccess event using $rootScope in combination with $translate.instant() like this:
.controller('foo', function ($rootScope, $scope, $translate) {
$rootScope.$on('$translateChangeSuccess', function () {
$scope.pageTitle = $translate.instant('PAGE.TITLE');
});
})
So why $rootScope and not $scope? The reason for that is, that in angular-translate's events are $emited on $rootScope rather than $broadcasted on $scope because we don't need to broadcast through the entire scope hierarchy.
Why $translate.instant() and not just async $translate()? When $translateChangeSuccess event is fired, it is sure that the needed translation data is there and no asynchronous execution is happening (for example asynchronous loader execution), therefore we can just use $translate.instant() which is synchronous and just assumes that translations are available.
Since version 2.8.0 there is also $translate.onReady(), which returns a promise that is resolved as soon as translations are ready. See the changelog.
EDIT: Please see the answer from PascalPrecht (the author of angular-translate) for a better solution.
The asynchronous nature of the loading causes the problem. You see, with {{ pageTitle | translate }}, Angular will watch the expression; when the localization data is loaded, the value of the expression changes and the screen is updated.
So, you can do that yourself:
.controller('FirstPageCtrl', ['$scope', '$filter', function ($scope, $filter) {
$scope.$watch(
function() { return $filter('translate')('HELLO_WORLD'); },
function(newval) { $scope.pageTitle = newval; }
);
});
However, this will run the watched expression on every digest cycle. This is suboptimal and may or may not cause a visible performance degradation. Anyway it is what Angular does, so it cant be that bad...
To make a translation in the controller you could use $translate service:
$translate(['COMMON.SI', 'COMMON.NO']).then(function (translations) {
vm.si = translations['COMMON.SI'];
vm.no = translations['COMMON.NO'];
});
That statement only does the translation on controller activation but it doesn't detect the runtime change in language. In order to achieve that behavior, you could listen the $rootScope event: $translateChangeSuccess and do the same translation there:
$rootScope.$on('$translateChangeSuccess', function () {
$translate(['COMMON.SI', 'COMMON.NO']).then(function (translations) {
vm.si = translations['COMMON.SI'];
vm.no = translations['COMMON.NO'];
});
});
Of course, you could encapsulate the $translateservice in a method and call it in the controller and in the $translateChangeSucesslistener.
What is happening is that Angular-translate is watching the expression with an event-based system, and just as in any other case of binding or two-way binding, an event is fired when the data is retrieved, and the value changed, which obviously doesn't work for translation. Translation data, unlike other dynamic data on the page, must, of course, show up immediately to the user. It can't pop in after the page loads.
Even if you can successfully debug this issue, the bigger problem is that the development work involved is huge. A developer has to manually extract every string on the site, put it in a .json file, manually reference it by string code (ie 'pageTitle' in this case). Most commercial sites have thousands of strings for which this needs to happen. And that is just the beginning. You now need a system of keeping the translations in synch when the underlying text changes in some of them, a system for sending the translation files out to the various translators, of reintegrating them into the build, of redeploying the site so the translators can see their changes in context, and on and on.
Also, as this is a 'binding', event-based system, an event is being fired for every single string on the page, which not only is a slower way to transform the page but can slow down all the actions on the page, if you start adding large numbers of events to it.
Anyway, using a post-processing translation platform makes more sense to me. Using GlobalizeIt for example, a translator can just go to a page on the site and start editing the text directly on the page for their language, and that's it: https://www.globalizeit.com/HowItWorks. No programming needed (though it can be programmatically extensible), it integrates easily with Angular: https://www.globalizeit.com/Translate/Angular, the transformation of the page happens in one go, and it always displays the translated text with the initial render of the page.
Full disclosure: I'm a co-founder :)

Understanding $resource and controllers

I'm trying to understand the following pieces of code. If I understand correctly, below I have three resource objects that interact with REST server data sources (these objects are CategorySrv, ArticleSrv and SearchSrv respectively).
app.factory('CategoryService', function($resource) {
return $resource('categories');
});
app.factory('ArticleService', function($resource) {
return $resource('articles');
});
app.factory('SearchService', function($resource) {
return $resource('articles/search');
});
Now, the question I'd like to ask if when is the controller's code executed? If I loaded the page, it would probably run only once. When will it run the next time? Controller's code doesn't run in a loop, but only when its methods are called from the view, or the portion of the page attached to this controller is loaded (AFAIK).
When this happens, the resource objects listed above are injected into the argument list and controller's function is called:
app.controller('AppCtrl', function($scope, $location, CategoryService, ArticleService, CartService) {
CategoryService.query(function(response) {
$scope.categories = response;
});
ArticleService.query(function(response) {
$scope.articles = response;
});
CartService.get(function(response) {
$scope.cartInfo = response;
});
Do I understand this correctly? Also, what's the difference between get and query above?
Yes. The code won't be running in a loop, only when the controller is loaded.
$resource gives you the option to use get or query, the difference being that the query will immediately return an empty array or object depending on whether isArray is set to true. This helps with rendering so that no errors are thrown when angular expects an array or object but instead gets undefined like you would for async actions. When the response comes back with your data, the digest cycle will run since $scope has changed and it will subsequently re-render your view.
docs: https://docs.angularjs.org/api/ngResource/service/$resource

How to structure angular factor/service that manipulates DOM

I'm building a growl like UI in angular. I'd like to expose it as a factory (or service) to make it available in my controllers. Calling growl.add will result in a change in the DOM, so it seems like I should have a directive take care of that, rather than doing direct DOM manipulation in the factory. Assuming that a factory-directive combo is the best option (and please correct me if that is not a good assumption), the question is:
How best to communicate between the factory and the directive?
Specifically, how best to send messages from the factory to the directive? Other questions have well covered sending information the other way, with onetime callback.
See below the working example. I suspect there is a better way though..
For reference, I have played with other options:
A) have the directive watch the service, e.g.
$scope.$watch(function(){
growl.someFunctionThatGetsNewData()},
function(newValue){
//update scope
})
But this means that someFunctionThatGetsNewData gets called in every digest cycle, which seem wasteful, since we know that the data only gets changed on growl.add
B) send an 'event', either via routescope, or via event bindings on the dom/window. Seem un-angular
Since neither of those options seem good, I'm using the one below, but it still feels hacky. The register function means that the directive and the factory are tightly coupled. But then again from usage perspective they are tightly bound - one is no good w/o the other.
It seem like the ideal solution would involve declaring a factory (or service) that includes the directive in its declaration (and perhaps functional scope) so that it exposes a single public interface. It seems icky to have two separate publicly declared components that entirely depend on each other, and which have tight coupling in the interfaces.
Working example - but there must be a better way..
vpModule.directive('vpGrowl',['$timeout', 'growl', function ($timeout, growl) {
return {
template: '<div>[[msg]]</div.',
link: function($scope, elm, attrs) {
growl.register(function(){
$scope.msg = growl.msg;
});
$scope.msg = growl.msg;
}
};
}]);
vpModule.factory('growl', ['$rootScope', '$sce', function($rootScope, $sce) {
var growl = {};
growl.msg = '';
var updateCallback = function(){};
growl.add = function(msg){
growl.msg = msg;
updateCallback();
};
growl.register = function(callback){
updateCallback = callback;
};
return growl;
}]);
I would have your growl service decide what to show, not the directive. So, the service handles any timers, state, etc. to decide when to hide/show messages. The service then exposes a collection of messages which the directive simply binds to.
The directive can inject the service and simply place it in scope, and then bind an ng-repeat to the service's collection. Yes, this does involve a watch, but you really don't need to worry about the performance of a single watch like this.
link: function(scope, elm, attrs) {
scope.growl = growl; // where 'growl' is the injected service
}
and then in the directive template:
<div ng-repeat="msg in growl.messages">
...
</div>
I would implement following logic:
Service growl defines some property growlProp on $rootScope & update it on each call of growl.add
Directive set watcher on $rootScope.growlProp
So directive knows nothing about service & service knows nothing about directve.
And additional overhead related to watcher is minimum.

Angular JS - How to safely retain global value "extracted" using a service

I need an object to be globally accessible all throughout my Angular application, and I've gladly put this object in a value service.
Unfortunately, this object is computed by another service, and I've not been able to inject myService into same value service.
ATM, I've acheived my goal with something like this:
global service code
app.service('myService', function() {
this.foo = function() {
// some code that returns an object
return computedObject
}
})
app.run(function ($rootScope, myService) {
$rootScope.global = {dbObject: myService.foo()}
})
And which I can use in any controller that pleases me by simply injecting $rootScope in it
However, I don't like the need of injecting the whole $rootScope wherever I need that damn object, and I trust is not much safe (or efficient?) either, since the team specifies
Of course, global state sucks and you should use $rootScope sparingly, like you would (hopefully) use with global variables in any language. In particular, don't use it for code, only data. If you're tempted to put a function on $rootScope, it's almost always better to put it in a service that can be injected where it's needed, and more easily tested.
Conversely, don't create a service whose only purpose in life is to store and return bits of data.
Do you, perchance, happens to know any way I can inject a service into a service value?
Or maybe any other Angular best practice which I could exploit?
I forgot one very important notice
The computation of the object could be quite computational intense, so I absolutely don't want it to be recomputed everytime I move from page to page, or anything else really.
Ideally Using $rootScope for storing a few globally required values is totally ok. But if you are still adamant on using a module, I suggest you use a provider.
Make your 'myService' a provider and that will let you configure the variables in the service.
More info here
AngularJS: Service vs provider vs factory
You could use $broadcast to send the value from the value service to myService.
You would still need to inject $rootscope into each of the services, but from then on you could just inject myService around the rest of the app.
Reference here.
I need an object to be globally accessible all throughout my Angular application
I would use service. Since Service is singleton you can register the service to all your controllers and share any data over service.
Unfortunately, this object is computed by another service, and I've not been able to inject myService into same value service.
Just create one main service (Parent) and child service that will inherit the parent. (like abstract service in Java world).
Application.factory('AbstractObject', [function () {
var AbstractObject = Class.extend({
virtualMethod: function() {
alert("Hello world");
},
abstractMethod: function() { // You may omit abstract definitions, but they make your interface more readable
throw new Error("Pure abstract call");
}
});
return AbstractObject; // You return class definition instead of it's instance
}]);
Application.factory('DerivedObject', ['AbstractObject', function (AbstractObject) {
var DerivedObject = AbstractObject.extend({
virtualMethod: function() { // Shows two alerts: `Hey!` and `Hello world`
alert("Hey!");
this._super();
},
abstractMethod: function() {
alert("Now I'm not abstract");
}
});
return DerivedObject;
}]);
Now, we can add some logic into AbstractObject and continue use DerivedObject
Here is example Plunker

angularjs passing variables into controller

I have my angular controller setup like most of the examples shown in the docs such that it is a global function. I assume that the controller class is being called when the angular engine sees the controller tag in the html.
My issue is that i want to pass in a parameter to my controller and i don't know how to do that because I'm not initializing it. I see some answers suggesting the use of ng-init. But my parameter is not a trivial string - it is a complex object that is being loaded by another (non-angular) part of my js. It is also not available right on load but takes a while to come along.
So i need a way to pass this object, when it finally finishes loading, into the controller (or scope) so that the controller can interact with it.
Is this possible?
You can use a service or a factory for this, combined with promises:
You can setup a factory that returns a promise, and create a global function (accessible from 3rd-party JS) to resolve the promise.
Note the $rootScope.$apply() call. Angular won't call the then function of a promise until an $apply cycle. See the $q docs.
app.factory('fromoutside', function($window, $q, $rootScope) {
var deferred = $q.defer();
$window.injectIntoAngularWorld = function(obj) {
deferred.resolve(obj);
$rootScope.$apply();
};
return deferred.promise;
});
And then in your controller, you can ask for the fromoutside service and bind to the data when it arrives:
app.controller('main', function($scope, fromoutside) {
fromoutside.then(function(obj) {
$scope.data = obj;
});
});
And then somewhere outside of Angular:
setTimeout(function() {
window.injectIntoAngularWorld({
A: 1,
B: 2,
C: 3
});
}, 2000);
Here's a fiddle of this.
Personally, I feel this is a little bit cleaner than reaching into an Angular controller via the DOM.
EDIT: Another approach
Mark Rajcok asked in a comment if this could be modified to allow getting data more than once.
Now, getting data more than once could mean incremental updates, or changing the object itself, or other things. But the main things that need to happen are getting the data into the Angular world and then getting the right angular scopes to run their $digests.
In this fiddle, I've shown one way, when you might just be getting updates to an Array from outside of angular.
It uses a similar trick as the promise example above.
Here's the main factory:
app.factory('data', function($window) {
var items = [];
var scopes = [];
$window.addItem = function(item) {
items.push(item);
angular.forEach(scopes, function(scope) {
scope.$digest();
});
};
return {
items: items,
register: function(scope) { scopes.push(scope); }
};
Like the previous example, we attach a function to the $window service (exposing it globally). The new bit is exposing a register function, which controllers that want updates to data should use to register themselves.
When the external JS calls into angular, we just loop over all the registered scopes and run a digest on each to make sure they're updated.
In your non-angular JavaScript, you can get access to the scope associated with a DOM element as follows:
angular.element(someDomElement).scope().someControllerFunction(delayedData);
I assume you can find someDomElement with a jQuery selector or something.

Categories

Resources