I am looking for advice with regards to creating a generic controller which I can reuse in my application. As an example I have created the following GIST:
https://gist.github.com/heide-de/10832576
but here is the relevant code snippet:
angular.module('genericTestApp', [])
.factory('Names', function() {
return {
names: []
};
})
.controller('IndexController', function(Names) {
Names.names = [
{firstname:'Sarah', surname:'Schmitt'},
{firstname:'Paul', surname:'Wells'},
{firstname:'Felix', surname:'the cat'}
]
})
.controller('SecondIndexController', function(Names) {
Names.names = [
{firstname:'Octo', surname:'Cat'},
{firstname:'Beth', surname:'Appleby'},
{firstname:'Fred', surname:'Bloggs'}
]
})
.controller('TableController', function($scope, Names) {
$scope.names = Names.names;
})
What feels very wrong to me is that the TableController in this example relies on the fact that the injected Names factory needs to have been configured previously in the IndexController.
The next developer that comes along will just inject Names, and will have no idea that it needs configuring prior to that.
Is there a better way of doing this with Angular?
You're right in your approach – Configuring a factory in a controller is always a bad idea. A controller is supposed to contain view specific logic, and in my opinion should even only be included when that particular view is rendered. (that's how I write my apps anyway)
To configure anything when the application loads up, you need run, that should contain all the initial stuff that your app needs to do in the beginning.
This might be a good read.
Related
I have seen numerous flavors of this question, but cannot seem to find the right answer for my issue.
The problem with the following is the service cannot share data between the other modules/controllers. It does not act as a singleton. Meaning, if I add windows in dashboard and call windowService.getWindows() I will see the windows I added. But if I do the same from myWidget after adding them via the dashboard, or vice versa, the collection in the service is empty as if the other module/controller hasn't added anything. What am I doing wrong here? I expected a single collection of windows to be retained by the service and be accessible from either of my other modules.
Module 1
var dashboard = angular.module('dashboard', [windowServiceModule]);
dashboard.controller('DashboardController', function DashboardController(windowService) {...stuff});
Module 2
var myWidget = angular.module('myWidget', [windowServiceModule]);
myWidget.controller('MyWidgetController', function MyWidgetController(windowService) {...stuff});
Service
var windowServiceModule = angular.module('windowService', []);
windowServiceModule.service('windowService', function() {
var windows = [];
this.addWindow = function(win)
{
this.windows.push(win)
}
this.getWindows = function()
{
return this.windows;
}
});
EDIT: while walking through other answers, I tried this: Share a single service between multiple angular.js apps
That exposed a part of the problem, I think. The above suggests iterating over a $rootScope collection, but in my case, there is only one $rootScope in the collection and it is not the same between dashboard and myWidget. As it is, myWidget is in an iframe and now I'm thinking I need to take a different approach, currently testing a solution similar to the link I left in the comments.
Ok, I'm pretty sure my initial code would have worked fine if the myWidget wasn't being loaded in an iframe. I have a dashboard that can open windows embedded or popped out and I wanted to use the service to manage the windows and handle passing state back and forth since popping out the iframe requires a reload of the page.
Anyways, I now have something like this in place in the service and it is working:
var windowServiceModule = angular.module('windowService', []);
windowServiceModule.service('windowService', function($window) {
...stuff
var parentScope = $window.parent.getScope();
...other stuff
After that you can access anything in the parent's scope with parentScope. I pass in the angular $window.
Just for reference, getScope() already existed in the parent JSP file. This is what is looks like:
function getScope()
{
return angular.element($("#DashboardController")).scope();
}
I'm working with a web application that allows designers to create pages by writing html in a combination with angularjs directives that are created by myself and other developers. I'm struggling with the best way to populate the directives with data.
Initial attempt was to keep all directives completely self contained. So a product page for example might look like the following (with custom html around all of this - left out for clarity):
<product>
<product-information></product-information>
<product-image></product-image>
<product-quantities></product-quantities>
<product-add-to-cart-button></product-add-to-cart-button>
</product>
If the directives need data (which almost all do) they would use a service to call a web API and get the data they need. There are a few issues that have arisen with this approach.
The directives often need some information from a parent or sibling. In the example below, product-image likely needs the ProductID so it can get the correct image. In this case I have to rely on getting that information from a querystring parameter or store in an angularJS service that is initially populated by the parent directive.
Lots of API calls. With every directive making it's own API calls I'm now ending up with pages that have 15+ API calls to load, and that can be expected to grow over time. Even though a lot of the data may be closely related (even in the same database table). Obviously this is less than ideal.
So I've started changing my approach with the second pass through. Now the directives are set up like a tree structure which each directive expecting it's data requirements to be passed in through an attribute. Here's an example:
<product-image product-url="vm.product.imageUrl" ng-if="vm.product"></product-image>
This solves the problem #2 of too many API and database calls but exposes too many internals to the designer. Now the designer has to know to pass in product-url and must understand that there is a vm.product behind the scenes. He may even need to understand some angularJS (ng-if). I've seen this pattern used a lot even in Angular2 with Inputs. Seems fine for developer usage but not for designer used directives, we want to hide the inner workings and complexity while giving the designer the power of controlling the layout.
Finally, I'm considering using the parent controller to populate everything that might be needed on the page. Then all the child directives will just use a service like they are now but instead of calling an API, the data is already loaded. The directives remain simple and mostly self-contained, but their data load is triggered by a parent. The only issue I have with this is that we may end up loading a lot of data that is unused because of directives not being used by the designer. But I feel like this is a necessary trade off.
Has anyone built something similar, are there any possible approaches I am missing?
You can use a component tree with your service.
Starting with a designer friendly:
<div ng-app="MyApp">
<product id="1">
<product-image></product-image>
</product>
</div>
Something like this sorta works:
angular.module('MyApp', [])
.factory('api', function($q){
return {
loadProduct: function(id){
return $q.when({
id: id,
imageUrl: 'http://i2.cdn.turner.com/cnnnext/dam/assets/160407085910-setsuna-main-overlay-tease.jpg'
})
}
};
})
.component('product', {
transclude: true,
bindings: {
id: '='
},
template: [
'<div ng-transclude></div>'
].join(''),
controller: function(api) {
var self = this;
this.$onInit = function() {
self.data = api.loadProduct(this.id);
};
}
})
.component('productImage', {
require: {
product: '^product'
},
bindings: {
},
template: [
'<pre>{{ $ctrl.url | json }}</pre>'
].join(''),
controller: function() {
var self = this;
this.url = false;
this.$onInit = function() {
this.product.data.then(function(data){
self.url = data.imageUrl;
})
};
}
})
See this CodePen:
http://codepen.io/anon/pen/wGmEKP?editors=1011
I am trying to call an API end point once a user clicks a button holding a myNavigator.pushPage() request. However,I can not get the $scope data generated from the $http.get request to be passed to the new page.
If I test using console.log('test'); inside the .success of the $http.get request I successfully get the log info in the console but any data held in $scope.var = 'something'; does not gets passed to the page! Really confused!
$scope.historyDetails = function(id){
var options = {
animation: 'slide',
onTransitionEnd: function() {
$http.get('http://xxx-env.us-east-1.elasticbeanstalk.com/apiget/testresult/testId/'+id).success(function(data) {
$scope.testscore = 'something'; // this is not getting passed to page!
console.log('bahh'); // But I see this in console
});
}
};
myNavigator.pushPage("activity.html", options);
}
Page:
<ons-page ng-controller="HistoryController">
...
<span style="font-size:1.2em">{{testscore}} </span><span style="font-size:0.5em;color:#555"></span>
...
</ons-page>
Yes, that's so because both pages has different controllers, resulting in different scopes. One can not access variables from one scope to another.
Hence one solution in this case can be using rootScope service.
Root Scope is parent scope for all scopes in your angular application.
Hence you can access variable of root scopes from any other scope, provided that you are injecting $rootScope service in that controller.
to know more about rootScope check this link.
Good luck.
Update 1:
check these articles
http://www.dotnet-tricks.com/Tutorial/angularjs/UVDE100914-Understanding-AngularJS-$rootScope-and-$scope.html
https://toddmotto.com/all-about-angulars-emit-broadcast-on-publish-subscribing/
As Yogesh said the reason you're not getting your values is because if you look at $scope.testscore and try to find where is the $scope defined you will see that it's an argument for the controller function (thus it's only for that controller).
However we can see that the controller is attached to the page and you are pushing another page.
So in that case you have several options:
Use the $rootScope service as Yogesh suggested (in that case accept his answer).
Create your own service/factory/etc doing something similar to $rootScope.
(function(){
var historyData = {};
myApp.factory('historyData', function() {
return historyData;
});
})();
Technically you could probably make it more meaningful, but maybe these things are better described in some angular guides.
If you have multiple components sharing the same data then maybe you could just define your controller on a level higher - for example the ons-navigator - that way it will include all the pages. That would be ok only if your app is really small though - it's not recommended for large apps.
If this data is required only in activity.html you could just get it in that page's controller. For example:
myApp.controller('activityController', function($scope, $http) {
$http.get(...).success(function(data) {
$scope.data = data;
});
}
But I guess you would still need to get some id. Anyway it's probably better if you do the request here, now you just need the id, not the data.
You could actually cheat it with the var directive. If you give the activity page <ons-page var="myActivityPage"> then you will be able to access it through the myActivityPage variable.
And the thing you've been searching for - when you do
myNavigator.pushPage("activity.html", options);
actually the options is saved inside the ons-page of activity.html.
So you can do
myNavigator.pushPage("activity.html", {data: {id: 33}, animation: 'slide'});
And in the other controller your id will be myActivityPage.options.data.id.
If you still insist on passing all the data instead of an id - here's a simple example. In the newer versions of the 2.0 beta (I think since beta 6 or 7) all methods pushPage, popPage etc return a promise - which resolve to the ons-page, making things easier.
$scope.historyDetails = function(id){
myNavigator.pushPage("activity.html", {animation: 'slide'}).then(function(page) {
$http.get('...' + id).success(function(data) {
page.options.data = data;
});
});
});
Side note: You may want to close the question which you posted 5 days ago, as it's a duplicate of this one (I must've missed it at that time).
I'm quite new to Angular, and I'm adapting a simple CRUD app written using standard controllers and ngResource to use the components introduced in 1.5. None of the docs and resources I've found so far discuss how to:
create a new item from scratch
integrate with ngResource
so I'm wondering if anyone can give some pointers on how best to proceed.
My existing app has a simple factory declaring a resource entity, and a single controller that
instantiates a new instance of the resource: $scope.newEntity = new Entity();
populates the $scope with a list of the resources retrieved from the backend: Entity.query(function (data) { $scope.entities = data; });
provides a couple of functions for deleting, updating, and saving the resource to the backend.
In the HTML I have a form that works with $scope.newEntity and the controller saving method to save the new entity to the backend. I also have an ng-repeat that lists the entries stored in $scope.entities, with a couple of additional ng-clicks to perform some editing and deleting.
What I want to do now is implement some inline editing in the list. I know I can do this with my existing approach, but I want to cleanly reuse the form validation functionality I have in the existing entity creation form in the entity editing code, without duplicating. Components seem like a natural fit for that to my (admittedly inexperienced) eyes.
With the component-based approach, I have followed the documentation at https://docs.angularjs.org/guide/component under Example of a component tree, and created an entity-list and entity-detail component. These work okay so far, and I think I can figure out how to wire up the on-delete and on-update events. What I can't figure out is how to approach an on-create event.
Should I use a completely separate controller with my existing simple form to handle the creation event? If so, how can I get the existing list to automatically update? Will that creation event propagate across to the list controller?
Or am I missing something in the existing list controller? Or is the entity creation a special case for the detail controller?
I'm looking specifically for information about how to implement this using Angular components and ngResource, as I'd also like to be ready for Angular 2. Unless components and resources aren't meant to work together please don't post answers about how to achieve this using a completely different approach, or how to reuse HTML code without components. Thanks!
Actually the C in CRUD is realy simple. You were probably expecting an on-create method to be used from your entity-detail. entity-list should take care of the creation of the details however.
Here is the working code
I extended the example from the guide https://docs.angularjs.org/guide/component under Example of a component tree you were reading too and added the create:
(function () {
'use strict';
angular
.module('componentCrud')
.component('heroList', {
templateUrl: "component/hero-list.component.html",
controller : [
HeroListController
]
});
function HeroListController() {
var ctrl = this;
ctrl.list = createHeroes();
ctrl.updateHero = updateHero;
ctrl.deleteHero = deleteHero;
ctrl.createHero = createHero;
function createHero(){
ctrl.list.push({
name : 'Crazy Newling',
location: 'Morgues'
})
}
function updateHero(hero, prop, value) {
hero[prop] = value;
}
function deleteHero(hero) {
var idx = ctrl.list.indexOf(hero);
if (idx >= 0) {
ctrl.list.splice(idx, 1);
}
}
function createHeroes() {
return [{
name : 'Superman',
location: ''
},
{
name : 'Batman',
location: 'Wayne Manor'
}
]
}
}
})();
Then in HTML you just add a create button:
<b>Heroes</b><br>
<hero-detail ng-repeat="hero in $ctrl.list"
hero="hero"
on-delete="$ctrl.deleteHero(hero)"
on-update="$ctrl.updateHero(hero, prop, value)"></hero-detail>
<button ng-click="$ctrl.createHero()">Hire a new Hero</button>
I hope it is going to help you!
The use case is that I have a custom service which needs to be configured based on user input.
So I created a service provider for that service, but now I can only config the provider inside the module.config call, which I think it is loaded only once during the life of the app.
Any solution for this?
Have your service provide some sort of a configuration API to set these configuration values as needed. As a simple example you might do something like this:
function myController(myService, $scope) {
$scope.config = myService.config;
// You can manipulate various config options now through direct binding.
}
However remember that AngularJS services are singletons, which means they will all share the same state. If you need different state, or need a "new" one each time you will want to do something more like the way $resource or $http works which is basically a factory.
function myController(myService, $scope) {
$scope.config = { value1: 'default', value2: 'default' };
var thisService = myService($scope.config);
// You can manipulate various config options now through direct binding.
}
Just remember that services are basically objects and you can manipulate them as per your design as you need. So these are probably not the only, or even necessarily the best way to accomplish your goal. You have total flexibility here.
I don't think a service's provider is what you are looking for here because it is precisely just what you describe it.
As Chris stated, angular services are singletons. However, if you want your service to output "instances" based on user input, I like the following approach.
function myController(myService, $scope) {
var config = { value1: 'default', value2: 'default' };
$scope.newInstance=myService.create(config);
}
app.service('myService', [function(){
function serviceInstance = function (config){
//take config and return output object
}
return {
create: function(config){
return new serviceInstance(config);
}
}
}]);
I haven't thought about being able to manipulate the config variables as Chris suggested. I don't think it would work in my example but you could data bind to the $scope.newInstance