AngularJS UI-Router Change States Of Dependent Modules - angularjs

So I am exploring different configuration options for my app. My main goal is to find a way to configure the states/routes on the highest level even if dependent angular modules have their own routes/states declared. There are several options I have come up with but I'm not sure if they are possible or not:
Assume I have module1 which is dependent on module2
1: If module2 declares its own states/routes, is there a way I can delete or override them from module1?
2: Is there a way I can get a list of recommended states (objects that represent states such as {name: 'state', templateURL: '/someURL'} and use module1 to logically determine which stateObjects to give the $stateProvider?
3: Is it possible to access the stateProvider NOT in the config? For example, say I have the following that gets setup in the config:
tinkModule.config(function($stateProvider, $urlRouterProvider){
$stateProvider.state('create', {
url: '/create',
templateUrl: 'templates/create.html'
});
$stateProvider.state("otherwise", {
url: "/otherwise",
templateUrl: 'templates/tinkOtherwise.html'
});
$urlRouterProvider.otherwise('/otherwise');
});
Can I add a 'review' state after they have been through the create state?
Please let me know if there are other options that I am not thinking about and also let me know if any of the above is possible.

Sounds like your main goal is to define states across modules and also lazy load states. There is some existing features to accomplish these.
Register states across various modules:
https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions#how-to-configure-ui-router-from-multiple-modules
You'll need to build 0.3.0 manually with grunt though: https://github.com/angular-ui/ui-router#developing
Then if you want to lazy load (define states on-the-fly) you can read about the $stateNotFound event: https://github.com/angular-ui/ui-router/wiki#state-change-events
And look at the ui-router test for lazy loading: https://github.com/angular-ui/ui-router/blob/master/test/stateSpec.js#L196-L208
The test shows how you might try to go to an undefined state, get the $stateNotFound error, which allows you to define the missing state at that time. It will always try once more to find the state, assuming that you may have defined it in the error callback.

Related

Dynamic nested routing in angularJs

Trying to implement the following route system with angular stateProvider:
some_domain.com/abc/pqr/xyz
Issue is the number of levels after "some_domain.com" can vary. It can be like
some_domain.com/abc/pqr/xyz
some_domain.com/abc/pqr
some_domain.com/abc
some_domain.com/abc/pqr/xyz/1234
tried the many approaches for this in the state definition:
$stateProvider
.state('dummy', {
url: '/*',
templateUrl: 'views/home.html',
controller: 'LandingPage'
});
}]);
Can anybody help me regarding this?
As the parameters you are talking about are part of the route itself, so you cannot have a single route to handle all these cases. I can think of 2 ways to go about it :
Create a seperate route for each case.
Use optional parameters if you can modify the logic accordingly. If the parameters can be optional, they should sent as query parameters instead of making them part of URL as such. So, the URL becomes something like :
url: '/dummy?param1?param2?param3'

Multiple states and urls for the same template

