AngularJS: Inject dependencies while $provide - javascript

I am a newbie to AngularJS. I am trying to develop a single page application in salesforce CRM plarform. I wanted to load the dependencies on demand i.e., only when the user click a particular link. I have also achieved this by using https://github.com/matys84pl/angularjs-requirejs-lazy-controllers
The above link has examples for directives alone. I have added services and filter in the same route configuration file.
function registerServices(service) {
if(service) {
if (!$provide){ throw new Error("Error: $setProvide is not set!"); }
$provide.value(service[0], service[1]);
} else {
$provide.value = null;
}
}
I'm registering a service like the above and it can be used as,
service = ['serviceName', function(){
return {
a: 1, b:2
}
}]
Everything is working great, But my doubt is, how to inject the dependencies such $q, $rootScope, etc in the above code.

I don't know about lazy loading but, if lazy loading work then the standard method of injection may work
service = ['serviceName', '$rootScope','$q', function($rootScope, $q){
return {
a: 1, b:2
}
}]

I have a solution for this problem, I am not sure whether the way is proper.
Controller
var controller = function($scope, customService){
$scope.name = "hello world";
customService($q, $rootScope).then(function(data){
console.log(data);
/*Received data can be manipulated*/
});
}
Service
'use strict';
define(function () {
return ['customService', function($q, $rootScope){
var deferred = $q.defer();
/*Your code*/
return deferred.promise;
}];
})

Related

How can I execute a function in an angularJS controller from a backbone controller

