How can I download multiple scripts in the resolve section with ui-router? - angularjs

I recently had a question on how I could load jQuery when I moved to a new page
using AngularJS ui-router. Here's the answer that I accepted:
resolve:{
jquery: function($q){
if (typeof jQuery === 'undefined') {
var deferred = $q.defer();
var script = document.createElement('script');
script.setAttribute("src", "http://code.jquery.com/jquery-2.0.3.min.js");
document.getElementsByTagName("head")[0].appendChild(script);
var wait = setInterval(function(){
if (typeof jQuery === 'function') {
console.log('jQuery:', typeof jQuery)
deferred.resolve();
clearInterval(wait);
}
}, 100);
return deferred.promise;
}
}
}
Can someone tell me how I can extend this so I get the jQuery script and also another
local script from my server. The important thing for me is that the second script would need to download after jQuery as it depends on it.
The second script I need to get is /Scripts/pagedown/markdown.js
Please note I am not looking for a solution that uses an external library like require.js. I have only one time I am doing this so using an external library would be overkill.

Try using http://requirejs.org/ since it's the best solution here.
jquery-deps.js:
define(['jquery-min'], function (dep1) {
// Your local script here. Dependent file will be fetched here if needed
return someObject; //For example, jQuery object
});
You can add this in router resove:
require(['jquery-min'], function(dependency) {
// jquery will be fetched first, then more scripts will be executed.
// Add more scripts
});
Take a look at requireJS docs, it's easy to implement and don't include js files using plain javascript just because it's not safe and not cool enough.

Convert the script loading process into a more generic, standalone function meant for reuse:
var injectJs = function(url, name, deferred) {
var script = document.createElement('script');
script.setAttribute("src", url);
document.getElementsByTagName("head")[0].appendChild(script);
var wait = setInterval(function(){
if (typeof window[name] === 'function') {
deferred.resolve();
clearInterval(wait);
}
}, 100);
}
Create another promise for markdown.js:
var mdDeferred = $q.defer();
Then, instead of returning the jQuery loading promise from the function, add a callback to it:
jqDeferred.promise.then(function(){
... and inside of that callback, use injectJS again to load the markdown promise, resolving its promise once it has.
Note: you need to pass the name (a string) of the object to check for in as the third object. For jQuery, it is 'jQuery'. Find out what object will be found on the window object once the script loads and use that.
Finally, it's the markdown promise that should now be returned from the resolve function:
return mdDeferred.promise;
Review this demo to see a working example, which uses Underscore in place of markdown.js.

Related

How to wait for load configuration AJAX request to respond and then make other service calls in angularjs

I have a common service which serves configuration details from backend that I am using in other services...is there a way to load the common service before loading other services.
Since I am getting an exception in JS "cannot read property "url" of null". Sometimes It works fine if the configuration loaded successfully before other calls
It sounds like using module.run() would help solve your problem as you can initialise your common service:
var app = angular
.module('demoApp', [])
.run(['commonService', function(commonService) {
commonService.Init()
}]);
Few suggestions,
You can make use module.run function which will execute immediately after module.config function, make AJAX/HTTP call their and keep it in $rootScope/Service, or as angular constant.
Interceptors: you can also make use of the $http's interceptors, so when ever you makes an AJAX request we can configure to execute the interceptor first, so that we can check whether the configs loaded or not, if not then we can grab that first.
If you are using angular routing, then we can make use of resolve convention.
You can set up a service deferred, which holds a promise that gets resolved after initialization of the service is complete. Then when asking for a value of the service return a promise chained to the deferred promise:
angular.module('YourApp').factory("CommonService", function(){
var initDeferred = $q.defer();
var initialize = function() {
/* initialize your service, maybe do something async, maybe set local variables with obtained info
* then resolve deferred promise (maybe with obtained values)*/
initDeferred.resolve(/* initialized service data */);
};
var getUrl = function() {
return initDeferred.promise.then(
function(initializedServiceData) {
return initializedServiceData.url;
}
);
};
return {
initialize: initialize,
getUrl: getUrl
}
});
And don't forget to initialize the service. A good point to do so is the run() function of the module as Brian Giggle already suggested.
angular.module('YourApp', []).run(function(CommonService){
CommonService.initialize();
});
You are able then to retrieve the initialized data as a promise:
angular.module('YourApp').controller('YourController', function($scope, CommonService){
CommonService.getUrl()
.then(function(url){
$scope.url = url;
})
});

AngularJs: Does not update the model after the modifying it through $http transform

I need to transform objects coming from $http call to an api. My code adds some fields (functions) to the object coming from the api, here the constructor of this object :
(function () {
window.TransformedObject = function (obj) {
var self = this;
self = {};
if (obj) {
self = angular.copy(obj);
}
self.hasChanged = function () {
// return true or false if the object has changed
}
return self;
}
}());
The $http transform code looks like this :
$http({
url: 'api/...',
method: 'GET',
transformResponse: function(value) {
return new TransformedObject(JSON.parse(value));
})
}).success(function(data){
vm.obj = angular.copy(data);
});
Note that the value in the transformResponse callback is stringified, and need to be parsed to get the object
All this is working fine, suppose the object coming from the api contains a key called title, doing obj.title = 'some title' will update the object.
The problem :
Binding the title field with an input tag will not update the object if the change is coming from the view.
I use a regular ng-model to do it:
<input type="text" placeholder="Title" ng-model="vm.obj.title"/>
even using $rootScope.$watch will never be triggered if the change is coming from the view aka the input tag.
$rootScope.$watch(function () {
return vm.obj;
}, function () {
console.log('watch');
// this log will never appear in the console
});
Am I doing something wrong, why transforming the object coming from the api is breaking angulars binding ???
Thanks.
http://www.bennadel.com/blog/2605-scope-evalasync-vs-timeout-in-angularjs.htm
Sometimes, in an AngularJS application, you have to explicitly tell
AngularJS when to initiate it's $digest() lifecycle (for dirty-data
checking). This requirement is typically contained within a Directive;
but, it may also be in an asynchronous Service. Most of the time, this
can be easily accomplished with the $scope.$apply() method. However,
some of the time, you have to defer the $apply() invocation because it
may or may not conflict with an already-running $digest phase. In
those cases, you can use the $timeout() service; but, I'm starting to
think that the $scope.$evalAsync() method is a better option.
...
Up until now, my approach to deferred-$digest-invocation was to
replace the $scope.$apply() call with the $timeout() service (which
implicitly calls $apply() after a delay). But, yesterday, I discovered
the $scope.$evalAsync() method. Both of these accomplish the same
thing - they defer expression-evaluation until a later point in time.
But, the $scope.$evalAsync() is likely to execute in the same tick of
the JavaScript event loop.