I'm building a web application and I have a screen that consists in five sections, each section represents a level, the areas are the higher level of my tree, when I click in any card of the area, the system should return the skills of that area and so on.
I need to change the url and state according what the user is accessing, for example, if the user access some skill, the url must be
example.com/#/curriculum/skill/<skillId>
and if I access this link it should automatically load the capabilities from this skill and his parent which is area in this case.
I have one controller for area, skill, capability, knowledge and criteria, in each controller I have a action to load the next level of the tree, which looks like that
$scope.loadSkills = function (id) {
Area.loadSkills(...)
$state.go('curriculo.skill', {id: this.current.id}, {nofity: false, reload: false});
}
And these are my states
$stateProvider
.state('curriculum', {
url: '/curriculum',
templateUrl: '/templates/curriculo.html',
})
.state('curriculum.are', {
url: '/area/:id',
template: '',
})
.state('curriculum.skill', {
url: '/skill/:id',
template: '',
})
.state('curriculum.capability', {
url: '/capability/:id',
})
.state('curriculum.knowledge', {
url: '/knowledge/:id',
})
.state('curriculum.criteria', {
url: '/criteria/:id',
});
I'm new in Angular and I not sure about what to do, should I created multiple nested views in this case, and if so, how do I load stuff that I need according the url?
I would suggest to use the capability of multiple named views offered by the ui-router. You can read more about it here. Basically the documentation says the following:
You can name your views so that you can have more than one ui-view per
template.
If you check the example in the documentation, you'll notive that there are similarities between your scenario and the example, because you want to dynamically populate a different views (here named views).
Example
I tried to recreate your scenario in this JSFiddle.
First I created an abstract state which provides the different views like areas, skills etc. This is the template for the abstract state:
<div class="curriculum" ui-view="areas"></div>
<div class="curriculum" ui-view="skills"></div>
Next I created a nested state curriculo.main, which declares the different views (areas, skills etc.) you need. Each view has its own template and controller. Notice that the nested state has a resolve which initially loads the areas from a service called curriculo. If you use resolves remember that the resolve keyword MUST be relative to the state not the views (when using multiple views).
Basically the service is responsible for the business logic, means getting the areas, skills etc. In the JSFiddle I have hard-coded the HTTP results. Replace that with HTTP calls and make use of promises. Since each named view has its own controller we need a mechanism to notify about changes, for example to notify the SkillsController that skills have been loaded. Thus, I created a simple event system (subcribe-notify):
.factory('notifier', function($rootScope) {
return {
subscribe: function(scope, callback, eventName) {
var handler = $rootScope.$on(eventName, callback);
scope.$on('$destroy', handler);
},
notify: function(eventName, data) {
$rootScope.$emit(eventName, data);
}
};
});
The SkillsController can then subscribe to a specific event like so:
notifier.subscribe($scope, function(event, data) {
$scope.skills = data;
}, 'onSkillsLoaded');
The curriculo service calls (at the end of the getSkills()) notifyand provides an event. In this case the same event as you subscribed to in the SkillsController.
notifier.notify('onSkillsLoaded', result);
All in all, that's the magic behind my little example. It's worth mentioning that you need to apply best practices to the code, since this is just to recreate your scenario. For best practices I suggest the Angular Style Guide by John Papa.
Update 1
I updated my example to show you deep linking. I simulate the deep link via
$state.go('.', {area: 2, skill: 5});
This way I can activate a certain state. Now each view has its activate function. Inside this function I do all the work that is neseccary for the initialization, e.g. selecting an area if the query param is set. As you know, you can access the params with the $state service. I had to use a $timeout to delay the init of the areas controller because the subscribe wasn't made yet. Please try to find a better solution to this problem. Maybe you can use promises or register each controller in a service which resolves if all controller have been initialized.
If anything has been selected I also use the go with an additional option to set the notify to false.
$state.go('.', {area: area.id, skill: skillId ? skillId : undefined}, {notify: false});
If notify is set to false it will prevent the controllers from being reinitialized. Thus you can only update the URL and no state change will happen.

Init application waiting for REST call data result

Having created some basic web applications with Angular, still learning and discovering ...
I need to create a Angular application which depends on various parameters used in the application. For example various label string we will retrieve through REST... These labels are used on every screen of the application, including the first screen that is shown on start-up...
I'm looking for a 'proper' / 'good' / 'angular' way to init the application waiting for the result of a sort of 'init-application' rest-call - due to the fact that the result of this rest call contains data the application will need from start on, I have found an example approach below ...
https://blog.mariusschulz.com/2014/10/22/asynchronously-bootstrapping-angularjs-applications-with-server-side-data
Are there any more ideas / thoughts / useful links ...
Thanks!
I would suggest you to explore the 'resolve' property of ngRoute(if you are using ngRoute) or UI-router state:-
While using $routeProvider:-
$routeProvider
.when("/news", {
templateUrl: "newsView.html",
controller: "newsController",
resolve: {
message: function(messageService){
return messageService.getMessage();
}
}
})
Or, if you are using UI Router :-
$stateProvider.state('root',{
abstract:true, // this is abstract statte, controller is in child states.
templateUrl:'/assets/root-module/partial/main/main.html',
resolve : {
securityContext : function($http){
return $http.get("/security/context");
},
token : function(securityService,securityContext){
securityService.setToken(securityContext.data.token);
console.debug(securityContext.data.token);
return securityContext.data.token;
}
}
});
In either of the approaches, just make sure that you DO NOT provide 'ng-controller' in your HTML templates. Rather provide the controller while defining your route|state as the above example suggests.
The properties being "resolved" - if they are promises, the controller (hence the view) won't be initialized until the promise is resolved.
I highly recommend you to explore UI Router state hierarchy approach that allows you to share 'resolved; proerties to the child states.

Override the System package in mean.io

