ngParty / ng-metadata

Angular 2 decorators and utils for Angular 1.x
https://hotell.gitbooks.io/ng-metadata/content/
MIT License
355 stars 46 forks source link

create @StateConfig #27

Closed Hotell closed 8 years ago

Hotell commented 8 years ago

We need to register states for component via @StateConfig similar like ng-forward does

API:

@StateConfig will provide all options which has uiRouter for $stateParams except those, which can be injectable. Injectable needs to be provided by child component which also makes sense to encapsulate those things together, because they are related.

property description
name unique state name
url A url fragment with optional parameters.
component Reference to Child component which should be activated on this state
template? will override component.selector to custom template
templateUrl? will override component.selector to custom template
parent? uiRouter specific
views? uiRouter named views
abstract? uiRouter abstractState
reloadOnSearch? uiRouter specific
params? uiRouter specific

How to resolve:

because resolve can be injectable, define static methods on your child component with @Resolve method decorator.

You shouldn't be using resolve anyway!

so you can end up with

@Component({})
class Child{

  @Resolve()
  static boo(){}

  @Resolve()
  static usesDi(@Inject('$http') $http){ return $http.get('/get/me'); }

 // and you can get those values from $state
  constructor(@Inject('$state') $state) {

       // get resolve property
        const {boo, usesDi} = $state.$current.locals;

    }

}

which will transform to:

Child.resolve = { 
  boo: function(){},
 // this is properly annotated via $inject, behind the scenes
  usesDi: function($http){ return $http.get('/get/me'); }
}

and this will be used on parent stateConfig creation

How to onExit | onEnter:

because onEnter,onExit can be injectable, implement OnExit,OnEnter interface on your child component and use `@Inject if there are any Injectables. see #28 for more

Example

import { Component, StateConfig, provide, makeDirective } from 'ng-metadata/ng-metadata';
import * as angular from 'angular';
import * as uiRouter from 'ui-router';

@Component({
    selector: 'child-a',
    template: '{{ ctrl.text }}' // will be 'A resolved!'
})
class ChildA {

    text: string;

   @Resolve()
   static resolveA() {
        return 'A resolved!';
    }

    constructor(@Inject('$state') $state) {

       // get resolve property
        this.text = $state.$current.locals.resolveA;

    }

}

@Component({
    selector: 'child-z',
    template: 'Im child Z '
})
class ChildZ {}

@Component({
    selector: 'parent',
    template: `<ui-view></ui-view>`
})
@StateConfig([
    { name: 'childA', url: '/childA', component: ChildA },
    { name: 'childZ', url: '/childZ', component: ChildZ }
])
class Parent {}

angular.module('app',[uiRouter])
  .config(provide(Parent))
  .directive(provide(Parent), makeDirective(Parent))
  .directive(provide(ChildA), makeDirective(ChildA))
  .directive(provide(ChildZ), makeDirective(ChildZ));
Hotell commented 8 years ago

closing this, uiRouter is not the way forward, instead we will implement @Route for componentRouter

aciccarello commented 8 years ago

@Hotell, ui-router is working on Angular 2 support (see their 1.0-alpha.3 release). For our team, we are already using TypeScript and ui-router, so using ng-metadata for annotation support would be helpful with state configuration.

I'm honestly not sure how ui-router for ng1 maps to ui-router for ng2 but would you reconsider ui-router support if it's a viable option for ng2. Alternatively, how hard would it be to create 3rd party annotations that can work along side ng-metadata to add ui-router support.

Hotell commented 8 years ago

Hey @aciccarello . Hmm so I did take a look at ng2-ui-router and with all do respect it looks pretty bad to me, that is not declarative at all... :( they don't even use decorators... so it doesn't make sense to me, to create decorators for ui-router with ng1 which would not be usable for ng2-ui-router. Maybe after ng2-ui-router gets stable API we can mirror its API for ng1-ui-router so you have smooth transition to ng2.

Anyway, at work we are using ui-router but it's perfectly fine for us to use it traditionall way, smthg like this:

// app.states.ts
export class StateConfig{
  static $inject = [
    '$stateProvider',
    '$urlRouterProvider'
  ];
  constructor(
    private $stateProvider,
    private $urlRouterProvider
  ){
    this.$stateProvider.state('home',/*...*/);
  }
}
// app.ts
import * as angular from 'angular';
import * as uiRouter from 'ui-router';
import {StateConfig} from './app.states';

const AppModule = angular.module('myApp',[uiRouter])
  .config(StateConfig)
  .directive(/*...*/);
aciccarello commented 8 years ago

Okay, thanks for the comments. That makes sense. We'll have to see how the ui-router API processes.

I wrote a custom decorator to put route config on a static property of the controller that is picked up by an angular config helper. This at least puts our configuration closer to our state controllers. I assume this would still be possible on top of ng-metadata.

aciccarello commented 8 years ago

After reviewing your docs and source a little more, the approach we are taking for registering routes doesn't seem to conflict with any part of ng-metadata since angular.module isn't changed.

Hotell commented 8 years ago

Of course :), ng-metadata is written to not break anything, so you are very flexible indeed. But if you really want I can reopen this ticket... But ngComponentRouter with @Route decorator is the primary target at the moment.

aciccarello commented 8 years ago

:+1: I like your decision to move towards ngComponentRouter. I'm still learning about ng-metadata and trying to see how it could fit with the project I'm working on. I was sharing my realization that because ng-metadata's architecture is mainly a set of decorators + helper functions to use with the standard angular.module registration, it's easy to work with. :smile:

Sorry I didn't grasp your code snippet earlier. It makes more sense now that I've done my research. The ui-router approach I've been using could easily be setup as its own set of decorator + helper functions.

aciccarello commented 8 years ago

Created a gist for a super basic decorator/factoryFn pair for declaring states more declaritively. It could exist as its own package for any ui-router users. Adding typings to the decorator config input would make it very user friendly. It doesn't attempt to handle the resolve/input/output case though I imagine it wouldn't be too hard to create relevant method decorators for those too.

import {RouteConfig, registerState} from './RouteConfig';

@RouteConfig({
  name: 'home',
  url: '/home'
  controller: HomePageController,
  controllerAs: 'vm'
})
class HomePageController {

}

angular.module('myApp', ['ui.router'])
  .config(registerState(HomePageController));
Hotell commented 8 years ago

may I propose following changes to your implementation?:

I would name it State instead of RouteConfig -> RouteConfig is used by Angular 2 component router, and uiRouter operates in States so it will be less confusing. Otherwise looks good. Maybe you should publish that as separate package, wdyt?

aciccarello commented 8 years ago

I agree it should be in a separate package. I've created aciccarello/ui-router-decorator to start using the name StateConfig. Still have a lot of work to do and I haven't had much experience developing open source so the work is slow going. But thanks for the feedback.