$templateCache from file undefined? When is the accessible by other js code? (with np-autocomplete)

I'm rather new to angular and I'm trying to integrate np-autocomplete in my application (https://github.com/ng-pros/np-autocomplete). However I can only get it to work when I'm passing a html string as a template inside the $scope.options and it doesn't work when I want to load it from a separate html.
the Code for my app looks as follows:
var eventsApp = angular.module('eventsApp',['ng-pros.directive.autocomplete'])
eventsApp.run(function($templateCache, $http) {
$http.get('test.html', {
cache: $templateCache
});
console.log($templateCache.get('test.html')) // --> returns undefined
setTimeout(function() {
console.log($templateCache.get('test.html')) // --> works fine
}, 1000);
//$templateCache.put('test.html', 'html string') //Would solve my issue in the controller,
//but I would rather prefer to load it from a separate html as I'm trying above
Inside my controller I am setting the options for autocomplete as follows:
controllers.createNewEventController = function ($scope) {
$scope.options = {
url: 'https://api.github.com/search/repositories',
delay: 300,
dataHolder: 'items',
searchParam: 'q',
itemTemplateUrl: 'test.html', // <-- Does not work
};
//other stuff...
}
however, it seems that test.html is undefined by the time np-autocomplete wants to use it (as it is also in first console.log above).
So my intuition tells me that the test.html is probably accessed in the controller before it is loaded in eventsApp.run(...). However I am not sure how to solve that?
Any help would be highly appreciated.
You are most likely correct in your assumption.
The call by $http is asynchronous, but the run block will not wait for it to finish. It will continue to execute and the execution will hit the controller etc before the template has been retrieved and cached.
One solution is to first retrieve all templates that you need then manually bootstrap your application.
Another way that should work is to defer the execution of the np-autocomplete directive until the template has been retrieved.
To prevent np-autocomplete from running too early you can use ng-if:
<div np-autocomplete="options" ng-if="viewModel.isReady"></div>
When the template has been retrieved you can fire an event:
$http.get('test.html', {
cache: $templateCache
}).success(function() {
$rootScope.$broadcast('templateIsReady');
});
In your controller listen for the event and react:
$scope.$on('templateIsReady', function () {
$scope.viewModel.isReady = true;
});
If you want you can stop listening immediately since the event should only fire once anyway:
var stopListening = $scope.$on('templateIsReady', function() {
$scope.viewModel.isReady = true;
stopListening();
});

Using future object to return not the promise but the data

I'm trying to use the following code:
$scope.property = $http.get(url).then(function(res) {
return res.data;
});
Just like a $resource, which I can use:
$scope.MyProp = MyResource.query();
(as seen in https://docs.angularjs.org/tutorial/step_11)
I'm not having success in my code, but I was successful in a jsfiddle that I created for tests. Later I found out that the problem is that angular 1.2+ doesn't support this technic.
You can find it out on http://jsfiddle.net/victorivens05/5jdsmk4s/ just by changing the framework from 1.1.1 to 1.2.1.
I want to know if there is a way to acomplish this behaviour in angular 1.2+.
Thanks.
If I'm understanding what you want to do wouldn't something like this work?
var promise = $http.get(url);
promise.then(function(res) {
$scope.property = res.data;
});

How to handle multiple JS libraries with different loading times in Angular?

I am just learning Angular and I have some questions regarding the architecture of my app.
The project I will be working on will be using allot of external libraries: jQuery, jQuery.ui, jsPlumb and so on, with different loading times.
I know that each code related to these libraries will have to be handled inside directives.
I worked with Backbone which uses Require JS effectively - on each view, I could set what libraries I need and the view would be loaded as soon as those libraries are available.
Now, on angular side - what would be the correct way of handling this issue?
From the top of my head, I am thinking of:
Place checks inside the router - checking if the desired libraries for a certain route are loaded.
Place checks inside each directive - for example if one directive uses jsPlumb, place a check inside and return the directives content when that library is loaded - I believe this could generate problems when interacting with other directives on the same view, which require multiple libraries with different loading times.
Load angular only after every other library is loaded - that would lead to long loading times.
What's the best way to handle all those issues?
You can create a factory to load the external library you need. Return a deferred object for the library's script after it loads. Here is one I used for d3 library:
var d3js = angular.module('d3', []);
d3js.factory('d3Service', ['$document', '$q', '$rootScope', '$window',
function($document, $q, $rootScope, $window) {
var d = $q.defer();
function onScriptLoad() {
// Load client in the browser
$rootScope.$apply(function() { d.resolve($window.d3); });
}
// Create a script tag with d3 as the source
// and call our onScriptLoad callback when it
// has been loaded
var scriptTag = $document[0].createElement('script');
scriptTag.type = 'text/javascript';
scriptTag.async = true;
scriptTag.src = 'lib/d3.v3.js';
scriptTag.onreadystatechange = function () {
if (this.readyState == 'complete') onScriptLoad();
}
scriptTag.onload = onScriptLoad;
var s = $document[0].getElementsByTagName('body')[0];
s.appendChild(scriptTag);
return {
d3: function() { return d.promise; }
};
}]);
then in your directive, use then function of the deferred to wait until it's ready
d3Service.d3().then(function(d3) {
// place code using d3 library here
}
If your directive is needing access to multiple libraries, you can chain the deferreds.
d3Service.d3().then(function(d3) {
someOtherLibSvc.getLib().then(function(lib){
// place code using d3 library and someOtherLibrary here
}
}
To avoid this chaining check out bluebird and use Promise.join, Angular comes with $q automatically so I just used that here.
Note: if you just load JQuery before angular, then angular.element will already reference JQuery. So you don't need to do this for JQuery, just load it in your main html page before Angular
In short go
http://slides.com/thomasburleson/using-requirejs-with-angularjs#/
route.
However now suffer having to put the defines and requires everywhere. For a large application this may get messy if not planned carefully. Use grunt requirejs plugin for builds because without this things would be wash.
I'm not sure this is the "correct" way to do this, but here is perhaps a simpler way to handle external libraries (in this case d3.js) within Angular 1.x code.
This is the same basic idea as #aarosil's answer (use a factory), but using fewer dependencies on other services - for what that's worth.
var app = angular.module('SomeApp', []);
app.factory('LibraryFactory', function () {
var factory = {};
factory.getD3 = function(callback) {
if(!window.d3) {
var script = document.createElement("script");
script.src = "https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.9/d3.min.js";
document.head.appendChild(script);
script.onload = function() {
callback(window.d3);
};
} else {
callback(window.d3);
}
};
//factory.getSomeOtherLibrary = ...ditto
return factory;
});
And to use the factory (eg, in a controller):
app.controller('SomeTrialWithD3Ctrl', function ($scope, LibraryFactory) {
LibraryFactory.getD3(function main(d3) {
// place code using d3 library here
});
});
And of course, the callbacks can be chained if you need multiple libraries at once.

Resources