AngularJS UI-Router v1.0: getting state name during resolving - angularjs

I am migrating from UI-Router v0.4.x to v1.0 and I'm having an issue. Consider the following piece of code.
myApp
.component('myComponent', {
bindings: {
resolveBinding: '<'
},
controller: class myComponentController {
constructor() {
// ...
}
},
templateUrl: someTemplateUrl
})
.config(($stateProvider) => {
$stateProvider
.state('some-state', {
url: '/some-state',
component: 'myComponent',
resolve: {
resolveBinding: function(DependencyResolver) {
let targetStateName = this.self.name
return DependencyResolver.doSomeThingsFor(targetStateName)
}
}
})
})
In the new versionlet targetStateName = this.self.name will fail because this is now null, whereas before it contained information on the state it was transitioning to.
How can I get the state name in this block of code?
I was thinking about using a $transitions.onBefore hook to put the name on rootScope, and doing something like:
resolveBinding: function($rootScope, DependencyResolver) {
let targetStateName = $rootScope.hackyStateName
return DependencyResolver.doSomeThingsFor(targetStateName)
}
But I feel this is ugly and I'm missing out on something easier and more elegant.

You can inject $transition$ into a resolve function:
resolveBinding: function($transition$) {
console.log($transition$.to());
}
See the $transition$ documentation.
Does that help you?

Related

Unable to transition to state, getting error that state is not defined

I am currently on the main page after invoking
http://localhost:8199/#/iceberg-ui
I have a state called iceberg.reconcreate as defined below.
But when I try to invoke $state.go('iceberg.reconcreate') on the click of a button, I get the error that state is not defined.
angular-ui-router.js:982 Uncaught Error: No such state undefined at
Object.transitionTo (angular-ui-router.js:982) at Object.go
(angular-ui-router.js:973) at HTMLAnchorElement.
(angular-ui-router.js:1383) at HTMLAnchorElement.dispatch
(jquery.js:4641) at HTMLAnchorElement.elemData.handle (jquery.js:4309)
ROUTING
routingSetup.$inject = ['$stateProvider', '$urlRouterProvider'];
function routingSetup($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/iceberg-ui");
$stateProvider
.state('iceberg', {
url: "/iceberg-ui",
templateUrl: "app/iceberg/iceberg.view.html",
controller: 'IcebergController as icebergCtrl'
})
.state('iceberg.reconlist', {
templateUrl: "app/iceberg/recon/list/recon.list.view.html",
controller: 'ReconListController as reconListCtrl'
})
.state('iceberg.reconcreate', {
templateUrl: "app/iceberg/recon/create/recon.create.view.html",
controller: 'ReconCreateController as reconCreateCtrl'
})
.state('iceberg.recondetails', {
templateUrl: "app/iceberg/recon/details/recon.details.view.html",
controller: 'ReconDetailsController as reconDetailsCtrl'
})
}
Controller
(function() {
'use strict';
var myApp = angular.module('iceberg.recon');
myApp.controller('ReconListController', ReconListController);
ReconListController.$inject = ['ReconListService', '$state'];
function ReconListController(ReconListService, $state) {
var vm = this;
function getReconciliationList() {
return {
load: function(loadOptions) {
var reconList = ReconListService.getReconciliationList();
return {
data: reconList,
totalCount: reconList.length
};
},
update: function(key, values) {
console.log("calling the UPDATE GRID");
//Do Nothing, this method is required else the framework throws an error
}
};
}
vm.createNewRecon = {
text: "Create New Reconciliation",
icon: "plus",
type: "success",
height: "45px",
onClick: function(e) {
$state.go('iceberg.reconcreate');
}
};
}
}());
UPDATE - Updated with controller details
Sorry folks, that was an oversight from my end. I had a ui-sref defined with the incorrect name and all this while I was thinking it was getting called as a onclick event. My bad, apologies for the same.

How to supply the separate `template` when param exist?

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

How to load Angular-translate before any UI is displayed with ui-router resolve