I am working on a application originally created with backbone and jQuery, however due to client requirement, new modules are built with angular. Routing of the application is handled with backbone route and we have successfully integrated angular modules.
The actual problem is, I need to retrieve the current instance of a module in angular and execute a function from the controller of that module based on actions handled by a backbone controller.
Here is what my angular module and controller looks like:
//In chat.module.js
( function () {
angular
.module( 'chat.module', [] );
})();
//In chat.controller.js
(function () {
angular
.module('chat.module')
.controller('chat.controller', ['profileFactory', '$filter', '$q', '$timeout', 'Position', 'Chat', chat]);
function chat(profileFactory, $filter, $q, $timeout, Position, Chat) {
var vm = this;
vm.initChatFlag = false;
vm.initChat = initChat;
vm.setInformation = setInformation;
function setInformation() {
//handle business logic here
}
...
In backbone, the module is created as follows:
chatmodule: function () {
var self = this;
var element = angular.element(document.querySelector('#modalCallback'));
var chat = angular.element(document.querySelector('#chatModule'));
var isInitializedChat = chat.injector();
var isInitialized = element.injector();
if (!isInitialized) {
angular.bootstrap($('#modalCallback'), ['app']);
}
if (!isInitializedChat) {
angular.bootstrap($('#chatModule'), ['app']);
}
//TODO: chat.controller.setInformation() get access to fields like chat.controller.initChatFlag etc
The main app module is defined thus:
(function(){
angular
.module('app',[
'callback',
'ui.bootstrap',
'720kb.datepicker',
'ngLocale',
'directives.module',
'interceptor',
'directive.loading',
'angularUtils.directives.dirPagination',
'blog.module',
'profile.module',
'filters.module',
'chat.module',
'ui.toggle',
]);
})();
The AngularJS $injector is where a lot of the magic happens, so if you expose that outside of the AngularJS code you can hook it up to non-AngularJS code like the following:
//A simple AngularJS service:
app.service('myService', function() {
this.message = "This is my default message.";
});
//Expose the injector outside the angular app.
app.run(function($injector, $window) {
$window.angularInjector = $injector;
});
//Then use the injector to get access to the service.
//Make sure to wrap the code in a `$apply()` so an
//AngularJS digest cycle will run
function nonAngularEventHandler() {
angularInjector.invoke(function(myService, $rootScope) {
$rootScope.$apply(function() {
myService.message = "Now this is my message."
});
});
}
Edit: Alternatively, simplify the call like so.
//Instead of exposing the $injector directly, wrap it in a function
//which will do the $apply() for you.
app.run(function($injector, $window, $rootScope) {
$window.callInMyAngularApp = function(func) {
$rootScope.$apply(function() {
$injector.invoke(func);
});
}
});
//Then call that function with an injectable function like so.
function nonAngularClick() {
callInMyAngularApp(function(myService) {
myService.message = "Now this is my message."
});
}
//And remember if you're minifying, you'll want the minify-safe
//version of the injectable function like this
function nonAngularClick() {
callInMyAngularApp(['myService', function(myService) {
myService.message = "Now this is my message."
}]);
}
Update: (last one I promise!)
The above will work fine, but you might want to consider exposing a well-defined API instead of a generic injectable interface. Consider the following.
//Now I have a limited API defined in a service
app.service("myExternalApi", function($rootScope, myService) {
this.changeMyMessage = function(message) {
$rootScope.$apply(function() {
myService.message = message;
});
};
});
//And I just expose that API
app.run(function($window, myExternalApi) {
$window.myExternalApi = myExternalApi;
});
//And the call from outside of angular is much cleaner.
function nonAngularClick() {
myExternalApi.changeMyMessage("Now this is my message.");
}
I was able to get access to the controller using answer from this post - https://stackoverflow.com/a/21997129/7411342
var Chat = angular.element(document.querySelector('#chatModule')).scope();
if(!Chat) return;
if(Chat.chatCtrl.initChatFlag) {
Chat.chatCtrl.setInformation();
}else{
console.log('Chat has not been initialized');
}

AngularJS module.controller vs $controllerProvider.register

I'm trying to add controller dynamically in my AngularJS application.
On sub-domain, I have anotherController.js file.
Here's anotherController.js content:
function anotherControllerWrapper() {
return ['$scope', '$state', function ($scope, $state) {
$scope.doWork = function () {
//...doing some work...
alert('work done');
};
$scope.doWork();
}];
};
Also I have wrote runtimeController provider to be able to use $controllerProvider in runtime:
app.provider('runtimeController', function () {
var controllerProvider = null;
this.setControllerProvider = function (cp) {
controllerProvider = cp;
};
this.$get = function () {
return {
registerController: function (controllerName, controllerConstructor) {
if (!controllerProvider.has(controllerName)) {
controllerProvider.register(controllerName, controllerConstructor);
}
}
};
};
});
Here's config section of application:
app.config(function($controllerProvider, runtimeControllerProvider) {
runtimeControllerProvider.setControllerProvider($controllerProvider);
});
I'm receiving controller's code over http (inside another controller), so it looks like this:
app.controller('testController', ['$scope', '$state', '$http', 'runtimeController',
function ($scope, $state, $http, runtimeController) {
$http.get('http://someUrl/anotherController.js')
.then(
function(sucess){
var evaluated = new Function('return ' + success.data)();
var ctrl = evaluated();
// routing to ui state with specified 'anotherController' works
// no 'anotherController' in app._invokeQueue
runtimeController.registerController('anotherController', ctrl);
// routing to ui state with specified 'anotherController' constanly fails
// 'anotherController' appears in app._invokeQueue
//app.controller('anotherController', ctrl);
//--registering new UI route with 'anotherController' as controller here
$state.go('anotherState');
},
function(error){ alert('something went wrong!'); },
);
}]);
Ui states are also added dymanically, after I'm adding controller.
Can someone explain me please, what's happening and what's difference between $controllerProvider.register and module.controller?
Module methods (controller, directive, etc) result in adding a config block (_configBlocks) that is executed on application initialization. Once the application has passed config phase, it won't execute newly added config blocks, so app.controller(...) has no effect during run phase.
As this example shows, runtimeController implementation can be simplified to
app.config(($provide, $controllerProvider) => {
$provide.value('$controllerProvider', $controllerProvider);
});
eval should be avoided for numerous reasons. Considering that the script is loaded from a domain that is allowed by CORS and doesn't require to be evaled, a suitable alternative is to load it as a script. This will require to patch AngularJS API to allow late component registrations, similarly to how ocLazyLoad does - or just use ocLazyLoad, because it already does that.

How do you inject a condition service into AngularJS 1 Controller

I have an angular module that is designed to be self contained. That a consuming app can add the directives with a url param and it will use that url as it's overall data source when interacting with the widget. This has a generic LoadService that uses $http to load the data, and expects a specific JSON format to run the widget.
Well right now I am trying to refactor so that someone can also create a custom load service and inject it into the module, but if it's not injected then it will use the default data load. So I am trying to figure out how I can create a way that CustomLoadService is injected if it is defined by the app that is consuming the module. However it should not error out if the custom service isn't defined, it should just use the default service.
I was looking into the $injector.get and saw that as a possibility but I am having trouble injecting the $injector into a controller. I thought it would be as simple as $location to inject. Something like...
angular
.module('Widget')
.controller('WidgetController',[
'$scope',
'WidgetLoadService',
'$injector',
WidgetController
]);
This method doesn't seem to work so I am wondering... What is the best most "angular way" to solve this issue. How should I be using the $injector.
You can use $injector:
app.controller('MainCtrl', function($scope, $injector) {
$scope.name = $injector.get('test').name;
}).factory('test', function() { return {name: 'world'} });
So you may have something like this as a result:
app.controller('MainCtrl', function($scope, shareService) {
$scope.name = shareService.getData();
shareService.setDataService('dataService2');
$scope.name = shareService.getData();
})
.factory('shareService', function($injector) {
var dataServiceName;
return {
setDataService: function(name) {
dataServiceName = name;
},
getData: function(name) {
return $injector.get(dataServiceName || 'dataService').data;
}
}
})
.factory('dataService', function() { return {data: 'world'} })
.factory('dataService2', function() { return {data: 'world 2'} });

Dependency injection hell, what is expected?

I'm trying to separate components into several files for a simple application but angular's dependency injector is giving me headaches and I don't really know what is expected.
Unknown provider: servicesProvider <- services <- maincontroller
Is the error I'm getting.
app.js
//Application definition with injected dependencies
var app = angular.module('leadcapacity', ['services', 'utils', 'customfilters', 'controllers']);
services.js
var services = angular.module('services', []);
services.service('xrmservice',
[
'$http', function($http) {
var oDataUrl = Xrm.Page.context.getClientUrl() + '/XRMServices/2011/OrganizationData.svc/';
var service = {};
service.query = function(entitySet, query) {
return $http.get(oDataUrl + entitySet + '?' + query);
};
return service;
}
]);
controllers.js
var ctrls = angular.module('controllers', ['utils', 'services']);
ctrls.controller('maincontroller',
function ($scope, services, utils) {
};
});
And the include order in index.html
<script src="service.js"></script>
<script src="controllers.js"></script>
<script src="app.js"></script>
Looks fine to me. I know this is perhaps not the best way to organize things, but getting a "Hello world" first would be nice.
Thanks.
Error message appearing in console clearly says that, services
dependency isn't exists in the module.
You have injected incorrect service name in maincontroller controller factory function, basically you were trying to to inject services(module name) instead of xrmservice(service name)
function ($scope, services, utils) {
should be
function ($scope, xrmservice, utils) {
Additional
Do follow Inline Array annotation of DI, as you were already used the same in your xrmservice service JS file, so that in future you don't need to go back and change that when you face javascript minification related issues.
Controller
ctrls.controller('maincontroller', [ '$scope', 'xrmservice', 'utils',
function ($scope, xrmservice, utils) {
//code goes here
//....
};
}]);
Although you have injected them into the module, you need to give them to the function so you can use the injected modules
ctrls.controller('maincontroller',
['$scope', 'services', 'utils', function ($scope, services, utils) {
};
}]);

AngularJS - Pass parameters into Controller?

I'm trying to create a simple blog website using AngularJS. I'm just starting out, so what I'm thinking my not be the best way to do this, so any alternative suggestions are welcome.
I have a controller.js file with two blog controllers. One to display a list of blog posts, and the other that displays the post content by including an HTML file.
controller.js
myAppControllers.controller('BlogListCtrl', ['$scope', '$http', function ($scope, $http) {
$http.get('articles/articles.json').success(function (articles) {
$scope.articles = articles;
});
}]);
myAppControllers.controller('BlogPostCtrl', ['$scope', '$routeParams', function ($scope, $routeParams) {
$scope.includeFile = 'articles/' + $routeParams.blogPostId + '.html';
}]);
articles.json
[
{
"id": "test-article-one",
"title": "Test Article one",
"author": "Gareth Lewis",
"datePosted": "2015-06-23",
"summary": "This is a test summary"
},
{
"id": "test-article-two",
"title": "Test article two",
"author": "Gareth Lewis",
"datePosted": "2015-06-23",
"summary": "This is a test for article two"
}
]
app.js
when('/blog', {
templateUrl: 'partials/blog-articles.html',
controller: 'BlogListCtrl'
}).
when('/blog/:blogPostId', {
templateUrl: 'partials/blog-post.html',
controller: 'BlogPostCtrl'
}).
blog-post.html
<ng-include src="'partials/header.html'"></ng-include>
<!-- Want to add title, author, datePosted information here... -->
<article class="content">
<ng-include src="includeFile"></ng-include>
</article>
This blog listings work fine. When I click into a blog post, it also serves up the content from the HTML file OK as well. However, I want to be able to reuse the title, author and datePosted properties from the selected article in the blog-post.html partial view. What's the best way to do this? Would I need to pass them to the Controller somehow to then pass to the view? I don't really want to pass these as routeParams. Or would I need to do a $http.get on articles.json and iterate through to find the selected article and then pass the property values back to the view?
Thanks for the help.
You said that suggestions are welcome, so here it goes.
1 - Transport all your Blog logic to a service;
2 - Provide the data on resolving routes. This is a better approach to handle errors during the load time, 404s, and so on. You can provide a listener to $routeChangeError and deal with it there;
3 - On the service declared below, you have the methods to call your data and a method to retrieve the list cached on the service:
// services.js
myAppServices
.service('BlogService', ['$http', '$q', function ($http, $q) {
var api = {},
currentData = {
list: [],
article: {}
};
api.getSaved = function () {
return currentData;
};
api.listArticles = function () {
var deferred = $q.defer(),
backup = angular.copy(currentData.list);
$http.get('articles/articles.json')
.then(function (response) {
currentData.list = response;
deferred.resolve(response);
}, function () {
currentData.list = backup;
deferred.reject(reason);
});
return deferred.promise;
};
api.getArticle = function (id) {
var deferred = $q.defer(),
backup = angular.copy(currentData.article),
path = 'articles/' + id + '.html';
$http.get(path, {
cache: true
})
.then(function (response) {
currentData.article = {
path: path,
response: response
};
deferred.resolve(currentData.article);
}, function (reason) {
currentData.article = backup;
deferred.reject(currentData.article);
});
return deferred.promise;
};
return api;
}]);
The BlogService.getSaved() will retrieve the stored data, made after each call.
I've made a method to call the ng-include path too, so you can verify if it exists, with cache === true, the browser will keep a copy of it, when calling it again on the view. A copy of the response of the blog article is made too, so you can access its path and the response whenever you need.
On the controllers below, they were adaptated to supply the current needs:
// controller.js
myAppControllers
.controller('BlogListCtrl', ['$scope', 'articles',
function ($scope, articles) {
$scope.articles = articles;
/* OTHER STUFF HERE */
}
])
.controller('BlogPostCtrl', ['$routeParams', '$scope', 'article' 'BlogService',
function ($routeParams, $scope, article, BlogService) {
// On `article` dependency, you have both the original response
// and the path formed. If you want to use any of it.
$scope.includeFile = article.path;
// To get the current stored data (if any):
$scope.articles = BlogService.getSaved().list;
// Traverse the array to get your current article:
$scope.article = $scope.articles.filter(function (item) {
return item.id === $routeParams.id;
});
/* OTHER STUFF HERE */
}
]);
And the route declarations were changed to load the data when resolving the routes.
// app.js
$routeProvider
.when('/blog', {
templateUrl: 'partials/blog-articles.html',
controller: 'BlogListCtrl',
resolve: {
articles: ['BlogService', '$routeParams', function (BlogService, $routeParams) {
return BlogService.listArticles();
}]
}
})
.when('/blog/:id', {
templateUrl: 'partials/blog-post.html',
controller: 'BlogPostCtrl',
resolve: {
article: ['BlogService', '$routeParams', function (BlogService, $routeParams) {
return BlogService.getArticle($routeParams.blogPostId);
}]
}
})
This is maybe a common question in angular. What you have to understand is that Scope is defined per controller... In order to share data across controller you still have the option to use $scope.$parent or $rootScope to link controllers but I would use those carefully.
It is better to use Angular Services which are based on singleton patterns therefore you can use them to share information between controllers and I think it will be a better approach.
I found that this has been previously discussed and here are some good examples:
AngularJS Service Passing Data Between Controllers
You can use a global scope to set this data, or you can use service to communicate between the controllers. There is a lot of ways to resolve this problem read a little bit more about services in the link bellow and see if you can find how to resolve your problem.
AngularJS: Service vs provider vs factory

Categories

Resources