-
Notifications
You must be signed in to change notification settings - Fork 7
Using A1AtScript With UI Router
Although A1AtScript has no built in injector for UI-Router, you can register your own! Below you'll see two approaches to doing @State decorators.
This approach is great is you want your states to be declared side-by-side with your components. Here's how you could use it:
@Component({
selector: 'products-list',
controllerAs: 'vm',
properties: {
state: 'state'
}
})
@View({
inline: `
<h1>{{ vm.id }}</h1>
<div ng-repeat="product in vm.products">{{ product.name }}}</div>
`
})
@State('MyState', {
url: '/products/:id',
resolve: {
products: ['$http', ($http) => $http.get('/api/products/all')]
}
/* leave these out
controller: auto-generated
template: auto-generated
*/
})
export default class HomePageComposer {
constructor() {
// All params are available from `this.state.params`
this.id = this.state.params.id;
// All fully resolved dependencies are in `this.state.resolve`
this.products = this.state.resolve.products;
}
}
Note: a1atscript v0.4.1 needed Here is the implementation:
import {getInjector, ToAnnotation, registerInjector} from 'a1atscript';
// Use the new hooks feature from v0.4.1 to register a hook that is run
// right after every component's `angular.directive` call is made.
getInjector('component').componentHooks.after.push(configureState);
// Each hook function gets the module and the ddo. That should be enough data to
// create almost any custom decorator that you'd want to work with Components.
// Some ddo exploration will be needed to find what you are after ;)
function configureState(module, ddo) {
let component = ddo._controller;
let annotations = component.annotations;
let stateInstance = annotations.find((ann) => ann instanceof State.originalClass);
if (stateInstance) {
// console.log('Configure State:', ddo, ddo._controller, ddo._controller.annotations);
stateInstance.generateControllerAndTemplate(ddo._annotation.selector);
module.config(['$stateProvider', function($stateProvider) {
$stateProvider.state(stateInstance.name, stateInstance.config);
}]);
}
}
// Create the State annotation
@ToAnnotation
export class State {
constructor(name, config) {
this.name = name;
this.config = config;
}
generateControllerAndTemplate(selector) {
if (this.config.resolve) {
let injectedResolves = Object.keys(this.config.resolve).map(name => name);
let params = {}
function controller($scope, $stateParams, ...resolves) {
$scope.state = {};
$scope.state.params = $stateParams;
$scope.state.resolve = resolves.reduce((obj, val, i) => {
obj[injectedResolves[i]] = val;
return obj;
}, {});
}
this.config.controller = ['$scope', '$stateParams', ...injectedResolves, controller];
}
let componentHtml = `<${selector} bind-state="state"></${selector}>`;
this.config.template = this.config.template || componentHtml;
}
}
export class StateInjector {
get annotationClass() {
return State;
}
instantiate() { }
}
// Register it.
registerInjector('state', StateInjector);
This method keeps your states separate from your components and opts for classes instead of state configs. One of the things that has always annoyed me about ui-router is you write your states into a config block. Wouldn't it be nice if you could do something like this:
@State('root.main.inner')
class RootMainInnerState {
constructor() {
this.template = 'awesome/awesome.html'
this.controller = 'AwesomeController'
}
@Resolve('Backend')
model: function(Backend) {
}
@Resolve('AuthService')
user: function(AuthService) {
}
}
Well the good news is you could potentially do that. Just define an Annotation and an Injector
import {ToAnnotation, registerInjector} from 'bower_components/dist/a1atscript'
@ToAnnotation
export class State {
constructor(stateName) {
this.stateName = stateName;
}
}
@ToAnnotation
export class Resolve {
constructor(...inject) {
this.inject = inject;
}
}
// An Injector must define an annotationClass getter and an instantiate method
export class StateInjector {
get annotationClass() {
return State;
}
annotateResolves(state) {
state.resolve = {}
for (var prop in state) {
if (typeof state[prop] == "function") {
var resolveItem = state[prop];
resolveItem.annotations.forEach((annotation) => {
if (annotation instanceof Resolve) {
resolveItem['$inject'] = annotation.inject;
state.resolve[prop] = resolveItem;
}
});
}
}
}
instantiate(module, dependencyList) {
var injector = this;
module.config(function($stateProvider) {
dependencyList.forEach((dependencyObject) => {
var metadata = dependencyObject.metadata;
var StateClass = dependencyObject.dependency;
var state = new StateClass();
injector.annotateResolves(state);
$stateProvider.state(
metadata.stateName,
state
);
});
})
}
}
registerInjector('state', StateInjector);
That code works -- I've used it in my own projects for making ui-router easy to use. The best part is then you can create base states with common resolves and the extend them for your individual states.