I used angular-translate for i18n. I want to use $translatePartialLoader service to modular language key as lazy load. Also I want to use ui-router resolve option for this.
Now How to do this? Is possible add a code sample for me?
Thanks
I find solutions and solve my problem.
In config:
$translatePartialLoaderProvider.addPart('index');
$translateProvider
.useSanitizeValueStrategy(null)
.fallbackLanguage('en-us')
.registerAvailableLanguageKeys(['en-us','pt-br'], {
'en_*': 'en-us',
'pt_*': 'pt-br'
})
.useLoader('$translatePartialLoader', {
urlTemplate: '{part}/locale_{lang}.json'
})
.useLoaderCache(true)
.useCookieStorage()
.determinePreferredLanguage();
In ui-router for index:
.state('index', {
url: '/index',
templateUrl: 'index.html',
controller:'IndexCtrl',
resolve: {
trans:['RequireTranslations',
function (RequireTranslations) {
RequireTranslations('index');
}],
dep: ['trans','$ocLazyLoad',
function(trans,$ocLazyLoad){
return $ocLazyLoad.load(['plugin']).then(
function(){
return $ocLazyLoad.load(['IndexCtrl.js']);
}
);
}]
}
})
.state('index.users',{
url: "/users",
templateUrl: "users.html",
controller:'UserListCtrl',
resolve: {
trans:['RequireTranslations',
function (RequireTranslations) {
RequireTranslations('modules/user');
}],
dep: ['trans','$ocLazyLoad',
function(trans,$ocLazyLoad){
return $ocLazyLoad.load(['UserListCtrl.js'])
}]
}
})
and in run:
app.run(function($rootScope,$translate) {
// translate refresh is necessary to load translate table
$rootScope.$on('$translatePartialLoaderStructureChanged', function () {
$translate.refresh();
});
$rootScope.$on('$translateChangeEnd', function() {
// get current language
$rootScope.currentLanguage = $translate.use();
});
})
and in RequireTranslations factory:
app.factory('RequireTranslations', function($translatePartialLoader, $translate,$rootScope) {
return function() {
angular.forEach(arguments, function(translationKey) {
$translatePartialLoader.addPart(translationKey);
});
return $translate.refresh().then(
function(){
return $translate.use($rootScope.currentLanguage);
}
);
};
});
and please note you should add $translatePartialLoader and trans as parameter in all controllers like this:
app.controller('UserListCtrl',function($scope,...,$translatePartialLoader,trans){

ES6 AngularJS directive <> service communication

I think i have a scope problem with js. Please take a look to my code below.
This is my AngularJS example in es6. I compile the code to es5 with grunt browserify.
If i call my example i got the error:
TypeError: this.gatewayServiceGet is not a function
at ChainsDirective.loadChains [as chainsServiceLoadChains]
I check it and find out that this in loadChains is not the same this than in the constructor.
What can i do?
This is my app.js
'use strict';
import AppController from './appController.js';
import ChainsDirective from './components/chains/chains.directive.js';
import ChainsService from './components/chains/chains.service.js';
import GatewayService from './components/common/gateway/gateway.service.js';
angular
.module('SalesCockpit', ['ui.router', 'ui.grid'])
.config($stateProvider => {
$stateProvider
.state('chains', {
url: '/chains',
templateUrl: 'components/chains/chains.html'
})
.state('chainDetail', {
url: '/chain/{chainId:int}/detail',
templateUrl: 'components/chain-detail/chain-detail.html'
})
;
})
.controller('AppController', AppController)
.service('chainsService', ChainsService)
.service('gatewayService', GatewayService)
.directive('chains', ChainsDirective);
This is my chain directive
export default function ChainsDirective() {
class ChainsDirective {
/*#ngInject*/
constructor(chainsService, $state) {
this.chainsServiceLoadChains = chainsService.loadChains;
this.gridOptions = {
enableColumnMenus: false,
columnDefs: [
{
name: 'id',
visible: false
},
{
name: 'name',
displayName: 'Kette',
cellTemplate: '<div class="ui-grid-cell-contents"><a ng-click="grid.appScope.openDetail(row.entity.id)">{{row.entity.name}}</a></div>'
}
]
};
this.$stateGo = $state.go;
this.fetch();
}
/**
* #param int chainId
*/
openDetail(chainId) {
this.$stateGo('chainDetail', {chainId})
}
fetch() {
return this.chainsServiceLoadChains().then(data => {
this.gridOptions.data = data
})
}
}
return {
restrict: 'E',
template: '<div id="chains" ui-grid="gridOptions" external-scopes="$scope" class="grid"></div>',
controller: ChainsDirective,
controllerAs: 'chains'
}
}
This is my chain servie
export default class ChainsService {
/*#ngInject*/
constructor(gatewayService) {
this.gatewayServiceGet = gatewayService.get;
}
/**
* #returns Promise
*/
loadChains() {
return this.gatewayServiceGet('loadChains');
}
}
FWIW, this has nothing to do with ECMAScript 2015. JavaScript always worked that way.
The value of this depends on how the function is called. So if you call it as
this.chainsServiceLoadChains()
this inside chainsServiceLoadChains will refer to what is before the ., which is this that refers to the ChainsDirective instance.
One solution would be to bind the this value of the function to a specific value:
this.chainsServiceLoadChains = chainsService.loadChains.bind(chainsService);
Now it doesn't matter anymore how the function is called, this will always refer to chainsService.
Learn more about this:
MDN - this
How to access the correct `this` context inside a callback?

Pass object as parameter in $state.go

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

Resources