I currently Angular 1.6 and angular-ui/ui-router
I have a problem with dynamic url which I don't know a limit of parameter
example
www.example.com/name/hello/address/telephone/postcode/city/ .... (n)
or
www.example.com/name/hello/school/age/weight/height/...(n)
or
www.example.com/name/hello/friends/family/age/address
from the example url I need to using ui-route to manage that url and get value parameters from url and just implement only one state.
expected result
let result = [hello,address,telephone,postcode,city, .... (n)];
example route (*only one state handle this case)
.state('person', {
url: '/name/:param1/:param2', (which I want to dynamic)
component: 'person',
resolve: {
search:($transition$) {
return $transition$.params();
}
}
})
Please advice :)
Update :
I found a good solution from this link
Recursive ui router nested views
you can make a function which takes your array input and return an state object.
function generateState(array){
var url = a.reduce(function(url, item){ return url+":"+item+"/" }, "/name/");
return {
url: url,
component: 'person',
resolve: {
search:($transition$)=> {
return $transition$.params();
}
}
}
}
after which you can dynamically register the state using $stateProvider
Related
I inherited a mess. And I need a quick fix for some code that needs to be completely rewritten - but not enough time to do a complete rewrite yet.
Orig developer created an index.js file that has all kinds of global functions used all through an AngularJS/Ionic project. Quite often I am finding AngularJS functions in specific controllers actually passing $scope/$q out to the standard JS functions in the index.js file - its a mess.
One of these global functions is window.FirebasePlugin.onNotificationOpen - which is watching for any inbound pushNotification/messages. The original developer was simply using an "alert" for inbound messages with a data payload. We are now trying to redirect those messages to open up in proper popover or modal windows that belong in the controller in two specific pages.
So the question is, in the global watch JS function, how can I direct
the 'data/payload' to a controller to process in a proper $scope?
I have modified the .state to accept parameters - and then all the subsequent code is in place to pass the $stateParams into specific controllers:
.state('tab.clubs', {
cache: true,
url: '/clubs',
views: {
'tab-clubs': {
templateUrl: 'templates/tab-clubs.html',
controller: 'ClubCtrl',
params: {
'pushAction' : 0,
'pushCode' : 'default'
}
}
}
})
The problem I am having is trying to figure out how to pass URL data from standard JS into the AngularJS .state
The global JS function:
window.FirebasePlugin.onNotificationOpen(function(payload) {
// if there is a payload it will be in payload object
if (payload.action == 1) {
window.location("/clubs/", payload) ; // process message in ClubCtrl
} else if (payload.action == 2) {
window.location("/map/", payload) ; // process message in MapCtrl
}
}, function(error) {
console.error(error);
}) ;
But this method fails.
If your not going to use angulars router to navigate to the page you will need to declare the params in the URL somehow. You can use path params by doing something like /clubs/:pushAction/:pushCode or url params with something like /clubs?pushAction&pushCode
Example:
.state('tab.clubs', {
cache: true,
url: '/clubs/:pushAction/:pushCode',
views: {
'tab-clubs': {
templateUrl: 'templates/tab-clubs.html',
controller: 'ClubCtrl',
params: {
'pushAction' : 0,
'pushCode' : 'default'
}
}
}
})
Then navigate it with
location.href = `/clubs/${payload.action}/${payload.code}`
Additionally if you have alot of unknown params you could also pass in the whole payload as base64 encoded json. I wouldnt recommend this but it is... a solution
.state('tab.clubs', {
cache: true,
url: '/clubs?state',
views: {
'tab-clubs': {
templateUrl: 'templates/tab-clubs.html',
controller: 'ClubCtrl',
params: {
'state' : 0,
}
}
}
})
window.location(`/clubs?state=${btoa(JSON.stringify(payload))}`
Then in your controller reverse that operation
class ClubCtrl {
...
// parses the state out of the state params and gives you the full payload
parseState($stateParams) {
return JSON.parse(atob($stateParams.state));
}
...
}
It would give you all the payload, but its pretty gross
I'm trying to dynamically set the page title in my angular app.
I've already have it working with hardcoded title dynamically changing based on the state
.state('faq-detail', {
url: '/faqs/:faqId',
templateUrl: 'client/faq/faq-detail.view.ng.html',
controller: 'FaqListCtrl',
data: {
title: 'Faq details'
}
});
but I want to get the, in this case the question title, and put it as the page title.
The question comes from mongo database and I'm using angular-meteor.
.state('faq-detail', {
url: '/faqs/:faqId',
templateUrl: 'client/faq/faq-detail.view.ng.html',
controller: 'FaqListCtrl',
data: {
title: Faq.findOne({_id: $stateParams.faqId}).question
},
resolve: {
theFaq: function ($meteor, $stateParams) {
return $meteor.subscribe('faq').then(function () {
return Faq.findOne({_id: $stateParams.faqId});
});
}
}
I've tried this but as you may know, $stateParams is not defiend in the data.title area.
The other method I've tried is this one:
// ATEMPT TO SET THE TITLE DYNAMICALLY WITH THE QUESTION
$scope.autorun(() => {
console.log($scope.getReactively('theFaq'));
if ($scope.theFaq) {
let faqTitle = $scope.getReactively('theFaq').question;
// $state.get('faq-detail').data.title = faqTitle;
$state.current.data.title = faqTitle;
}
});
But this instead sets the title after I load another view with the same controller. So now I have the title from the last visited view instead of the current one.
Question
How do I set the page title from an object key value returned from mongo collection in angular-meteor?
For me, the cleanest way to achieve what you want is to do it in your controller. For example :
angular.module('mymodule').controller('FaqListCtrl', function($stateParams, $window){
let title = Faq.findOne({_id: $stateParams.faqId}).question;
$window.document.title = title;
})
I am trying to find a better solution to use the ui-router together with angular components.
Consider two simple components:
app.component('componentOne', {
template: '<h1>My name is {{$ctrl.name}}, I am {{$ctrl.age}} years old.</h1>',
bindings : {
name : '#',
age : '#'
}
}
);
app.component('componentTwo', {
template: '<h1>I am component 2</h1>'
});
Right now, I am specifying the component and its parameter using the template property:
app.config(function($stateProvider, $urlRouterProvider ){
$stateProvider
.state('component1', {
url: "/component1",
template: "<component-one name=\"foo\" age=\"40\"></component-one>"
})
.state('component2', {
url: "/component2",
template: "<component-two></component-two>"
})
});
While this is working fine, I have components with arround ten bindings which makes the configuration of the ui-router realy awkward.
I tried using the component property but this doesn't work for me at all. The only other solution I found is to specify the parent using the require property and omit the bindings - but this doesn't feel right for me... Is there a better way to do this?
Here is a plnkr.
UI-Router component: routing exists in UI-Router 1.0+ (currently at 1.0.0-beta.1)
Here's an updated plunker: https://plnkr.co/edit/VwhnAvE7uNnvCkrrZ72z?p=preview
Bind static values
To bind static data to a component, use component and a resolve block which returns static data.
$stateProvider.state('component1', {
url: "/component1",
component: 'componentOne',
resolve: { name: () => 'foo', age: () => 40 }
})
Bind async values
To bind async values, use a resolve which returns promises for data. Note that one resolve can depend on a different resolve:
$stateProvider.state('component1Async', {
url: "/component1Async",
component: "componentOne",
resolve: {
data: ($http) => $http.get('asyncFooData.json').then(resp => resp.data),
name: (data) => data.name,
age: (data) => data.age
}
});
Bind lots of values
You mention you have 10 bindings on a component. Depending on the structure of the data you're binding, you can use JavaScript to construct the resolve block (it's "just javascript" after all)
var component2State = {
name: 'component2',
url: '/component2',
component: 'componentTwo',
resolve: {
data: ($http) => $http.get('asyncBarData.json').then(resp => resp.data)
}
}
function addResolve(key) {
component2State.resolve[key] = ((data) => data[key]);
}
['foo', 'bar', 'baz', 'qux', 'quux'].forEach(addResolve);
$stateProvider.state(component2State);
Alternatively, you can move your bindings a level down and create an object which will be the only bindings. If 10 bindings is what is bothering you.
One alternative you can try is to override the template by custom properties of states in $stateChangeStart event.
Run block like this to achieve this kind of behaviour.
app.run(function($rootScope){
//listen to $stateChangeStart
$rootScope.$on("$stateChangeStart",function(event, toState, toParams, fromState, fromParams, options){
//if the component attribute is set, override the template
if(toState.component){
//create element based on the component name
var ele = angular.element(document.createElement(camelCaseToDash(toState.component)));
//if there is any binding, add them to the element's attributes.
if(toState.componentBindings){
angular.forEach(toState.componentBindings,function(value,key){
ele.attr(key,value)
})
}
//you may also do something like getting bindings from toParams here
//override the template of state
toState.template = ele[0].outerHTML;
}
})
//convert camel case string to dash case
function camelCaseToDash(name) {
return name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
}
})
And with this now you can have component property in your state config.
app.config(function($stateProvider, $urlRouterProvider ){
$stateProvider
.state('component1', {
url: "/component1",
component:"componentOne",
componentBindings:{
name:"foo",
age:"40",
}
})
.state('component2', {
url: "/component2",
component:"componentTwo",
})
});
Here is the working plunker.
Still you may have a large config function, but it will look not so awkward.
I am using ngResource in combination with ng-repeat and noticed that slow REST calls doesn't update the list properly. It keeps empty.
As far as I understood I need a binding between controller and ng-repeat element.
My resource and controller definition:
(function (configure) {
configure('loggingResource', loggingResource);
function loggingResource($resource, REST_CONFIG) {
return {
Technical: $resource(REST_CONFIG.baseUrl + REST_CONFIG.path + '/', {}, {
query: {
method: 'GET',
isArray: true
}
})
};
}
})(angular.module('loggingModule').factory);
(function (configure) {
configure('logController', logController);
function logController(loggingResource) {
var that = this;
loggingResource.Technical.query(function (data) {
that.logs = data;
});
//that.logs = loggingResource.Technical.query();
}
})(angular.module('loggingModule').controller);
ng.repeat usage:
<tr class="table-row" ng-repeat="log in logController.logs">
What I have tried so far:
ng-bind in combination with ng-repeat
$q with deferrer
$promise of ngResource
What did I miss?
My try to get it on plnkr: https://plnkr.co/edit/t1c5Pxi7pzgocDMDNITX
The {{}} provides a default binding between your controller and view and you don't need to add anything explicitly. I have updated your plunkr with some minor changes to injected constants etc. and it is working.
// Code goes here
angular.module("loggingModule", ['ngResource']);
(function(configure) {
configure('loggingResource', loggingResource);
function loggingResource($resource) {
return {
Technical: $resource('https://api.github.com/users/addi90/repos', {}, {
query: {
method: 'GET',
isArray: true
}
})
};
}
})(angular.module('loggingModule').factory);
(function(configure) {
configure('logController', logController);
function logController(loggingResource) {
var that = this;
that.logs = [{
title: 'test2'
}];
loggingResource.Technical.query(function(data) {
that.logs = data;
});
//that.logs = loggingResource.Technical.query();
}
})(angular.module('loggingModule').controller);
Since the api resource was not working, I have used my github repo api link there
The updated working plunkr is available here: https://plnkr.co/edit/cederzcAGCPVzc5xTeac?p=preview
Try to use
that.logs.push(data)
so you prevent to reinitialise the logs list. If you override the logs list and the ng-repeat is initialised before the logs list it seems to not be resolved.
Your logs list is set correctly after the rest call?
In one application that I'm working with, the route system need to be integrated with i18n, like the example below:
$routeProvider.when('/:i18n/section', ...);
But I'm facing some issues due to, what I guess it is, the $digest cycle, which doesn't change the i18n param at the runtime.
Other issue that I'm facing is, if the location path is pointed to something like:
http://localhost:9000/section/...
not like:
http://localhost:9000/en/section/...
the i18n path param ends being associated with /section/, which means, on $routeParams service, $routeParams.i18n = 'section';. This is expected, but I need to be able to parse the /:i18n/ param, to avoid this conflicts, and change the URL, concatenating one locale, to contextualize the session, replacing the current route with the new one, i18n-ized, whithout refreshing the view/app automatically, yet selectivelly, because some features only need to be changed, not all.
Also, I've designed one service that evaluates, based on a list of possible language settings and its weights, the language that'll be selected to the current context:
var criteria = {
default: {
tag: 'en',
weight: 10
},
user: {
tag: 'pt',
weight: 20
},
cookie: {
tag: 'zh',
weight: 30
},
url: {
tag: 'ru',
weight: 40
},
runtime: {
tag: 'es',
weight: 50
}
};
function chooseLanguage (languages) {
var deferred = $q.defer();
var weights = [];
var competitors = {};
var choosen = null;
if (defineType(languages) === 'array') {
for (var i = languages.length - 1; i >= 0; i--) {
if (languages[i].tag !== null) {
weights.push(languages[i].weight);
competitors[languages[i].weight] = languages[i];
}
}
choosen = competitors[Math.max.apply(Math, weights)];
} else if (defineType(languages) === 'object') {
choosen = languages;
} else {
return;
}
setRuntimeLanguage(choosen.tag);
deferred.resolve(choosen);
return deferred.promise;
}
Explaining the code above, when angular bootstraps the app, the snippet is executed, selecting which language is defined and if its strong enough to be selected. Other methods are related to this operation, like de detection of the URL param, if there's one logged user, etc, and the process is executed not only on the bootstrap, but on several contexts: $routeChangeStart event, when the user autenticates its session, switching the languages on a select box, and so on.
So, in resume, i need to be able to:
Parse the URL and apply the locale param properly, if its not informed initialy;
Change the URL i18n param during the runtime, whithout reloading the whole view/app;
Deal with the language changes correctly, which means, if my approach based on weights isn't the better way to go, if you suggest me something else.
Edit 1:
A $watcher doesn't do the trick because the app needs the correct locale in every path, even before it instantiates all the elements. AngularJS is used in every step of this check, but if there's any clue to do this outside, before Angular instantiates, we can discuss about it.
For now, I'm using the accepted answer below, with a solution that I developed, but it has to be improved.
I've ended up doing a kind of preliminary parse on the URL. Using only ngRoute (ui-router wasn't an option...), I check if the path matches with the restrictions, if not, a redirect is triggered, defining correctly the path.
Below, follows a snippet of the solution, for the primary route, and a simple subsequent example, due to the quantity of the routes on the app, and their specific data, that doesn't belongs to the basis idea:
$routeProvider
.otherwise({
redirectTo: function () {
return '/404/';
}
})
.when('/:i18n/', {
redirectPath: '/:i18n/',
templateUrl: 'home.html',
controller: 'HomeCtrl',
resolve: {
i18n: [
'$location',
'$route',
'i18nService',
function ($location, $route, i18nService) {
var params = $route.current.params;
return i18nService.init(params.i18n)
.then(null, function (fallback) {
if (params.i18n === '404') {
return $location.path('/' + params.i18n + '/404/').search({}).replace();
}
$location.path('/' + fallback).search({}).replace();
});
}
],
data: [
'dataService',
function (dataService) {
return dataService.resolve();
}
]
}
})
.when('/:i18n/search/:search/', {
redirectPath: '/:i18n/search/:search/',
templateUrl: 'search.html',
controller: 'SearchCtrl',
resolve: {
i18n: [
'$location',
'$route',
'i18nService',
function ($location, $route, i18nService) {
var params = $route.current.params;
return i18nService.init(params.i18n)
.then(null, function (fallback) {
$location.path('/' + fallback + '/search/').search({
search: params.search
}).replace();
});
}
],
data: [
'$route',
'searchService',
function ($route, searchService) {
var params = $route.current.params;
return searchService.resolve({
'search': params.search
});
}
]
}
});
The redirectPath property is used in case of you need the pattern for that route, because ngRoute keeps one copy for private use, but doesn't give access to it with $route public properties.
Due to the required parse before any app request (except the i18nService, which loads the locale list and triggers angular-translate to load the translations), this methods causes several redirects, which leads to a significant delay on the instantiation.
If any improvements are possible, I'll thanks for, also, if I found a better way to do it, I'll update this answer with it.