Angularjs ui-router state definition - javascript

In an angular ui-router tutorial, all states are defined in the same js file.
myApp.config(function($stateProvider) {
// An array of state definitions
var states = [
{ name: 'hello', url: '/hello', component: 'hello' },
{ name: 'about', url: '/about', component: 'about' },
{
name: 'people',
url: '/people',
component: 'people',
resolve: {
people: function(PeopleService) {
return PeopleService.getAllPeople();
}
}
},
{
name: 'people.person',
url: '/{personId}',
component: 'person',
resolve: {
person: function(people, $stateParams) {
return people.find(function(person) {
return person.id === $stateParams.personId;
});
}
}
}
]
// Loop over the state definitions and register them
states.forEach(function(state) {
$stateProvider.state(state);
});
});
However, when creating a large application, we may have a lot of states. And usually a state calls a component that calls a template and probably uses a service and so on.
Therefore, I use to define the state in a separate js file, same as I do for component, template, service, ...
So I may have for example:
home.stt.js (for state)
home.srv.js (for service)
home.cpt.js (for component)
home.html (for view)
Is it a good practice? Or it is better to have all states defined in the same file?

It is more readable, understandable, and organised, to have them split into separate files. Presumably you have a good directory structure, so major areas would have their own directories and sub-directories etc., Your config files for states can go into them to follow the same hierarchy.
This is my own experience from several mid- to large- sized projects where structure is so important for ease-of-use.

Related

Vue emit data to router view component. Not emitting data and Form submission is cancelled error

I have an array called components at the LiveEdit.vue file data. I want to add an array item which has an object inside to the components array. Everything works fine except the emitting data part. LiveEdit component is on /live url path. The child i'm emitting is NavBarEdit.vue which is on /edit/nav url path. I want to emit data form NavBarEdit to LiveEdit. I also want to change url path to go back to LiveEdit component. I somehow found a way to change routes which is this
this.$emit('addComponent', data)
this.$router.push('/live')
This is my code at LiveEdit vue file
data() {
return {
components: []
}
},
methods: {
addComponent(data) {
this.components = [...this.components, data]
}
},
This is my routes
routes: [
{
path: '/',
component: LandingPage,
},
{
path: '/live',
component: LiveEdit,
},
{
path: '/edit/nav',
component: NavBarEidt,
},
{
path: '/edit/slider',
component: SliderEdit,
}
]
How can I solve this problem? I am really in a trouble with this one. I've been dealing with this like two days. And still no luck. So, please help me out

How can I change data object in Vue component depending on route and using vue-route?

