Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

idea - bind resolved data to controller #2175

Closed
GabiGrin opened this issue Aug 14, 2015 · 17 comments
Closed

idea - bind resolved data to controller #2175

GabiGrin opened this issue Aug 14, 2015 · 17 comments

Comments

@GabiGrin
Copy link

I'm trying to make everything in my app a component(directive), and resolve as much as possible in the ui-router state, to ensure the component has all it needs when the state is loaded.
This is an example of what I'm referring to:

.state('my-state', {
  template: '<my-component data="ctrl.data" type="ctrl.type"></my-component>',
  resolve: {
    data: function($http) {
      return $http.get('/api/getMyData');
    },
    type: function(myService) {
      return myService.getType();
    }
  },
  controller: function(data, type) {
    this.data = data;
    this.type = type;
  },
  controllerAs: 'ctrl'
});

I find my self needing to repeat the this.data = data, this.type = type parts for almost all my states.

I think that supporting a bindToController option, that will insert resolved data in the controller will be helpful here, similar to a directive's definition object.

What do you guys think? if this sounds right I can even try to implement it, as it looks like a simple addition.

@eddiemonge
Copy link
Contributor

I think it might be harder then you think it is. Have you looked to see if the feature-1.0 branch does this yet? If not, you can try taking a stab at it to see if its possible.

@nateabele
Copy link
Contributor

If you're writing ES6, you can just do angular.extend(this, { data, type }).

@GabiGrin
Copy link
Author

I am, but I'm want to remove the need to even to that.
I might be missing something, but it makes sense to me while attempting to treat each state as a component that receives data solely via resolves when it comes to external state.

It'll probably look like that:

.state('my-state', {
  template: '<my-component data="ctrl.data" type="ctrl.type"></my-component>',
  resolve: {
    data: function($http) {
      return $http.get('/api/getMyData');
    },
    type: function(myService) {
      return myService.getType();
    }
  },
  controller: angular.noop,
  controllerAs: 'ctrl'
});

The angular.noop + controllerAs might be dropped if the resolve params are added to the created $scope.

I'll fork and try to do achieve it. Will update so you guys can decide wether it fits the main branch or not. Worst case I'll stick to the fork :)

@nateabele
Copy link
Contributor

You could probably combine controllerProvider with some state introspection to do this automatically.

@christopherthielen
Copy link
Contributor

Huh, I thought I commented already...

I think the idea behind this request is reasonable. I've considered something very similar myself. This is something we may want to consider post-1.0

@amcdnl
Copy link
Contributor

amcdnl commented Aug 27, 2015

I'd love to see this make it in too. I think a bindToController option would be great here. My controllers often directly take all those arguments and attach them right away to the controller.

@juristr
Copy link

juristr commented Aug 27, 2015

👍

@christopherthielen
Copy link
Contributor

