Inheritance angularJS Modules - javascript

I'm wondering about some AngularJS behaviour.
I'm curious if AngularJS modules inherit the dependencies of other modules.
Let's say i have this structure:
var PVNServices = angular.module('PVN.services', []);
PVNServices.factory('ItemService', ['$q', 'apiUrl', '$http', 'errorHandler', function($q, apiUrl, $http, errorHandler) {
return {
getAlert: function(alert_id, user_id, categorie_id) {
return $http({ method: 'get',
url: apiUrl + 'getAlert/',
params : {
'alert_id' : alert_id,
'user_id' : user_id,
'categorie_id' : categorie_id,
}
}).then(function(res){return res.result }, errorHandler);
}
}
}]);
var PVNControllers = angular.module('PVN.controllers', ['PVN.services']);
PVNControllers.controller('AppController', ['$scope', 'ItemService', function($scope, ItemService) {
$scope.getAlert = function(alert_id, user_id, categorie_id){
ItemService.getAlert(alert_id, user_id, categorie_id).then(function(alert){
$scope.alert = alert;
}
}
}]);
var PVNDashboard = angular.module('PVN', ['ngSanitize','ngMaterial','PVN.controllers'], function($interpolateProvider) {
$interpolateProvider.startSymbol('<<');
$interpolateProvider.endSymbol('>>');
});
PVNDashboard.run(function() {
moment.locale('nl');
});
<body class="" ng-app="PVN">
</body>
With this structure, would I be able to use the ItemService in the PVNDashboard module because it has the controllers as dependency which in turn has the services as a dependecy. And because of the ng-app being the PVN module will the configuration of the PVN module, moment.js locale in this example case. Also persist in the services because it's the first to run?

Yes, the dependencies all inherit. The intention was that you could create a module for each feature and create apps by injecting several modules.
There is one catch: angular seems to use a single namespace for all injectable things, so it will overwrite anything with the same name. See this blog for example. http://michalostruszka.pl/blog/2015/05/21/angular-dependencies-naming-clash/
I'm not sure if anything has changed and some comments say this should be fixed in angular 2.

Related

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) {
};
}]);

Opinionated Javascript, Passing Resolvers not working (ui-router)

In simple, I'm adapting a simple example to get some practice with opinionated javascript produced by Todd Motto.
Plunkr Of Issue As described originally
Plunkr Of using a Resolver Using traditional resolver
The problem is that my resolver does not have access to the factories queried results. I am not sure why, but the ways I have found to do this are too hackish. What is best way to pass resolved objects here?
In this example I have a simple MVVM page that contains a div with controllerAs syntax.
<div ng-controller="ProjectCtrl as vm">
<h1>Hello: {{vm.message}}</h1>
</div>
And here is the configuration of opinionated javascript.
'use strict';
(function(){
/* Module & Dependencies */
angular.module('project', ['ui.router']);
angular.module('project').factory('Project', Project);
angular.module('project').controller('ProjectCtrl', ProjectCtrl);
angular.module('project').config(ProjectStates);
/* Factory */
function Project($resource){
return $resource('app/rest/projects/:id', {}, {
'query': { method: 'GET', isArray: true},
'get': { method: 'GET'}
});
};
/* Controller */
function ProjectCtrl(Project){
var model = this;
/* No access to anything that's resolved */
model.message = "World";
};
ProjectCtrl.resolve = {
projectResolver: function(Project, $q){
return Project.query();
}
}
/* Routes */
function ProjectStates($stateProvider, $urlRouterProvider, $translateProvider, USER_ROLES) {
/* Default state and parent to all project states */
$stateProvider.state('project',{
url: "/project",
templateUrl: "views/projects.html",
resolve: ProjectCtrl.resolve
});
};
})();
You can use your queried result via dependency injection:
function ProjectCtrl(Project, projectResolver){
var model = this;
// now use your queried result, e.g.
model.projects = projectResolver;
model.message = "World";
};
But this only works if you define your controller and controllerAs properties in the state object:
$stateProvider.state('project',{
url: "/project",
templateUrl: "views/projects.html",
resolve: ProjectCtrl.resolve,
controller: "ProjectCtrl",
controllerAs: "vm"
});
Now the Html should look like this:
...
<body>
<ui-view>
</body>
...
And the views/projects.html file can be something like the following:
<h1>Hello {{vm.message}}!</h1>
<ul><li ng-repeat='project in vm.projects' ng-bind='project.name'></li></ul>
Here is a working demo

AngularJS - minification error

I am trying to minify my js files in my angular app. I know there is some pbs when it comes to dependency injection and minification but I felt like I followed the best practices I found on the internet.
Here is the structure of my code:
My provider:
function myPvdr() {
this.getUrl = function() {
return 'http://some/api/call';
};
this.$get = function($q, $http) {
var self = this;
return {
getData: function(points) {
var d = $q.defer();
$http({
method: 'GET',
url: self.getUrl(),
cache: true
}).success(function(data) {
d.resolve(data);
}).error(function(err) {
d.reject(err);
});
return d.promise;
}
}
}
}
My Controller:
function myCtrl($scope, MyProvider, localStorageService) {
// some code
}
My Module:
angular.module('myApp', ['LocalStorageModule', 'ngAnimate'])
.provider('MyProvider', myPvdr)
// Loading controllers into the module
.controller('myCtrl', ['$scope', 'MyProvider', 'localStorageService', myCtrl])
It works fine when unminified. But after using the uglify task from grunt I get the following error printed to the console:
Error: [$injector:unpr] http://errors.angularjs.org/1.3.0-rc.2/$injector/unpr?p0=aProvider%20%3C-%20a%20%3C-%20MyProvider
What am I doing wrong?
You aren't making your code minfication safe:
There are two options, $injecting or adding it inline when you are declaring the function and using an array with quoted params (things in quotes aren't minified)
https://docs.angularjs.org/tutorial/step_05
Create a $inject property on the controller function which holds an array of strings. Each string in the array is the name of the service to inject for the corresponding parameter. In our example we would write:
function PhoneListCtrl($scope, $http) {...}
PhoneListCtrl.$inject = ['$scope', '$http'];
phonecatApp.controller('PhoneListCtrl', PhoneListCtrl);
Use an inline annotation where, instead of just providing the function, you provide an array. This array contains a list of the service names, followed by the function itself.
function PhoneListCtrl($scope, $http) {...}
phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', PhoneListCtrl]);
If you're already using grunt, consider using the grunt-ng-annotate task. ng-annotate is a tool that automatically inserts proper minification ready syntax where it is needed.

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