I faced issue while working on vuejs application:
I have to change titles in few vuejs components, depending on routes
routes: [
{
path: '/',
components: { //with default data
FirstScreen,
Advantages,
Slider,
},
},
{
path: '/moscow',
components: { //with special data for Moscow
FirstScreen,
Advantages,
Slider,
},
},
{
path: '/berlin',
components: { //with special data for Berlin
FirstScreen,
Advantages,
Slider,
},
},
],
and data at all .vue files looks like this
data() {
return {
defaultTitle: 'some string',
defaultArray: ['defaultFirst', 'defaultSec'],
};
},
And I have about 100 cities... how can I solve this issue?
Assuming you use vue-router you can 'hook into' one of it's extremely helpful methods called beforeEach. This methods acts as a sort of middleware and runs before any given route is executed.
router.beforeEach((to, from, next) => {
document.title = to.meta.title
next();
});
If someone will need to implement data transfer depending on routes and really want to hardcode it to route file it is possible with this: https://github.com/vuejs/vue-router/blob/dev/docs/en/essentials/passing-props.md
In my case it will be looks like:
routes: [
{
path: '/',
components: {
FirstScreen,
Advantages,
BigSlider,
},
props: {
FirstScreen: {
title: 'title',
subtitle: ['arr1', 'arr2'],
},
Advantages: {
title: 'title',
advantages: 'advantages',
},
BigSlider: {
title: 'title',
slideText: 'slideText',
},
},
},
and in component you have to do something like this
export default {
props: {
title: {
type: String,
},
subtitle: {
type: Array,
},
},
It will work fine, but I agree with Kevin Karsopawiro in part that this approach is unmaintainable. So using city-component for fetching data from back-end is best in my case.

Best Practice: Handle read-only data with Redux

I am building an application with React and Redux and I have a question about design.
My application uses React Router. It has a Navigation Bar (on the left) that displays the routes from React Router configuration file with a Material Design Menu.
I would know what is the best practice to handle the static data of this LeftNav component.
This data has the following shape:
const menuItems = [
{
icon: 'home',
label: 'Home',
url: '/home',
withDivider: true,
access: 'all',
},
{
icon: 'settings',
label: 'Settings',
url: '/settings',
access: 'admin',
},
{
icon: 'power_settings_new',
label: 'Log Out',
url: '/logout',
access: 'user',
},
];
To respect the smart and dumb component proposal, my LeftNav component is dumb and stateless.
I also have a smart component (right now it's just my AppContainer) that renders the LeftNav component and provides the menuItems array to it via props.
I wonder if I must include this read-only data into my redux state tree. In this case, I would have a constant reducer like this:
export default handleActions({}, [
{
icon: 'home',
label: 'Home',
url: '/home',
withDivider: true,
access: 'all',
},
{
icon: 'settings',
label: 'Settings',
url: '/settings',
access: 'admin',
},
{
icon: 'power_settings_new',
label: 'Log Out',
url: '/logout',
access: 'user',
},
]);
Is it a good practice to have a constant reducer with no action handler? If not, what should I do?
Thanks in advance.
I'm not recognizing how your reducer is working. What is handleActions?
I think a constant reducer seems like a reasonable solution, but I would implement it by using a default first parameter. Assuming you're using combineReducers to scope all your reducers to the part of the state they control, I'd do something like:
/* reducers.js */
const menuItemsInitialState = [
{
icon: 'home',
label: 'Home',
url: '/home',
withDivider: true,
access: 'all',
},
{
icon: 'settings',
label: 'Settings',
url: '/settings',
access: 'admin',
},
{
icon: 'power_settings_new',
label: 'Log Out',
url: '/logout',
access: 'user',
},
];
export function menuItems(state = menuItemsInitialState, _) {
return state;
}
Personally I prefer my reducers to focus on dealing with application / system-wide state. The configuration in your example would feel more at home living as a constant inside the component which makes use of it (LeftNav?)
#Cnode I agree #acjay that it should be in reducer state, even without action creators.
I would go one step further and suggest that you should fetch the data after the app loads rather than including the data in your built files. Generally data does not belong in built files.
This is because, in the real world this particular set (and others - possibly much larger sets) would be statically available on an API as you probably would want the values configurable by a CMS.
Add a static reducer to host this state
Fetch the data after the app initialises and then push it on state - meaning you will have at least one action creator.

Supporting multiple parameters with nested properties in Meteor and Iron Router

Using Meteor and Iron Router, I've created dynamic page paths that use multiple parameters. However, if I attempt to access nested/child properties in my path, the route breaks. These posts were helpful but did not address child-properties:
Iron-router nested routes with multiple parameters
meteor iron-router nested routes
Iron Router
this.route('location',{
path: '/properties/:foo/:_id',
waitOn: function(){
return Meteor.subscribe('properties', this.params._id);
},
action: function(){
this.render('propertyPage', {
data: function(){
return Properties.findOne(this.params._id);
}
});
}
});
Markup (Works)
Click Me
When attempting to reference a nested property in the markup, it breaks:
Markup (NOT working)
Click Me
I also tried it inside of the javascript, with no luck:
path: '/properties/:foo.nestedChild/:_id',
Is there a way to reference a nested property without breaking Iron Router?
- - - Edit - - -
For a more practical example:
// data context from route action (Properties.findOne(this.params._id))
property = {
_id: "3cu7B8b6K3EzCgYnQ"
address: {
city: 'Houston',
state: 'TX',
zip: 77006,
lat: null,
lng: null
},
images: ['img1.png', 'img2.png', 'img3.png'],
schools: [
{ grade:'elementary', name:'Haude', rating:4 },
{ grade:'middle', name:'Strauke', rating:5 },
{ grade:'high', name:'Klein', rating:3 },
]
}
I'm trying to build out a url schema like this:
path: '/properties/:address.city/:address.state/:address.zip/:_id'
or in the example's case:
"/properties/Houston/TX/77006/3cu7B8b6K3EzCgYnQ"
In your route, you need to fetch :foo from the params object if you want to use it:
var foo = this.params.foo;
It's little too late, but somebody might benefit anyway. I solved it the following way:
Defining a nested path (BTW Defining paths this way is better for SEO)
Content
Router
this.route('playlistItem', {
path: '/user/:owner/playlist/:playlist',
onBeforeAction: function() {
// You can get your params
var ownerId = this.params.owner
var playlistId = this.params.playlist
// execute some code
},
});

How to make Automated Dynamic Breadcrumbs with AngularJS + Angular UI Router

One key component to web applications is breadcrumbs/navigation. With Angular UI Router, it would make sense to put the breadcrumb metadata with the individual states, rather than in your controllers. Manually creating the breadcrumbs object for each controller where it's needed is a straight-forward task, but it's also a very messy one.
I have seen some solutions for automated Breadcrumbs with Angular, but to be honest, they are rather primitive. Some states, like dialog boxes or side panels should not update the breadcrumbs, but with current addons to angular, there is no way to express that.
Another problem is that titles of breadcrumbs are not static. For example, if you go to a User Detail page, the breadcrumb title should probably be the user's Full Name, and not a generic "User Detail".
The last problem that needs to be solved is using all of the correct state parameter values for parent links. For example, if you're looking at a User detail page from a Company, obviously you'll want to know that the parent state requires a :companyId.
Are there any addons to angular that provide this level of breadcrumbs support? If not, what is the best way to go about it? I don't want to clutter up my controllers - I will have a lot of them - and I want to make it as automated and painless as possible.
Thanks!
I did solve this myself awhile back, because nothing was available. I decided to not use the data object, because we don't actually want our breadcrumb titles to be inherited by children. Sometimes there are modal dialogs and right panels that slide in that are technically "children views", but they shouldn't affect the breadcrumb. By using a breadcrumb object instead, we can avoid the automatic inheritance.
For the actual title property, I am using $interpolate. We can combine our breadcrumb data with the resolve scope without having to do resolves in a different place. In all of the cases I had, I just wanted to use the resolve scope anyway, so this works very well.
My solution also handles i18n too.
$stateProvider
.state('courses', {
url: '/courses',
template: Templates.viewsContainer(),
controller: function(Translation) {
Translation.load('courses');
},
breadcrumb: {
title: 'COURSES.TITLE'
}
})
.state('courses.list', {
url: "/list",
templateUrl: 'app/courses/courses.list.html',
resolve: {
coursesData: function(Model) {
return Model.getAll('/courses');
}
},
controller: 'CoursesController'
})
// this child is just a slide-out view to add/edit the selected course.
// It should not add to the breadcrumb - it's technically the same screen.
.state('courses.list.edit', {
url: "/:courseId/edit",
templateUrl: 'app/courses/courses.list.edit.html',
resolve: {
course: function(Model, $stateParams) {
return Model.getOne("/courses", $stateParams.courseId);
}
},
controller: 'CourseFormController'
})
// this is a brand new screen, so it should change the breadcrumb
.state('courses.detail', {
url: '/:courseId',
templateUrl: 'app/courses/courses.detail.html',
controller: 'CourseDetailController',
resolve: {
course: function(Model, $stateParams) {
return Model.getOne('/courses', $stateParams.courseId);
}
},
breadcrumb: {
title: '{{course.name}}'
}
})
// lots more screens.
I didn't want to tie the breadcrumbs to a directive, because I thought there might be multiple ways of showing the breadcrumb visually in my application. So, I put it into a service:
.factory("Breadcrumbs", function($state, $translate, $interpolate) {
var list = [], title;
function getProperty(object, path) {
function index(obj, i) {
return obj[i];
}
return path.split('.').reduce(index, object);
}
function addBreadcrumb(title, state) {
list.push({
title: title,
state: state
});
}
function generateBreadcrumbs(state) {
if(angular.isDefined(state.parent)) {
generateBreadcrumbs(state.parent);
}
if(angular.isDefined(state.breadcrumb)) {
if(angular.isDefined(state.breadcrumb.title)) {
addBreadcrumb($interpolate(state.breadcrumb.title)(state.locals.globals), state.name);
}
}
}
function appendTitle(translation, index) {
var title = translation;
if(index < list.length - 1) {
title += ' > ';
}
return title;
}
function generateTitle() {
title = '';
angular.forEach(list, function(breadcrumb, index) {
$translate(breadcrumb.title).then(
function(translation) {
title += appendTitle(translation, index);
}, function(translation) {
title += appendTitle(translation, index);
}
);
});
}
return {
generate: function() {
list = [];
generateBreadcrumbs($state.$current);
generateTitle();
},
title: function() {
return title;
},
list: function() {
return list;
}
};
})
The actual breadcrumb directive then becomes very simple:
.directive("breadcrumbs", function() {
return {
restrict: 'E',
replace: true,
priority: 100,
templateUrl: 'common/directives/breadcrumbs/breadcrumbs.html'
};
});
And the template:
<h2 translate-cloak>
<ul class="breadcrumbs">
<li ng-repeat="breadcrumb in Breadcrumbs.list()">
<a ng-if="breadcrumb.state && !$last" ui-sref="{{breadcrumb.state}}">{{breadcrumb.title | translate}}</a>
<span class="active" ng-show="$last">{{breadcrumb.title | translate}}</span>
<span ng-hide="$last" class="divider"></span>
</li>
</ul>
</h2>
From the screenshot here, you can see it works perfectly in both the navigation:
As well as the html <title> tag:
PS to Angular UI Team: Please add something like this out of the box!
I'd like to share my solution to this. It has the advantage of not requiring anything to be injected into your controllers, and supports named breadcrumb labels, as well as using resolve: functions to name your breadcrumbs.
Example state config:
$stateProvider
.state('home', {
url: '/',
...
data: {
displayName: 'Home'
}
})
.state('home.usersList', {
url: 'users/',
...
data: {
displayName: 'Users'
}
})
.state('home.userList.detail', {
url: ':id',
...
data: {
displayName: '{{ user.name | uppercase }}'
}
resolve: {
user : function($stateParams, userService) {
return userService.getUser($stateParams.id);
}
}
})
Then you need to specify the location of the breadcrumb label (displayname) in an attribute on the directive:
<ui-breadcrumbs displayname-property="data.displayName"></ui-breadcrumbs>
In this way, the directive will know to look at the value of $state.$current.data.displayName to find the text to use.
$interpolate-able breadcrumb names
Notice that in the last state (home.userList.detail), the displayName uses the usual Angular interpolation syntax {{ value }}. This allows you to reference any values defined in the resolve object in the state config. Typically this would be used to get data from the server, as in the example above of the user name. Note that, since this is just a regular Angular string, you can include any type of valid Angular expression in the displayName field - as in the above example where we are applying a filter to it.
Demo
Here is a working demo on Plunker: http://plnkr.co/edit/bBgdxgB91Z6323HLWCzF?p=preview
Code
I thought it was a bit much to put all the code here, so here it is on GitHub: https://github.com/michaelbromley/angularUtils/tree/master/src/directives/uiBreadcrumbs
I made a Angular module which generate a breadcrumb based on ui-router's states. All the features you speak about are included (I recently add the possibility to ignore a state in the breadcrumb while reading this post :-) ) :
Here is the github repo
It allows dynamic labels interpolated against the controller scope (the "deepest" in case of nested/multiple views).
The chain of states is customizable by state options (See API reference)
The module comes with pre-defined templates and allows user-defined templates.
I do not believe there is built in functionality, but all the tools are there for you, take a look at the LocationProvider. You could simply have navigation elements use this and whatever else you want to know just inject it.
Documentation
After digging deep into the internals of ui-router I understood how I could create a breadcrumb using resolved resources.
Here is a plunker to my directive.
NOTE: I couldn't get this code to work properly within the plunker, but the directive works in my project. routes.js is provided merely for example of how to you can set titles for your breadcrumbs.
Thanks for the solution provided by #egervari. For those who need add some $stateParams properties into custom data of breadcrumbs. I've extended the syntax {:id} for the value of key 'title'.
.state('courses.detail', {
url: '/:courseId',
templateUrl: 'app/courses/courses.detail.html',
controller: 'CourseDetailController',
resolve: {
course: function(Model, $stateParams) {
return Model.getOne('/courses', $stateParams.courseId);
}
},
breadcrumb: {
title: 'course {:courseId}'
}
})
Here is an Plunker example. FYI.

Categories

Resources