In 1.0, resolves are now much lazier than they are in 0.2.15. For example, if a resolve is defined but never requested for injection, the resolve fn may never be called (we're calling this the "JIT" resolve policy). We'd have to decide on some rules for behavior.

In 1.0, we've got a few building blocks that could help implement this idea:
We have a Resolvable object, which has a .get() fn which returns the resolved data, or a Promise for the resolved data.
We have a ResolveContext object, which has a .getResolvables() fn, which returns a map of all the resolvables for that context.
We have a Path object for the path in the state tree that is being entered. Each node in the path corresponds to a nested state, and has its own ResolveContext.

I think we could expose the ResolveContext object as an injectable object for all the ui-router injection points (similar to $stateParams), then let the callers explicitly use the resolve data. I think I'd prefer this over another configuration option

Perhaps something like this:

.state({ 
  name: 'foo', 
  resolve: { 
    resolve1: ($http) => $http.get("/foo.json").then((resp) => resp.data) ,
    resolve2: ($http) => $http.get("/bar.json").then((resp) => resp.data)
  },
  resolvePolicy: { resolve2: "JIT" },
  controller: ($resolve$) => {
    let keys = Object.keys($resolve$); // [ "resolve1", "resolve2" ]
    let resolvedFoo = $resolve$.resolve1; // data is fully resolved
    let resolveBar = $resolve$.resolve2; // a JIT promise for the resolved data?
  }
})
.state({ 
  name: 'foo.bar', 
  resolve: { 
    resolve3: ($http) => $http.get("/baz.json").then((resp) => resp.data) ,
  },
  controller: ($resolve$, resolve2) => {
    let keys = Object.keys($resolve$); // [ "resolve1", "resolve2", "resolve3" ]
    let resolvedFoo = $resolve$.resolve1; // data is fully resolved
    let resolveBar = $resolve$.resolve2; // data is fully resolved because of injection 
    let resolveBaz = $resolve$.resolve3; // data is fully resolved
    angular.extend(this, $resolve$); // resolved data is on controller
  }
})

Another possible approach would be to resolve everything (even JIT resolves) if $resolve$ is injected anywhere.

@christopherthielen
Copy link
Contributor

summon @nateabele @wesleycho @eddiemonge

@nateabele
Copy link
Contributor

I think we could expose the ResolveContext object as an injectable object for all the ui-router injection points (similar to $stateParams), then let the callers explicitly use the resolve data. I think I'd prefer this over another configuration option

Concur 100%.

resolve: { 
    resolve1: ($http) => $http.get("/foo.json").then((resp) => resp.data) ,
    resolve2: ($http) => $http.get("/bar.json").then((resp) => resp.data)
},
resolvePolicy: { resolve2: "JIT" },

I think I'd prefer a structure that involved less repetition, but that's sort of a minor thing.

@wesleycho
Copy link
Contributor

I feel like users should explicitly choose to bind data in the controller.

bindToController, if implemented, should probably match how bindToController works in the DDO as part of the Angular API itself, and not handle resolves that way.

@uetkaje
Copy link

uetkaje commented Oct 8, 2015

+1

@wesleycho
Copy link
Contributor

I would like to add that if one is using TypeScript, one can easily bind to the controller by declaring public or private in the constructor for the controller.

IMO this shouldn't be supported in the library itself. UI Router is already very config heavy, and adding more minor config bloats up the library with what is supported & can potentially hurt extendibility down the road.

@red2678
Copy link

red2678 commented Jan 20, 2016

+1

@fesor
Copy link

fesor commented Feb 7, 2016

-1 on this.

It would be better to just export $resolve to scope, especially since angular shifting to component-based approach. This is already done in ngRoute.

resolve: { 
    resolve1: ($http) => $http.get("/foo.json").then((resp) => resp.data) ,
    resolve2: ($http) => $http.get("/bar.json").then((resp) => resp.data)
},
template: `<my-component foo="$resolve.resolve1" bar="$resolve.resolve2"></my-component>`

Check documentation for angular 1.5 for details: https://code.angularjs.org/1.5.0/docs/guide/component#components-as-route-templates

@davincho
Copy link

+1 for @fesor

@christopherthielen christopherthielen added this to the 0.2.19 milestone Mar 4, 2016
christopherthielen added a commit that referenced this issue Mar 5, 2016
feat(uiView): Expose the resolved data for a state as $scope.$resolve
feat(uiView): Route to components using templates
closes #2175
closes #2547
@vituchon
Copy link

dear @GabiGrin perhaps something like this will do the trick

state('route_param_state', {
    url: '/a_path/:anId',
    templateUrl: '/the_template.html',
    controller: 'The Name of the register controler',
    controllerAs: 'ctr', // keep it simple
    resolve: {
      "": ($http: ng.IHttpService, $stateParams: ng.ui.IStateParamsService) => {
        var parsedId =  +$stateParams["anId"]
        return $http.get("/your_api/v1/thing/" + parsedId).then((response) => {
          $stateParams["data"] = response.data
        })
      }
    }
   })

And you will have in $stateParams the data you required.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests