This is my first attempt with angularjs and ionic-framework.
I have an example json file and i'd like to display onscreen some data from it.
The displaying-data bit works, but i'd like to populate a "details" page with some info that are stored as an abject inside the main json file, and i need to use the id from the url to select to display only the data that i need.
Here's some code:
App.js
angular.module('hgapp', ['ionic', 'hgapp.controllers', 'ngResource'])
.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('app', {
url: '/app',
abstract: true,
templateUrl: 'templates/menu.html',
controller: 'AppCtrl'
})
.state('app.details', {
url: '/details/:roomID',
views: {
'menuContent': {
templateUrl: 'templates/details.html',
controller: 'DetailsCtrl'
}
}
})
$urlRouterProvider.otherwise('/app/home');
});
Controllers.js
angular.module('hgapp.controllers', ['hgapp.services'])
.controller('AppCtrl', function ($scope, HGJson) {
HGJson.get(function (data) {
$scope.rooms = data.data;
})
})
.controller('DetailsCtrl', function ($scope, $stateParams, HGJson) {
$scope.roomID = $stateParams.roomID;
console.log($stateParams.roomID);
})
services.js
angular.module('hgapp.services', ['ngResource'])
.factory('HGJson', function ($resource) {
return $resource('json/data.json')
});
Data.json (Just a simplified example)
{
tm: 00000000,
errors: 0,
data: {
{id: 0, name: Value 0, url:url-0},
{id: 1, name: Value 1, url:url-1},
{id: 2, name: Value 2, url:url-2}
}
details.html
<ion-view view-title="Details">
<ion-content>
<h1>{{roomID}}</h1>
</ion-content>
In the details page i'm printing the roomID just to see if the controller (detailsCtrl) works, and i have the correct id printed every time. Now, the bit where i'm stuck is how to manipulate the data from HGJson service so that it allows my to print on data from the right room id.
I hope this question is clear enought, if not, feel free to ask for more clarification.
Thanks a lot
EDIT
At the end i solved it adding this to my controller.js file:
.controller('DetailsCtrl', function ($scope, $stateParams, HGJson) {
HGJson.get(function (data) {
angular.forEach(data.data, function (item) {
if (item.id == $stateParams.roomID)
$scope.currentRoom = item;
});
});
})
Just do the same thing as what you're doing in the app controller, but find the room you want in the returned JSON:
HGJson.get(function (data) {
$scope.room = data.data.filter(function(room) {
return room.id == $stateParams.roomID);
})[0];
});
You could also put that filtering functionality in your service, so that in the future, when you have a real dynamic backend, you call a different URL returning only the requested room rather than calling a URL that returns all the rooms.
angular.module('hgapp.services')
.factory('HGJson', function ($http) {
return {
getRooms: function() {
return $http.get('json/data.json').then(function(response) {
return response.data;
});
},
getRoom: function(roomId) {
return $http.get('json/data.json').then(function(response) {
return response.data.data.filter(function(room) {
return room.id == roomID;
})[0];
});
}
};
});
Note that your JSON is invalid: data must be an array, not an object.
In your controller, you will need to create a function to "find" the correct object in your data object.
Try something like this:
$scope.getRoom = function(id) {
for(var i in $scope.rooms) {
if($scope.rooms[i].id === id) {
return $scope.rooms[i];
}
}
};
And you can display it in your DOM:
{{ getRoom(roomID) }}
BUT it would probably be even better to set the current room to a scoped variable instead of running the function every time. So in this case (I strongly recommend), instead of returning $scope.rooms[i], you could set angular.copy($scope.rooms[i], $scope.currentRoom) (this will copy the room into the currentRoom scoped variable) and then use it in the DOM with simply {{ currentRoom }}
Good luck!
Related
I have a scenario, to handle the params. ( when param exist it will handled differently )
so, how can i keep 2 templates and use them according to the requirement? at present I am trying like this, which is not working at all.
any one help me?
my state with 2 template: ( please help me to correct )
.state('serialCreateCase', {
url: '/serialCreateCase?sn=',
views:{
"" : {
"templateUrl": 'app/login/loginWithSerial.html'
},
"?sn=" : {
"templateUrl": 'app/login/login.html'
}
}
})
here is the redirection with 2 scenarios: ( correct me if I am wrong )
if(!$rootScope.isUserLoggedIn && toParams.sn !== undefined ) {
console.log('dont take action', toState, toParams.sn );
$rootScope.canNavigate = true;
$state.go('serialCreateCase'); //works
$state.go('serialCreateCase', {sn:'1234'}); //not works
event.preventDefault();
return;
}
There is a working plunker
I would say that you need replace templateUrl with
Templates
TemplateUrl ...
templateUrl can also be a function that returns a url. It takes one preset parameter, stateParams, which is NOT
injected.
TemplateProvider
Or you can use a template provider function which can be injected, has access to locals, and must return template HTML,
like this...
There are more details and plunkers
Angular UI Router: decide child state template on the basis of parent resolved object
dynamic change of templateUrl in ui-router from one state to another
This I prefer the most
...
templateProvider: [$stateParams, '$templateRequest',
function($stateParams, templateRequest)
{
var tplName = "pages/" + $stateParams.type + ".html";
return templateRequest(tplName);
}
],
(check it here) because it uses also $templateRequest
EXTEND
There is a working plunker
this could be the state def
.state('serialCreateCase', {
url: '/serialCreateCase?sn',
views: {
"": {
templateProvider: ['$stateParams', '$templateRequest',
function($stateParams, templateRequest) {
var tplName = "app/login/loginWithSerial.html";
if($stateParams.sn){
tplName = "app/login/login.html";
}
return templateRequest(tplName);
}
]
},
}
});
what we really need is to always pass some value, as sn. So, these should be the calls:
// we need to pass some value, to be sure that there will be other than last
<a ui-sref="serialCreateCase({sn: null})">
// here is reasonable value
<a ui-sref="serialCreateCase({sn:'1234'})">
Check it here in action
use, $stateParams instead of toParams,
1) Deciding the template depending on the param(your requirement)
.state('serialCreateCase', {
url: '/serialCreateCase?sn=',
views: {
'': {
templateUrl: function(stateParams) {
var param = stateParams.sn
return (param == undefined) ? 'app/login/loginWithSerial.html' : 'app/login/login.html'
},
controller: 'myController',
}
}
})
You can check the stateParam using the parameter of templateUrl, and change the templates.
2) change the state depending on the param from controller.
This is a sample controller where you can check the state parameter and use the re directions as your wish.
allControllers.controller('myController', ['$scope','$rootScope','$state','$stateParams',
function($scope,$rootScope,$state,$stateParams) {
if(!$rootScope.isUserLoggedIn)
{
if($stateParams.sn !== undefined )
{
alert('dont take action', $stateParams.sn );
}
else
{
alert('You can redirect, no parameter present');
}
}
}
}])
I'm having an issue trying to pass a parameter object to a state using stage.go().
Here is my state definition:
.state('drillhole.ddhinttype', {
url: '/ddhinttype',
templateUrl: VIRTUAL_DIR_PATH + '/App/Views/drillholemanager/drillhole/tabddhinttype.html?v=' + fileVer,
controller: 'DrillHoleDdhIntTypeController',
params: { name: null, description: null }
})
And here is my controller:
try {
angular.module('centric.drillhole.manager');
} catch (e) {
angular.module('centric.drillhole.manager', ['app.config', 'ui.router', 'kendo.directives', 'ui.bootstrap', 'ngCookies', 'centric.common', 'centric.notification', 'pascalprecht.translate', 'centric.security', 'centric.app.settings']);
}
angular.module('centric.drillhole.manager').controller('DrillHoleDdhIntTypeController', ['$scope', 'CentricUIHelper', 'NumberHelper', 'DrillHoleManagerService', 'app.config', '$stateParams',
function ($scope, uihelper, numberHelper, service, appconfig, $stateParams) {
$scope.loading = false;
$scope.isbusy = function () {
return $scope.loading || $scope.$parent.loading;
}
var load = function () {
var hello = $stateParams.name;
var hello2 = $stateParams.description;
};
load();
}]);
And I'm calling the state like so:
$state.go('drillhole.ddhinttype', { name: tab.params.name, description: tab.params.description });
In my controller the name and description properties are always null.
Not sure what I'm missing here. Any ideas?
If you put the params in your url you will be able to access it in controller using $stateParams
.state('drillhole.ddhinttype', {
url: '/ddhinttype/:name/:description',
templateUrl: VIRTUAL_DIR_PATH + '/App/Views/drillholemanager/drillhole/tabddhinttype.html?v=' + fileVer,
controller: 'DrillHoleDdhIntTypeController',
params: { name: null, description: null }
})
You can read more about url routing here: https://github.com/angular-ui/ui-router/wiki/url-routing
Try this in the state definition:
params: { name: undefined, description: undefined }
or this:
params: ['name', 'description']
I feel like I should post the final result. I have decided to pass the parameter in the URL so that I can re-use the same controller for several tabs which each have the same functionality but against different tables in the DB.
Here is the part of my base controller which creates the tabs (CoreLogController.js):
service.getDrillHoleIntervalTypes()
.success(function (res) {
$scope.data.drillHoleIntervalTypes = res;
for (var i = 0; i < $scope.data.drillHoleIntervalTypes.length; i++) {
// add the tab and set it as active if we're in the correct $state
$scope.dynamictabs.push({ heading: $scope.data.drillHoleIntervalTypes[i].Name, route: 'drillhole.ddhinttype', params: { ddhinttype: $scope.data.drillHoleIntervalTypes[i].Name }, active: ($scope.$state.params.ddhinttype == $scope.data.drillHoleIntervalTypes[i].Name) });
}
})
.error(function (error) {
uihelper.showError(error);
});
And here is the relevant HTML portion where the tabs are shown (corelog.html):
<tabset>
<tab ng-repeat="t in statictabs" heading="{{t.heading}}" ui-sref="{{t.route}}" active="t.active"></tab>
<tab ng-repeat="t in dynamictabs" heading="{{t.heading}}" ui-sref="drillhole.ddhinttype({ddhinttype: '{{t.params.ddhinttype}}'})" active="t.active"></tab>
</tabset>
And here is where I define the state (app.js):
.state('drillhole.ddhinttype', {
url: '/ddhinttype/{ddhinttype}',
templateUrl: VIRTUAL_DIR_PATH + '/App/Views/drillholemanager/drillhole/tabddhinttype.html?v=' + fileVer,
controller: 'DrillHoleDdhIntTypeController',
params: { ddhinttype: null }
})
I now get access to the ddhinttype variable on each instance of the controller (DrillHoleDdhIntTypeController.js) which tells it which table to perform operations against.
Since ddhinttype is also contained the URL the user can create a bookmark which will bring them right back to the same tab even though they are dynamically generated.
I have a couple of states set up in my angular application that look like this:
.state('createOrder', {
url: '/customer-services/orders/create?orderNumber',
templateUrl: 'tpl/customerServices/orders/save.html',
controller: 'SaveOrderController',
controllerAs: 'controller',
params: {
accountNumber: null,
},
resolve: {
// If we have an order number, we populate the new order
order: ['$q', '$stateParams', 'OrderService', 'SaveOrderService', function ($q, $stateParams, service, shared) {
// Get our order number
var orderNumber = $stateParams.orderNumber;
// Defer our promise
var deferred = $q.defer();
// If we have an order number
if (orderNumber) {
// Get our order from the system
service.get(orderNumber).then(function (response) {
// Loop through the current lines
for (var i = 0; i < response.lines.length; i++) {
// Get the current line
var line = response.lines[i];
// Set to disabled
line.disabled = true;
}
// Assign our models
shared.order = response;
// Resolve our promise
deferred.resolve();
});
// If we don't have an order number
} else {
// Resolve our promise anyway
deferred.resolve();
}
// Return our promise
return deferred.promise;
}],
// Updates the account number in our order
updateAccount: ['$stateParams', 'SaveOrderService', function ($stateParams, shared) {
// Assigns the account number to the parameter if it has been passed
shared.order.accountNumber = $stateParams.accountNumber;
}]
},
data: {
requireLogin: true,
pageTitle: 'Add order'
}
}).state('createOrder.lines', {
url: '/lines',
views: {
'#': {
templateUrl: 'tpl/customerServices/orders/save/lines.html',
controller: 'SaveOrderLinesController',
controllerAs: 'controller'
}
},
params: {
id: null
},
resolve: {
validate: ['$state', '$stateParams', '$timeout', 'SaveOrderService', function ($state, $stateParams, $timeout, shared) {
console.log(shared);
// If we don't have an account number
if (!shared.order.accountNumber) {
// Timeout to avoid digest issues
$timeout(function () {
// Redirect to the create order view
$state.go('createOrder', { orderNumber: $stateParams.orderNumber });
});
}
}],
// Genearates a default order line for selects an existing one for editing
orderLine: ['$stateParams', 'ArrayService', 'SaveOrderService', function ($stateParams, arrayService, shared) {
// Get our id
var id = $stateParams.id;
// If we have an id
if (id) {
// Get our index
var index = arrayService.indexOf(shared.order.lines, { id: id }, 'id');
// If our index is greater than -1
if (index > -1) {
// Return the current line
return shared.order.lines[index];
}
}
// Falback, return blank (ish) order line
return {
forDelivery: true,
quantity: 1,
unitOfMeasure: 0
};
}],
// If we have an existing line, gets the product
product: ['ProductService', 'SaveOrderService', 'orderLine', function (service, shared, orderLine) {
// If we have a product
if (orderLine.productCode) {
// Return our product
return service.get(orderLine.productCode, shared.order.accountNumber);
}
// Fallback, return nothing
return null;
}]
},
data: {
requireLogin: true,
pageTitle: 'Add order : Lines'
}
})
What I would like is for my "order" resolve to resolve before any resolves are called on the child state (createOrder.lines). I thought using a promise would do this, but my validation keeps failing if I go straight to the lines view when supplying an orderNumber.
Does anyone know how I can get the order to resolve before the validation on the lines state?
This is what I found in the ui-router docs for inherited resolved dependencies...
The resolve keys MUST be injected into the child states if you want to
wait for the promises to be resolved before instantiating the
children.
https://github.com/angular-ui/ui-router/wiki/Nested-States-%26-Nested-Views#inherited-resolved-dependencies
I have some views in my applications, and I have hard time to show the data when moving from one view to another.
I have a list of news and when I click on the particular news I want the view for that particular news to be shown. Here is my code:
My app.js :
.state('app.list', {
url: "/list",
views: {
'appScreen': {
templateUrl: "list.html",
controller: 'List.Ctrl'
}
}
})
.state('app.singleview', {
url: "/list/:newsId",
views: {
'appScreen': {
templateUrl: "single.html",
controller: 'SingleCtrl'
}
}
})
My controllers:
ListCtrl.$inject = ['$http', '$scope', 'datacontext'];
function ListCtrl( $http, $scope, datacontext) {
$scope.list = [];
datacontext.getPosts().then(function (posts) {
console.log('posts', posts);
$scope.list= posts;
}, function(reason) {
alert(reason);
});
The following controller is the one which will show me the single news and I have written some code but is not correct. In the URL I get the ID but I can't manage to show the data for that ID.
SingleCtrl.$inject = ['$scope', '$stateParams', 'datacontext'];
function ListNewCtrl($scope, $stateParams, datacontext) {
$scope.New = getNewsById($stateParams.newsId);
function getNewsById(id) {
datacontext.getPosts().then(function(posts) {
var found = null;
for (var i = 0; i < posts.length; i++) {
if (posts[i].id == id) {
found = posts[i];
break;
}
}
return found;
})
}
};
So in this controller what I am trying to do is get the ID and match it with postsId, and then show the data accordingly but it does no seem to work
You're confused with the asynchronism. The code should be
getNewsById($stateParams.newsId);
function getNewsById(id) {
datacontext.getPosts().then(function(posts) {
var found = null;
for (var i = 0; i < posts.length; i++) {
if (posts[i].id == id) {
$scope.New = posts[i];
break;
}
}
});
}
So that, when the success callback is executed, the New scope variable is initialized by the found post.
That said, I have a hard time understanding why you're getting a whole list of posts from the backend instead of using a REST service returning a single post by ID. If you did, it would be reduced to
function getNewsById(id) {
datacontext.getPost(id).then(function(post) {
$scope.New = post;
});
}
I want to navigate to another state/screen and pass a simple json object to this next screen.
I have the following:
var benefit = { "x": "y"};
$state.go('pages.claimed', { 'benefit': benefit });
My state looks like this:
.state('pages.claimed', {
url: '/claimed',
views: {
'page': {
templateUrl: 'templates/pages/claimed.html'
}
}
})
I can't however access the "benefit" object/parameter in the pages.claimed view. I'm using the ionic framework based on angular.
Parse object to json:
var benefit = angular.toJson({ "x": "y"});
Define variable in state params:
.state('pages.claimed', {
url: '/claimed?params',
views: {
'page': {
templateUrl: 'templates/pages/claimed.html'
}
}
})
Access to variable from controller via $stateParams:
var benefit = angular.fromJson($stateParams.benefit);
Here full doc
Edit:
There are several ways to pass an object to controller from url:
Via query params:
define options url: '/yoururl?a&b&c',
pass variables yoururl?a=1&b=2&c=3
Via url params:
define options url: '/yoururl/:a/:b/:c',
pass variables yoururl/1/2/3
For more complicated situations you can parse your object to json string and encode it with base64
Object: { a:1, b:2, c:3 }
JSON String: {"a":1,"b":2,"c":3}
Base64 Encoded string: eyJhIjoxLCJiIjoyLCJjIjozfQ==
define options url: '/yoururl?params'
pass variables yoururl?params=eyJhIjoxLCJiIjoyLCJjIjozfQ==
More info about base64
$state.go should be corrected like this
var benefit = { "x": "y"};
$state.go('pages.claimed', { benefit: benefit });
.state should be like this
.state('pages.claimed', {
url: '/claimed',
params: { benefit: null },
views: {
'page': {
templateUrl: 'templates/pages/claimed.html'
}
}
})
Catch the passed object as follows in the next controller
(function () {
'use strict';
angular.module('YourAppName').controller('ControllerName', ['$stateParams', ControllerName]);
function ControllerName($stateParams) {
var vm = this;
vm.variableToBind = $stateParams.benefit;
};
})();
Very clean solution:
app.js:
$stateProvider.state('users', {
url: '/users',
controller: 'UsersCtrl',
params: { obj: null }
})
controllers.js
function UserCtrl($stateParams) {
console.log($stateParams);
}
Then from any other controller:
$state.go('users', {obj:yourObj});
I'm not sure what you're application is doing, but if you need to share information between two controllers you should be using some sort of service, not passing a bunch of data through the URL. The URL is to pass parameters around to identify the state, not be the means of data transportation.
You're probably going to want a factory of some sort. Here's a little benefit registration service... assuming underscore.
.factory('benefitsService', ['_', function(_){
function BenefitsService(){
this.benefits = [{
id: 'benefit1',
x: 100,
y: 200
},{
id: 'benefit2',
x: 200,
y: 300
}];
}
// use this to register new benefits from a controller, other factory, wherever.
BenefitsService.prototype.addBenefit = function(benefit){
this.benefits.push(benefits);
}
BenefitsService.prototype.findById = function(id){
return _.findWhere(this.benefits, {id: id});
}
return new BenefitsService();
}]);
.run(['benefitsService', function(benefitsService){
// we're going to register another benefit here to show usage....
benefitsService.addBenefit({
id: 'addedBenefit',
x: 2000,
y: 4000
});
}])
Then you just pass the id through the URL to something normal "/url/:id"
.controller('firstController', ['$state', function($state){
$state.go('stateId', {
id: 'addedBenefit'
});
});
// and use your injected service to find your data.
.controller('secondController', ['$state', 'benefitService', function($state, benefitService){
var benefit = benefitService.findById($state.params.id);
// and you're home!
}]);
This way you don't end up with a bunch of cruft inside of your URL, only what you need to identify state. You've also obfuscated the storage mechanism, so you can use an object, local storage, or any synchronous storage mechanism you'd like.
You also have a service you can inject and use anywhere else through your application.
Looks like you missed parameter 'data' in your state:
.state('pages.claimed', {
url: '/claimed',
views: {
'page': {
templateUrl: 'templates/pages/claimed.html'
}
},
data: {
benefit: {}
}
})
Here is description from documentation