Q1) I need help with overriding System package in mean.io? I am trying to create my own home page.
So I did this
angular.module('mean.mysystem', ['mean.system'])
.config(['$viewPathProvider',
function($viewPathProvider) {
$viewPathProvider.override('system/views/index.html', 'mysystem/views/index.html');
}
]);
I get following error http://errors.angularjs.org/1.3.2/ng/areq?p0=MysystemController&p1=not+aNaNunction%2C+got+undefined
Can anyone suggest a resolution or a better way to do this?
Q2) Also, is this the right way to extend a package
Mysystem.register(function(app, auth, database, System) {
Does mean know the package to extend from a variable name?
Don't know why, but removing the ['mean.system'] worked for me. Something as below,
angular.module('mean.mysystem')
.config(['$viewPathProvider',
function($viewPathProvider) {
$viewPathProvider.override('system/views/index.html', 'mysystem/views/index.html');
}
]);
This might be the why: "angular.module('mean.mysystem', ['mean.system'])" creates a new module which probably is in conflict with the one MEAN creates during package registration.
Creation versus Retrieval
Beware that using angular.module('myModule', []) will create the module myModule and overwrite any existing module named myModule. Use angular.module('myModule') to retrieve an existing module.
See detail # https://docs.angularjs.org/guide/module
Angular Modules and Dependencies
Every package registration automatically creates a corresponding angular module of the form mean.[package-name]
See detail # https://github.com/linnovate/mean#angular-modules-and-dependencies
You can just add:
$stateProvider
.state('/', {
url: '/',
templateUrl: 'youPackage/views/index.html'
});
into your routes file.
$viewPathProvider not exist in older version of mean.io, for example in this used by openshift quickstart.

Angular: ui-router how to pass data object or some parameter to other controller through state

I'm already read about resolve, data for ui-router but still can not get how I can solve my problem?
View 1 HTML:
<a ui-sref="configuration.settings.list"><span>Settings</span></a>
<a ui-sref="configuration.settings.personal({user: userId})"><span >Personal</span></a>
userId is variable which is defined in the controller for current view.
Route definition:
angular.module("app").config(["$stateProvider", "$urlRouterProvider", "$locationProvider",
function($stateProvider, $urlRouterProvider, $locationProvider) {
$stateProvider
.state('configuration.settings.lis', {
url: '/',
templateUrl: 'html/settings',
controller: 'settingsController',
}),
.state('configuration.settings.personal', {
url: '/:userId',
templateUrl: 'html/settings',
controller: 'personalController',
}),
I can access userId in personalController through stateParams.
Question: I want pass object or some parameter from View 1 to personalController without `stateParams. Is it possible?
Something like that:
<a ui-sref="configuration.settings.personal({myDamnCooLObject})"><span >Personal</span></a>
.state('configuration.settings.personal', {
url: '/', <-- NO attributes here!!!
templateUrl: 'html/settings',
controller: 'personalController',
data: {{myDamnCooLObject}} <-- THIS object or parameter shuld be accesseble from 'personalController'
})
$stateParams are only guaranteed to be preserved for target routes that use them. ui-router actually keeps this object clean so you can't just stuff random things into it.
I've had good luck using a service as a manager object to persist items between views. For example, I have an app that has a user profile page and a photo gallery as a separate view. I want a lot of the user's core data over in the gallery because there's a title with their name, links back, a little bio box on the side, etc.
The way I wired this up, both views have a user ID in their URL as a parameter. I have a manager that maintains a cache of user data, and a getter that returns a promise to return it. In both states, I have a resolve block that takes the user ID and asks the manager for the whole data object. The manager makes a Web service call to get the data the first time, and immediately resolves for the second call (doesn't matter who gets there first). It works very well and it's lightweight.
In your example you don't want a parameter in the URL at all, but the model could work the same way. Services are global singletons so you can use them as a dumping ground for all kinds of things to be passed around and persisted through things like state transitions. Obviously this is a slippery slope because there are many bad programming practices that are also enabled once you start doing this - but with care and good patterns, it's a very useful technique.
Note that a lot of people actually use $rootScope for this same purpose - it has basically the same effect. But I personally believe a service is better because that way you have a very explicit, defined wrapper around this data you need to pass around, it's much more testable, and it's easier to document it. With $rootScope you're just dumping raw variables into a huge pile and it becomes a pile of spaghetti to pick apart later, especially if you accidentally re-use a variable name. That's causes all kinds of hard-to-find bugs. With a service you can go so far as to use getters/setters, and you could even wire the state transition ($state.go()) call into the setter.

Resources