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
Related
I'm trying to cleanup code of one of my controllers which became too big. First I have decided to move attendee registration, which uses AngularJS Material mdDialog, to the service.
Original (and working) controller code looks like:
myApp.controller('RegistrationController', ['$scope','$routeParams','$rootScope','$location','$filter','$mdDialog', function($scope, $routeParams, $rootScope, $location, $filter, $mdDialog){
var attendee = this;
attendees = [];
...
$scope.addAttendee = function(ev) {
$mdDialog.show({
controller: DialogController,
templateUrl: 'views/regForm.tmpl.html',
parent: angular.element(document.body),
targetEvent: ev,
clickOutsideToClose:true,
controllerAs: 'attendee',
fullscreen: $scope.customFullscreen, // Only for -xs, -sm breakpoints.
locals: {parent: $scope}
})
.then(function(response){
attendees.push(response);
console.log(attendees);
console.log(attendees.length);
})
};
function DialogController($scope, $mdDialog) {
var attendee = this;
$scope.hide = function() {
$mdDialog.hide();
};
$scope.cancel = function() {
$mdDialog.cancel();
};
$scope.save = function(response) {
$mdDialog.hide(response);
};
}
}]);
and the code for the controller after separation:
myApp.controller('RegistrationController', ['$scope','$routeParams','$rootScope','$location','$filter','$mdDialog','Attendees', function($scope, $routeParams, $rootScope, $location, $filter, $mdDialog, Attendees){
...
$scope.attendees= Attendees.list();
$scope.addAttendee = function (ev) {
Attendees.add(ev);
}
$scope.deleteAttendee = function (id) {
Attendees.delete(id);
}
}]);
New service code looks like:
myApp.service('Attendees', ['$mdDialog', function ($mdDialog) {
//to create unique attendee id
var uid = 1;
//attendees array to hold list of all attendees
var attendees = [{
id: 0,
firstName: "",
lastName: "",
email: "",
phone: ""
}];
//add method create a new attendee
this.add = function(ev) {
$mdDialog.show({
controller: DialogController,
templateUrl: 'views/regForm.tmpl.html',
parent: angular.element(document.body),
targetEvent: ev,
clickOutsideToClose:true,
controllerAs: 'attendee',
fullscreen: this.customFullscreen, // Only for -xs, -sm breakpoints.
//locals: {parent: $scope}
})
.then(function(response){
attendees.push(response);
console.log(attendees);
console.log(attendees.length);
})
};
//simply search attendees list for given id
//and returns the attendee object if found
this.get = function (id) {
for (i in attendees) {
if (attendees[i].id == id) {
return attendees[i];
}
}
}
//iterate through attendees list and delete
//attendee if found
this.delete = function (id) {
for (i in attendees) {
if (attendees[i].id == id) {
attendees.splice(i, 1);
}
}
}
//simply returns the attendees list
this.list = function () {
return attendees;
}
function DialogController($mdDialog) {
this.hide = function() {
$mdDialog.hide();
};
this.cancel = function() {
$mdDialog.cancel();
};
this.save = function(response) {
$mdDialog.hide(response);
};
}
}]);
but I'm not able to "save" from the spawned mdDialog box which uses ng-click=save(attendee) neither close the dialog box.
What I'm doing wrong?
I'm not able to "save" from the spawned mdDialog box which uses ng-click=save(attendee) neither close the dialog box.
When instantiating a controller with "controllerAs" syntax, use the name with which it is instantiated:
<button ng-click="ctrl.save(ctrl.attendee)">Save</button>
this.add = function(ev) {
$mdDialog.show({
controller: DialogController,
templateUrl: 'views/regForm.tmpl.html',
parent: angular.element(document.body),
targetEvent: ev,
clickOutsideToClose:true,
controllerAs: ̶'̶a̶t̶t̶e̶n̶d̶e̶e̶'̶ 'ctrl',
fullscreen: this.customFullscreen, // Only for -xs, -sm breakpoints.
//locals: {parent: $scope}
})
.then(function(response){
attendees.push(response);
console.log(attendees);
console.log(attendees.length);
return response;
});
To avoid confusion, choose a controller instance name that is different from the data names.
$scope is not available to be injected into a service when it's created. You need to refactor the service and its methods so that you don't inject $scope into it and instead pass the current scope to the service methods when you call them.
I actually have a notification module that I inject which uses $mdDialog. Below is the code for it. Perhaps it will help.
(() => {
"use strict";
class notification {
constructor($mdToast, $mdDialog, $state) {
/* #ngInject */
this.toast = $mdToast
this.dialog = $mdDialog
this.state = $state
/* properties */
this.transitioning = false
this.working = false
}
openHelp() {
this.showAlert({
"title": "Help",
"textContent": `Help is on the way for ${this.state.current.name}!`,
"ok": "OK"
})
}
showAlert(options) {
if (angular.isString(options)) {
var text = angular.copy(options)
options = {}
options.textContent = text
options.title = " "
}
if (!options.ok) {
options.ok = "OK"
}
if (!options.clickOutsideToClose) {
options.clickOutsideToClose = true
}
if (!options.ariaLabel) {
options.ariaLabel = 'Alert'
}
if (!options.title) {
options.title = "Alert"
}
return this.dialog.show(this.dialog.alert(options))
}
showConfirm(options) {
if (angular.isString(options)) {
var text = angular.copy(options)
options = {}
options.textContent = text
options.title = " "
}
if (!options.ok) {
options.ok = "OK"
}
if (!options.cancel) {
options.cancel = "Cancel"
}
if (!options.clickOutsideToClose) {
options.clickOutsideToClose = false
}
if (!options.ariaLabel) {
options.ariaLabel = 'Confirm'
}
if (!options.title) {
options.title = "Confirm"
}
return this.dialog.show(this.dialog.confirm(options))
}
showToast(toastMessage, position) {
if (!position) { position = 'top' }
return this.toast.show(this.toast.simple()
.content(toastMessage)
.position(position)
.action('OK'))
}
showYesNo(options) {
options.ok = "Yes"
options.cancel = "No"
return this.showConfirm(options)
}
uc() {
return this.showAlert({
htmlContent: "<img src='img\\underconstruction.jpg'>",
ok: "OK",
title: "Under Construction"
})
}
}
notification.$inject = ['$mdToast', '$mdDialog', '$state']
angular.module('NOTIFICATION', []).factory("notification", notification)
})()
Then inject notification (lower case) into my controllers in order to utilize it. I store notification in a property of the controller and then call it with something like this:
this.notification.showYesNo({
clickOutsideToClose: true,
title: 'Delete Customer Setup?',
htmlContent: `Are you sure you want to Permanently Delete the Customer Setup for ${custLabel}?`,
ariaLabel: 'Delete Dialog'
}).then(() => { ...
this.notification.working = true
this.ezo.EDI_CUSTOMER.remove(this.currentId).then(() => {
this.primaryTab = 0
this.removeCustomerFromList(this.currentId)
this.currentId = 0
this.notification.working = false
}, error => {
this.notification.working = false
this.notification.showAlert({
title: "Error",
htmlContent: error
})
})
I am learning to use JHipster and can't figure out how to use create a custom query.
In my project I have Orders table with DeliveryDay and Week fields and want to show only orders for current day of the week. DeliveryDay and Week is int with values (1-7 and 0-2)
So in OrdersRepository.java I added custom query like this:
public interface OrdersRepository extends JpaRepository<Orders,Long> {
Page<Orders> findByDeliveryDayAndWeek(int weekday, int week, pageable);
in OrdersResource.java i added this one:
#RequestMapping(value = "/today",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
public ResponseEntity<List<Orders>> getOrdersForToday(Pageable pageable)
throws URISyntaxException {
log.debug("REST request to get a page of Orderss");
Page<Orders> page = ordersRepository.findByDeliveryDayAndWeek(1, 0, pageable);
HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/today");
return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);
}
I also added today.html (copied orders.html) and today.js
'use strict';
angular.module('fruitcrmApp')
.config(function ($stateProvider) {
$stateProvider
.state('today', {
parent: 'entity',
url: '/today',
data: {
authorities: ['ROLE_USER'],
pageTitle: 'fruitcrmApp.orders.home.title'
},
views: {
'content#': {
templateUrl: 'scripts/app/custom/today.html',
controller: 'OrdersController'
}
},
resolve: {
translatePartialLoader: ['$translate', '$translatePartialLoader', function ($translate, $translatePartialLoader) {
$translatePartialLoader.addPart('orders');
$translatePartialLoader.addPart('global');
return $translate.refresh();
}]
}
})
});
and add today.js in the index.html
My orders.controller.js looks like this (generated by JHipster)
'use strict';
angular.module('fruitcrmApp')
.controller('OrdersController', function ($scope, $state, Orders, OrdersSearch, ParseLinks) {
$scope.orderss = [];
$scope.predicate = 'id';
$scope.reverse = true;
$scope.page = 1;
$scope.loadAll = function() {
Orders.query({page: $scope.page - 1, size: 20, sort: [$scope.predicate + ',' + ($scope.reverse ? 'asc' : 'desc'), 'id']}, function(result, headers) {
$scope.links = ParseLinks.parse(headers('link'));
$scope.totalItems = headers('X-Total-Count');
$scope.orderss = result;
});
};
$scope.loadPage = function(page) {
$scope.page = page;
$scope.loadAll();
};
$scope.loadAll();
$scope.search = function () {
OrdersSearch.query({query: $scope.searchQuery}, function(result) {
$scope.orderss = result;
}, function(response) {
if(response.status === 404) {
$scope.loadAll();
}
});
};
$scope.refresh = function () {
$scope.loadAll();
$scope.clear();
};
$scope.clear = function () {
$scope.orders = {
details: null,
orderDate: null,
firstDelivery: null,
isActive: false,
id: null
};
};
});
Now I can access http://localhost:3000/#/today but it shows all data from Orders what I did wrong? How to use my own method from OrdersRepository.java?
I tried to search for examples but didn't found any relevant. What are the needed steps I missed? Link for some tutorial where it is covered will be great if answer will be to long.
You need to create a new angular service for your today API endpoint. Something like this, called orders-today.service.js:
'use strict';
angular.module('fruitcrmApp')
.factory('OrdersToday', function ($resource) {
return $resource('api/orders/today', {}, {
'query': { method: 'GET', isArray: true}
});
});
Then in your orders.controller.js file, you need to inject your new OrdersToday service:
.controller('OrdersController', function ($scope, $state, Orders, OrdersSearch, OrdersToday, ParseLinks) {
When you want to get the list of today's orders, you need to use OrdersToday.query just like you used Orders.query in the example you pasted.
You will probably want to create a OrdersTodayController with references to OrdersToday, and use that in today.js instead of OrdersController.
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!
.controller('CyclesController', function ($scope, $state) {
$scope.age = 0;
$scope.name = "";
$scope.email = "";
$scope.calculateByAge = function (age, name, email) {
$scope.data = $scope.data || {};
if (age > 0) {
$scope.data.age = age;
$scope.data.name = name;
$scope.data.email = email;
$state.go('tab.cycles-detail');
}
}
})
.controller('CyclesDetailController', function ($scope, $stateParams, CyclesService) {
console.log('scope data', $scope.data); // <--- undefined.
})
This may be a dumb question, but can get to get the data from the form on the CyclesDetailController controller.
If it's simple property you could do it by routing. Just change your "tab.cycles-detai' to 'tab.cycles-detai/:age' in your ui-router configuration and pass it when you're redirecting: $state.go('tab.cycles-detail', {age: age});
in 'CyclesDetailController' access it by $stateParams.age;
e.g:
//app.config
//...
.state('tab.cycles-detail', {
url: "^detail/{age:int}",
controller: 'CyclesDetailController',
templateUrl: "url_for_detail_template"
})
//...
// CyclesController
.controller('CyclesController', function ($scope, $state) {
$scope.age = 0;
$scope.calculateByAge = function (age) {
$scope.data = $scope.data || {};
if (age > 0) {
$scope.data.age = age;
$state.go('tab.cycles-detail', {age: age);
}
}
})
//CyclesDetailController
.controller('CyclesDetailController', function ($scope, $stateParams, CyclesService) {
console.log('scope data', $stateParams.age);
})
//
If you want to pass data from one route to another but dont want to expose it in browser menu bar, you can use squash.
Example -
.state('app.enroll', {
url: '/enroll/',
params: {
classId: {
value: null,
squash: true
},
className: {
value: null,
squash: true
}
},
title: 'Students Enrollment',
templateUrl: helper.basepath('enroll.html')
})
2nd Technique -
You can use localStorage / cookies to save data and retrieve at later stage.
3rd Technique -
You can always share data via services/factory in between controllers.
I am using the ui-router module and have defined these states:
.state('projects.create', {
url: '/create',
views: {
'outer#': {
templateUrl: 'views/projects.create.html',
resolve: {
schoolyear: function(schoolyearService) {
return schoolyearService.createSchoolyear();
}
},
controller: 'ProjectWizardController'
}
}
})
.state('projects.edit', {
url: '/edit',
views: {
'outer#': {
templateUrl: 'views/projects.edit.html',
resolve: {
schoolyear: function(schoolyearService) {
return schoolyearService.editSchoolyear();
}
},
controller: 'ProjectWizardController'
}
}
})
As each of both ui-router states know which states they are they also know what dependencies should be passed to the ProjectWizardController.
When the projects.create state is activated I want to pass the CreateWizardDataService to the ProjectWizardController.
When the projects.edit state is activated I want to pass the EditWizardDataService to the ProjectWizardController.
HOW can I manually inject the service dependency into the ProjectsWizardController?
'use strict';
angular.module('schoolyearProjectModule').controller('ProjectWizardController',
function ($scope, wizardDataService, $state, schoolyear) {
// wizardDataService => could be the CreateWizardDataService or EditWizardDataService
// The wizardDataService is the individual service for an AddService or EditService
// service contain the 3 same main properties: schoolyearData, schoolclasscodesData, timetableData
wizardDataService.schoolyearData = schoolyear.schoolyearData;
wizardDataService.schoolyearData = schoolyear.schoolclassCodesData;
wizardDataService.schoolyearData = schoolyear.timetableData;
// The if and else if should be injected into this Controller becaue the outside ui router states know their state edit/create
if ($state.current.name === 'projects.create') {
$scope.steps = [wizardDataService.schoolyearData, wizardDataService.schoolclassCodesData, wizardDataService.timetableData];
}
else if ($state.current.name === 'projects.edit') {
$scope.steps = [wizardDataService.schoolyearData, wizardDataService.schoolclassCodesData, wizardDataService.timetableData];
}
$scope.steps = [wizardDataService.schoolyearData, wizardDataService.schoolclassCodesData, wizardDataService.timetableData];
$scope.activeStep = $scope.steps[0];
$scope.step = 0;
var stepsLength = $scope.steps.length;
$scope.isLastStep = function () {
return $scope.step === (stepsLength - 1);
};
$scope.isFirstStep = function () {
return $scope.step === 0;
};
$scope.getCurrentStep = function () {
return $scope.activeStep.name;
};
$scope.getNextLabel = function () {
return ($scope.isLastStep()) ? 'Submit' : 'Next';
};
$scope.previous = function () {
if ($scope.step > 0) {
$scope.step--;
$scope.activeStep = $scope.steps[$scope.step];
}
};
$scope.next = function () {
if ($scope.isLastStep() && $scope.activeStep.isValid()) {
$state.go('^');
}
else if ($scope.activeStep.isValid()) {
$scope.step += 1;
$scope.activeStep = $scope.steps[$scope.step];
}
}
});
You have two ways to do this:
Option 1 - Use resolve with a string as the value. As per the documentation:
The resolve property is a map object. The map object contains
key/value pairs of:
key – {string}: a name of a dependency to be injected into the
controller.
factory - {string|function}: If string, then it is an
alias for a service. Otherwise if function, then it is injected and
the return value is treated as the dependency. If the result is a
promise, it is resolved before the controller is instantiated and its
value is injected into the controller.
Option 1 example:
.state('projects.create', {
url: '/create',
views: {
'outer#': {
templateUrl: 'views/projects.create.html',
resolve: {
schoolyear: function(schoolyearService) {
return schoolyearService.createSchoolyear();
},
wizardDataService: 'CreateWizardDataService'
},
controller: 'ProjectWizardController'
}
}
})
.state('projects.edit', {
url: '/edit',
views: {
'outer#': {
templateUrl: 'views/projects.edit.html',
resolve: {
schoolyear: function(schoolyearService) {
return schoolyearService.editSchoolyear();
},
wizardDataService: 'EditWizardDataService'
},
controller: 'ProjectWizardController'
}
}
})
Option 2 - Use $injector.get('CreateWizardDataService') or $injector.get('EditWizardDataService') directly in your controller depending on which state you are in.