I have $scope.question which has questions for all the page.
I want to loop through page wise questions. for this I wrote a function questionsCtrl. This function I am calling in config while setting route.
but here I am getting undefined.
Please suggest how to get data for page from $scope.questions.
app.js
(function () {
"use strict";
var app = angular.module("autoQuote",["ui.router","ngResource"]);
app.config(["$stateProvider","$urlRouterProvider", function($stateProvider,$urlRouterProvider){
$urlRouterProvider.otherwise("/");
$stateProvider
.state("step1", {
url : "",
controller: "questionsCtrl",
resolve: {
pageQuestion: $scope.questions
}
})
.state("step2", {
url : "/step2",
controller: "questionsCtrl",
})
}]
);
}());
questionCtrl.js
(function () {
"use strict";
angular
.module("autoQuote")
.controller("questionsCtrl",["$scope","$state","$q",questionsCtrl]);
function questionsCtrl($scope,$state,$q) {
console.log('here in get question for page: '+$state.current.name);
//var deferred = $q.defer();
if($state.current.name == "" || $state.current.name == "step1")
{
$scope.pageQuestion = $scope.RC1_Cars_v2;
}
else if($state.current.name == "step2")
{
$scope.pageQuestion = $scope.RC1_Drivers_v2;
}
//return deferred.promise;
console.log($scope);
}
}());
html
<div class="container-fluid col-md-8">
<div ng-controller="autoQuoteCtrl">
<div ng-repeat="que in pageQuestion">
<!-- pagequestion will be in loop here -->
</div>
</div>
</div>
here is my plunker http://plnkr.co/edit/kLc6DPqzQgLNpUBaLtZi?p=preview for complete code.
The controller for a view contains view specific logic. If you add a template to your states via template or templateUrl you will have access to your controller's scope within that template. As far as I understand your problem you want to provide data to your content before the state is being displayed. If you take a look at the documentation the resolve property is exactly what you are looking for:
You can use resolve to provide your controller with content or data that is custom to the state. resolve is an optional map of dependencies which should be injected into the controller.
However, you are using it incorrectly. You won't have access to $scope in a config block. What you have to do is provide a function to your resolve property as follows:
resolve: {
pageQuestion: function(myService) {
return myService.getData();
}
}
As you can see in the code snippet above, I am using a function which injects a service called myService. The service is responsible for getting your data. In the example I assume that the service has a method getData which returns a promise. The ui-router will now wait for all promises to be resolved before showing the state. Once you have created such function, you can use the name of your resolve property (in this case pageQuestion) to inject your data into your view controller. So your question controller would look like this:
function questionsCtrl(pageQuestion) {
this.pageQuestion = pageQuestion;
}
You simply inject your resolve into your controller and assign it to a view variable.
Notice, that I am assuming the controllerAs syntax for your state, so I can ommit the $scope and directly use this inside the controller. All you have to do is to add a property controllerAs to your state configuration as follows:
.state("step1", {
...
controller: "questionsCtrl",
controllerAs: 'vm'
})
This has the advantage, that you won't run into scope problems (this has something to do with scope inheritance) in the long run. This is a great article on scopes.
Your view should look like this now:
<div class="container-fluid col-md-8">
<div ng-repeat="que in vm.pageQuestion">
<!-- pagequestion will be in loop here -->
</div>
</div>
Here you access your data via vm, which comes from the controllerAs property in your state config. I would also suggest to use the template for your state and remove occurrences of ng-controller. You won't be needing this because you have a view controller declared for your state already. Additionally, ng-controller's make it difficult to propertly manage your state. You end up with a scope soup, and this is something you definietly want to avoid.
Related
One of the great things about angular is that you can have independent Modules that you can reuse in different places. Say that you have a module to paint, order, and do a lot of things with lists. Say that this module will be used all around your application. And finally, say that you want to populate it in different ways. Here is an example:
angular.module('list', []).controller('listController', ListController);
var app = angular.module('myapp', ['list']).controller('appController', AppController);
function AppController() {
this.name = "Misae";
this.fetch = function() {
console.log("feching");
//change ListController list
//do something else
}
}
function ListController() {
this.list = [1, 2, 3];
this.revert = function() {
this.list.reverse();
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div class="app" ng-app="myapp" ng-controller="appController as App">
<div class="filters">
Name:
<input type="text" ng-model="App.name" />
<button ng-click="App.fetch()">Fetch</button>
</div>
<div class="list" ng-controller="listController as List">
<button ng-click="List.revert()">Revert</button>
<ul>
<li ng-repeat="item in List.list">{{item}}</li>
</ul>
</div>
</div>
Now, when you click on Fetch button, you'll send the name (and other filters and stuff) to an API using $http and so on. Then you get some data, including a list of items you want to paint. Then you want to send that list to the List module, to be painted.
It has to be this way because you'll be using the list module in diferent places and it will always paint a list and add some features like reordering and reversing it. While the filters and the API connection will change, your list behaviour will not, so there must be 2 different modules.
That said, what is the best way to send the data to the List module after fetching it? With a Service?
You should be using Angular components for this task.
You should create a module with a component that will be displaying lists and providing some actions that will modify the list and tell the parent to update the value.
var list = angular.module('listModule', []);
list.controller('listCtrl', function() {
this.reverse = function() {
this.items = [].concat(this.items).reverse();
this.onUpdate({ newValue: this.items });
};
});
list.component('list', {
bindings: {
items: '<',
onUpdate: '&'
},
controller: 'listCtrl',
template: '<button ng-click="$ctrl.reverse()">Revert</button><ul><li ng-repeat="item in $ctrl.items">{{ item }}</li></ul>'
});
This way when you click on "Revert" list component will reverse the array and execute the function provided in on-update attribute of HTML element.
Then you can simply set your app to be dependent on this module
var app = angular.module('app', ['listModule']);
and use list component
<list data-items="list" data-on-update="updateList(newValue)"></list>
You can see the rest of the code in the example
It is very simple. Please take a look at this small snippet. comments are added to highlight.
You can have a common module that contains all the data that needs to be shared across modules by two steps
adding module dependency
injecting corresponding provider in the respective module's controller
angular.module('commonAppData', []).factory('AppData',function(){
var a,b,c;
a=1;
return{
a:a,
b:b,
c:c
}
})
angular.module('list', ['commonAppData']).controller('listController', ListController);
var app = angular.module('myapp', ['list','commonAppData']).controller('appController', AppController);
function AppController(AppData) {
//assigning a variable
AppData.a=100;
this.name = "Misae";
this.fetch = function() {
console.log("feching");
//change ListController list
//do something else
}
}
function ListController(AppData) {
//Using the data sent by App Controller
this.variableA=AppData.a;
this.list = [1, 2, 3];
this.revert = function() {
this.list.reverse();
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div class="app" ng-app="myapp" ng-controller="appController as App">
<div class="filters">
Name:
<input type="text" ng-model="App.name" />
<button ng-click="App.fetch()">Fetch</button>
</div>
<div class="list" ng-controller="listController as List">
<b>Shared variable : {{List.variableA}}</b>
<br>
<button ng-click="List.revert()">Revert</button>
<ul>
<li ng-repeat="item in List.list">{{item}}</li>
</ul>
</div>
</div>
There are a few ways to handle objects acress multiple controllers. Here are two.
1. Using Angulars $rootScope
You can assign an object to $rootScopewhich will hold up all information. This object can be passed into every controller through Angulars dependency injection. Also you can listen up to changes on your object by watching it through $watch or $emit.
Using $rootScope is an easy way, but may lead to performance issues on larger applications.
2. Using services
Angular provides a possibility to share object through services. Instead of defining your object inside of your controller, you could also do that inside of a service. Doing so you could inject that service into any controller and use it's values across your application.
function AppController(listService) {
// reference to the injected data
}
function ListController(listService) {
// update data
}
There are many ways to pass data from one module to another module and many ppl have suggested different ways.
One of the finest way and cleaner approach is using a factory instead of polluting $rootScope or using $emit or $broadcast or $controller.If you want to know more about how to use all of this visit this blog on
Accessing functions of one controller from another in angular js
By simply inject the factory you have created in main module and add child modules as dependancy in main module, then inject factory in child modules to access the factory objects.
Here is a sample example on how to use factory for sharing data across the application.
Lets create a factory which can be used in entire application across all controllers to store data and access them.
Advantages with factory is you can create objects in it and intialise them any where in the controllers or we can set the defult values by intialising them in the factory itself.
Factory
app.factory('SharedData',['$http','$rootScope',function($http,$rootScope){
var SharedData = {}; // create factory object...
SharedData.appName ='My App';
return SharedData;
}]);
Service
app.service('Auth', ['$http', '$q', 'SharedData', function($http, $q,SharedData) {
this.getUser = function() {
return $http.get('user.json')
.then(function(response) {
this.user = response.data;
SharedData.userData = this.user; // inject in the service and create a object in factory ob ject to store user data..
return response.data;
}, function(error) {
return $q.reject(error.data);
});
};
}]);
Controller
var app = angular.module("app", []);
app.controller("testController", ["$scope",'SharedData','Auth',
function($scope,SharedData,Auth) {
$scope.user ={};
// do a service call via service and check the shared data which is factory object ...
var user = Auth.getUser().then(function(res){
console.log(SharedData);
$scope.user = SharedData.userData;// assigning to scope.
});
}]);
In HTML
<body ng-app='app'>
<div class="media-list" ng-controller="testController">
<pre> {{user | json}}</pre>
</div>
</body>
It depends on the circumstance. Is there a parent child relationship? Is the relationship unknown, or you simply want to avoid having to worry about it at all?
I think this post lays it out well (it always seems to be helpful):
http://mean.expert/2016/05/21/angular-2-component-communication/
AngularJS provides $on, $emit, and $broadcast services for event-based communication between controllers.
So, if we want to pass data from the inner controller (listController) to outer controller (appController) then we have to use $emit. It dispatches an event name upwards through the scope hierarchy and notify to the registered $rootScope.
Working Plunker : https://plnkr.co/edit/szf9jHvvOPLOvQc5sQI2?p=preview
This plunker is not as per the exact requirement as I don't know the API response but this sample plunker describe the problem statement.
I think you can use the publisher-subscriber pattern implemented in $rootScope.
In the ListController you have to inject $rootScope and after that you have to subscribe to an arbitrary called event that could have the pattern _data_received
$rootScope.$on('ingredients_data_received', function(ingredients) { prepare_recipe();});
so in your AppController you have to call $rootScope.$emit once the data of that list has been received
$rootScope.$emit('ingredients_data_received', ingredients);
This is just a way to pass data, you can also push those data or the promise in a $rootScope property but this is not a good practice, or you can create your own Service that manage the data for you (remember that you are working with a frontend framework so the controller has to control the view, the business logic has to be transfered to a service, not to a crontroller).
I like to use angular resource with a caching layer to persist data to multiple controllers using a singular method. This has a few benefits namely any controller has the same entry point to data. Keep in mind sometimes you need to fetch fresh data when navigating from one portion of the site to another so persisting http data is not always ideal.
'use strict';
(function() {
angular.module('customModule')
.service('BookService', BookService);
BookService.$inject = ['$resource'];
function BookService($resource) {
var BookResource = $resource('/books', {id: '#id'}, {
getBooks: {
method: 'GET',
cache: true
}
});
return {
getBooks: getBooks
};
function getBooks(params) {
if (!params) {
params = {};
}
return BookResource.getBooks(params).$promise;
}
}
})();
In any controller
BookService.getBooks().then(function(books) {
//books will be cached so invoking the call will return the same set of data anywhere
});
Services in angular are used to share functionality between components. Please try to keep them simple and with a single responsibility/purpose.
An idea would be creating a store service that will cache every response from the api in your application. Then you don't have to request it every time.
Hope it helps! ;)
I am trying to fetch some data from server before controller get render.
I have found many answers for it with respect to routeProvider.
But my main issue is my controller does not bound with any route.
So is there any way to make this possible?
I have controller in following ways...
<!-- HERE I WANT TO BLOCK RENDERING TILL DATA GET LOAD -->
<AppController>
<ng-view>
</AppController>
It sounds like a resolve is what you are looking for, but if you are not using a routing table for this controller, you'll not have this option. Why not just resolve an asynchronous call in your controller, and set scope variables inside the callback. This is what I interpret your desire to await controller "rendering", whereas a resolve through a route table would await controller instantiation. Observe the following...
module.controller('ctrl', function($scope, $http) {
$http.get('/uri').then(function(response) {
// set $scope variables here
});
console.log('executed first');
});
You could also set a variable to prevent the associated view from rendering if your data call is lengthy. This would prevent the UI from "dancing." Observe the following changes to the above example...
<div ng-controller="ctrl" ng-show="resolved"></div>
module.controller('ctrl', function($scope, $http) {
$http.get('/uri').then(function(response) {
$scope.resolved = true; // show rendering
});
});
JSFiddle Link - simplified demo
JSFiddle Link - demo ng-if
One idea will work
in html controller:
<p ng-if="notLoadedContent">Wait</p>
<div ng-if="!notLoadedContent">Content fetched</div>
And in Controller all controller is inside one function will start all, and the controller will be :
fetch(init)
$scope.notLoaded = true;
function init(){
$scope.notLoaded=false;
}
hope it help you
I have an AngularJS app which grab data from PHP via AJAX and permit user to edit it through few steps.
Structure of the app is very simple :
I have a main controller which is loaded from ng-controller directive.
<div ng-controller="MainCtrl">
<!-- All my app take place here, -->
<!-- so all my others controllers are in MainCtrl scope -->
<div ng-view></div>
</div>
I have one controller by editing steps (ex. general info, planner, validation, etc.). Each controller is loaded by the $routeProvider (inside MainCtrl scope).
My problem is when I load (or refresh) the page, MainCtrl make an AJAX request to retrieve data to edit. The controller attached to $routeProvider is loaded before AJAX request is finished, so I can't use data grabbed by MainCtrl.
I want to defer $routeProvider loading route while AJAX request is not ended. I think I have to use the $q provider, but I can't prevent route loading.
I have tried this (in MainCtrl) and controller is still rendered premature :
$scope.$on('$routeChangeStart', function(event, current, previous) {
$scope.pathLoaded.promise.then(function() {
// Data loaded => render controller
return true;
});
// Stop controller rendering
return false;
});
And AJAX call is defined like this :
$scope.pathLoaded = $q.defer();
// Edit existing path
$http.get(Routing.generate('innova_path_get_path', {id: EditorApp.pathId}))
.success(function (data) {
$scope.path = data;
$scope.pathLoaded.resolve();
})
.error(function(data, status) {
// TODO
});
So the question is : is it the good way to achieve this ? And if yes, how can I defer controller rendering ?
Thanks for help.
You can use the resolve property of routes, execute the AJAX there and pass the result to your controller. In the route definition:
$routeProvider.when("path", {
controller: ["$scope", "mydata", MyPathCtrl], // NOTE THE NAME: mydata
templateUrl: "...",
resolve: {
mydata: ["$http", function($http) { // NOTE THE NAME: mydata
// $http.get() returns a promise, so it is OK for this usage
return $http.get(...your code...);
}]
// You can also use a service name instead of a function, see docs
},
...
});
See docs for more details. The controller for the given path will not be called before all members in the resolve object are resolved.
What is the better solution to hide template while loading data from server?
My solution is using $scope with boolean variable isLoading and using directive ng-hide, ex: <div ng-hide='isLoading'></div>
Does angular has another way to make it?
You can try an use the ngCloak directive.
Checkout this link http://docs.angularjs.org/api/ng.directive:ngCloak
The way you do it is perfectly fine (I prefer using state='loading' and keep things a little bit more flexible.)
Another way of approaching this problem are promises and $routeProvider resolve property.
Using it delays controller execution until a set of specified promises is resolved, eg. data loaded via resource services is ready and correct. Tabs in Gmail work in a similar way, ie. you're not redirected to a new view unless data has been fetched from the server successfully. In case of errors, you stay in the same view or are redirected to an error page, not the view, you were trying to load and failed.
You could configure routes like this:
angular.module('app', [])
.config([
'$routeProvider',
function($routeProvider){
$routeProvider.when('/test',{
templateUrl: 'partials/test.html'
controller: TestCtrl,
resolve: TestCtrl.resolve
})
}
])
And your controller like this:
TestCtrl = function ($scope, data) {
$scope.data = data; // returned from resolve
}
TestCtrl.resolve = {
data: function ($q, DataService){
var d = $q.defer();
var onOK = function(res){
d.resolve(res);
};
var onError = function(res){
d.reject()
};
DataService.query(onOK, onError);
return d.promise;
}
}
Links:
Resolve
Aaa! Just found an excellent (yet surprisingly similar) explanation of the problem on SO HERE
That's how I do:
$scope.dodgson= DodgsonSvc.get();
In the html:
<div x-ng-show="dodgson.$resolved">We've got Dodgson here: {{dodgson}}. Nice hat</div>
<div x-ng-hide="dodgson.$resolved">Latina music (loading)</div>
I'm trying to dynamically assign a controller for included template like so:
<section ng-repeat="panel in panels">
<div ng-include="'path/to/file.html'" ng-controller="{{panel}}"></div>
</section>
But Angular complains that {{panel}} is undefined.
I'm guessing that {{panel}} isn't defined yet (because I can echo out {{panel}} inside the template).
I've seen plenty of examples of people setting ng-controller equal to a variable like so: ng-controller="template.ctrlr". But, without creating a duplicate concurrant loop, I can't figure out how to have the value of {{panel}} available when ng-controller needs it.
P.S. I also tried setting ng-controller="{{panel}}" in my template (thinking it must have resolved by then), but no dice.
Your problem is that ng-controller should point to controller itself, not just string with controller's name.
So you might want to define $scope.sidepanels as array with pointers to controllers, something like this, maybe:
$scope.sidepanels = [Alerts, Subscriptions];
Here is the working example on js fiddle http://jsfiddle.net/ADukg/1559/
However, i find very weird all this situation when you might want to set up controllers in ngRepeat.
To dynamically set a controller in a template, it helps to have a reference to the constructor function associated to a controller. The constructor function for a controller is the function you pass in to the controller() method of Angular's module API.
Having this helps because if the string passed to the ngController directive is not the name of a registered controller, then ngController treats the string as an expression to be evaluated on the current scope. This scope expression needs to evaluate to a controller constructor.
For example, say Angular encounters the following in a template:
ng-controller="myController"
If no controller with the name myController is registered, then Angular will look at $scope.myController in the current containing controller. If this key exists in the scope and the corresponding value is a controller constructor, then the controller will be used.
This is mentioned in the ngController documentation in its description of the parameter value: "Name of a globally accessible constructor function or an expression that on the current scope evaluates to a constructor function." Code comments in the Angular source code spell this out in more detail here in src/ng/controller.js.
By default, Angular does not make it easy to access the constructor associated to a controller. This is because when you register a controller using the controller() method of Angular's module API, it hides the constructor you pass it in a private variable. You can see this here in the $ControllerProvider source code. (The controllers variable in this code is a variable private to $ControllerProvider.)
My solution to this issue is to create a generic helper service called registerController for registering controllers. This service exposes both the controller and the controller constructor when registering a controller. This allows the controller to be used both in the normal fashion and dynamically.
Here is code I wrote for a registerController service that does this:
var appServices = angular.module('app.services', []);
// Define a registerController service that creates a new controller
// in the usual way. In addition, the service registers the
// controller's constructor as a service. This allows the controller
// to be set dynamically within a template.
appServices.config(['$controllerProvider', '$injector', '$provide',
function ($controllerProvider, $injector, $provide) {
$provide.factory('registerController',
function registerControllerFactory() {
// Params:
// constructor: controller constructor function, optionally
// in the annotated array form.
return function registerController(name, constructor) {
// Register the controller constructor as a service.
$provide.factory(name + 'Factory', function () {
return constructor;
});
// Register the controller itself.
$controllerProvider.register(name, constructor);
};
});
}]);
Here is an example of using the service to register a controller:
appServices.run(['registerController',
function (registerController) {
registerController('testCtrl', ['$scope',
function testCtrl($scope) {
$scope.foo = 'bar';
}]);
}]);
The code above registers the controller under the name testCtrl, and it also exposes the controller's constructor as a service called testCtrlFactory.
Now you can use the controller in a template either in the usual fashion--
ng-controller="testCtrl"
or dynamically--
ng-controller="templateController"
For the latter to work, you must have the following in your current scope:
$scope.templateController = testCtrlFactory
I believe you're having this problem because you're defining your controllers like this (just like I'm used to do):
app.controller('ControllerX', function() {
// your controller implementation
});
If that's the case, you cannot simply use references to ControllerX because the controller implementation (or 'Class', if you want to call it that) is not on the global scope (instead it is stored on the application $controllerProvider).
I would suggest you to use templates instead of dynamically assign controller references (or even manually create them).
Controllers
var app = angular.module('app', []);
app.controller('Ctrl', function($scope, $controller) {
$scope.panels = [{template: 'panel1.html'}, {template: 'panel2.html'}];
});
app.controller("Panel1Ctrl", function($scope) {
$scope.id = 1;
});
app.controller("Panel2Ctrl", function($scope) {
$scope.id = 2;
});
Templates (mocks)
<!-- panel1.html -->
<script type="text/ng-template" id="panel1.html">
<div ng-controller="Panel1Ctrl">
Content of panel {{id}}
</div>
</script>
<!-- panel2.html -->
<script type="text/ng-template" id="panel2.html">
<div ng-controller="Panel2Ctrl">
Content of panel {{id}}
</div>
</script>
View
<div ng-controller="Ctrl">
<div ng-repeat="panel in panels">
<div ng-include src="panel.template"></div>
</div>
</div>
jsFiddle: http://jsfiddle.net/Xn4H8/
Another way is to not use ng-repeat, but a directive to compile them into existence.
HTML
<mysections></mysections>
Directive
angular.module('app.directives', [])
.directive('mysections', ['$compile', function(compile){
return {
restrict: 'E',
link: function(scope, element, attrs) {
for(var i=0; i<panels.length; i++) {
var template = '<section><div ng-include="path/to/file.html" ng-controller="'+panels[i]+'"></div></section>';
var cTemplate = compile(template)(scope);
element.append(cTemplate);
}
}
}
}]);
Ok I think the simplest solution here is to define the controller explicitly on the template of your file. Let's say u have an array:
$scope.widgets = [
{templateUrl: 'templates/widgets/aWidget.html'},
{templateUrl: 'templates/widgets/bWidget.html'},
];
Then on your html file:
<div ng-repeat="widget in widgets">
<div ng-include="widget.templateUrl"></div>
</div>
And the solution aWidget.html:
<div ng-controller="aWidgetCtrl">
aWidget
</div>
bWidget.html:
<div ng-controller="bWidgetCtrl">
bWidget
</div>
Simple as that! You just define the controller name in your template. Since you define the controllers as bmleite said:
app.controller('ControllerX', function() {
// your controller implementation
});
then this is the best workaround I could come up with. The only issue here is if u have like 50 controllers, u'll have to define them explicitly on each template, but I guess u had to do this anyway since you have an ng-repeat with controller set by hand.