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

Nested components with Angular 1.5 and latest ui-router version #2896

Closed
jmanuelrosa opened this issue Jul 28, 2016 · 13 comments
Closed

Nested components with Angular 1.5 and latest ui-router version #2896

jmanuelrosa opened this issue Jul 28, 2016 · 13 comments

Comments

@jmanuelrosa
Copy link

jmanuelrosa commented Jul 28, 2016

Hi! I'm trying to use the new 1.5 router and angular components kissing routes, but I arises a problem with nested components

.state('custom', {
      abstract: true,
      url: '/custom,
      component: 'custom'
    })
      .state('custom.children', {
        url: '/children',
        component: 'customChildren'
      })

custom Component has this template:

    <p>Lorem Ipsum</p>
    <div>
        <h1>My Title</h1>
        <div ui-view class='settings-content' custom-children-attr='$ctrl.childrenValue'></div>
    </div>

custom Component:

    class CustomController {
      constructor() {
        ...
      }

      $onInit() {
          this.childrenValue = 'test';
      }

      $onChanges(changes) {
        ...
      }
    }

    angular
      .module('app')
      .component('custom', {
        templateUrl: 'custom.html',
        controller: CustomController
      });

customSon is this:

class CustomChildrenController {
  constructor(...) {
    ...
  }

  $onInit() {
    ...
  }

  $onChanges(changes) {
    ...
  }
}

angular
  .module('app')
  .component('customChildren', {
    templateUrl: 'custom-children.html',
    controller: CustomChildrenController,
    bindings: {
      customChildrenAttr: '<'
    }
  });

When I go to /custom/children, I can not get the value of customChildrenAttr. How I can get the customChildrenAttr binding? I try with resolve and other things but I don't get it to work .

@jmanuelrosa jmanuelrosa changed the title Nested components with Angular 1 and latest ui-router version Nested components with Angular 1.5 and latest ui-router version Jul 28, 2016
@christopherthielen
Copy link
Contributor

We don't currently support binding attributes from the parent component, through the ui-view, to the child routed component.

This idea is something that a few people have raised. I've given it some thought, but don't know if it is a reasonable idea for ui-router architecture, or if it introduces too tight of parent/child coupling.

However, your current options for jumping the logical ui-view boundary are:

  1. require: the parent component's controller from the child component
  2. expose a container object as a resolve in the parent state. Update a property in the parent component, and bind the resolve to the child (reading the data off the property).

#1 is tightly coupling the child component to the parent.
#2 is a lot of hoops to jump through.

@jonathaningram
Copy link

@christopherthielen not sure if this is a valid use case that would help you to further consider this, but I have wanted to do something like the following in my app.

Consider a set of pages that are for managing a user's account. The base URL might be /users/123/. On the base URL we show the "Overview" tab of the user by default. Then there are a few other child URLs of this route such as /users/123/membership/ (accessed by clicking on the "Membership" tab, then maybe /users/123/access-logs/ (accessed by clicking on the "Access Logs" tab). Each of these child routes probably fetches their own data. E.g. the membership tab fetches the membership object, the access logs tab fetches the most recent access logs. Because we have a state change, refreshing the URL will mean staying on the right tab when the page reloads.

The "parent" in this case might be a component named the userViewPage. In this component the first thing that's done is to fetch the user object (e.g. some this.loadUser() call is made in the component constructor).

// parent
class ViewPageComponent {
  constructor () {
    this.loadUser()
  }
}

export default {
  template: '...',
  controller: ViewPageComponent,
  controllerAs: 'vm'
}

Every tab-page is a child of this parent state, but needs access to the user object, so it's defined as a binding. The child also fetches its own data that it specifically needs. For the record, one reason it might need the user object is maybe it needs to pass that down to some other component in the view (e.g. imagine the access logs tab-page wants to show the "last logged in" prop somewhere - it can get it straight off the bound user).

// child
class AccessLogsTabPaneComponent {
  constructor () {
    this.loadLogs()
  }
  // ...
}

export default {
  template: '...',
  controller: AccessLogsTabPaneComponent,
  bindings: {
    user: '<'
  }
}

And the route definitions that don't work atm:

export default function routes ($stateProvider) {
  $stateProvider
    .state('app.user.view', {
      url: '/:userId',
      abstract: true,
      component: 'userViewPage'
    })
    .state('app.user.view.overview', {
      url: '/',
      component: 'userViewOverviewTabPane'
    })
    .state('app.user.view.membership', {
      url: '/membership/',
      component: 'userViewMembershipTabPane'
    })
    .state('app.user.view.accessLogs', {
      url: '/access-logs/',
      component: 'userViewAccessLogsTabPane'
    })
}

As far as your comment goes that adding this functionality might introduce too tight a coupling, based on my example I can't see this being a problem - the inputs and outputs of each component are still just as clear. The child just says "hey, I need a user object" - it still doesn't know anything about the parent.

With regards to your proposed alternatives, if this feature is not something that will ever happen I'd love to see some examples of how you might do 1. or 2. above as it's not immediately clear to me. Some thoughts I've had on them:

  1. Seems like it could build a bit of a bad inheritance tree? E.g. what if your app component wants to pass something down to all children (I don't know, say a session object) - are you suggesting making every page require('app') and maybe inherit from it?
  2. I like my component to be in charge of fetching its data, not resolves in the state definition, so not sure that it allows for a container object to be created in the state resolve and passed down to a child. But maybe I'm missing something here.

Anyway, would be great to hear your thoughts, and thanks for your time. Jon.

@MystK
Copy link

MystK commented Sep 22, 2016

I'm having tons of trouble passing callbacks due to no native functionality in ui-router also.

I would expect that I could pass callbacks from the parent to child state just like a component.
The resolves also seem to be fired only once, so the one way component bindings are actually binded once.

Is there something I'm missing? It seems too complicated and hard to pass data/functions from parent to child.

EDIT: I found a solution here and it works great.

#2888 (comment)

@christopherthielen
Copy link
Contributor

I'm becoming more open to allowing attribute bindings to pass through a ui-view down to the routed component. e.g.:

// When `.child` state is active, the ui-view gets filled by 'fooComponent'
<ui-view name="namedview" on-change="$ctrl.handleChange()"></ui-view>

where on-change binding is passed through to the route-to-component template, like
<foo-component resolve-data-1="::$resolve.fooResolve" on-change="$ctrl.handleChange()"></foo-component> or something similar to this.

I don't have time to work on this now, but would be happy to work with anyone attempting a PR. I suspect it's not as simple as it seems at first glance, but I think it's possible.
@jonathaningram

@christopherthielen
Copy link
Contributor

cc: @fpipita @lucianogreiner @timothyswt @michalpanek

@shawnlan
Copy link

shawnlan commented Jan 6, 2017

How do you do this?

"expose a container object as a resolve in the parent state. Update a property in the parent component, and bind the resolve to the child (reading the data off the property)."

Any simple code example or docs? Thanks!

@christopherthielen
Copy link
Contributor

We implemented the original request for the next release. See #3111 and #3239.

I'm on my phone, so I can't give you an example of the other approach.

@shawnlan
Copy link

shawnlan commented Jan 9, 2017

Oh is this what you mean?

$stateProvider .state('home', { url: '/home', component: 'home', resolve:{container:()=>{}} })

@orneryd
Copy link

orneryd commented Jan 9, 2017

I'm becoming more open to allowing attribute bindings to pass through a ui-view down to the routed component. e.g.:

// When .child state is active, the ui-view gets filled by 'fooComponent'

where on-change binding is passed through to the route-to-component template, like
or something similar to this.

I don't have time to work on this now, but would be happy to work with anyone attempting a PR. I suspect it's not as simple as it seems at first glance, but I think it's possible.

When I look at this, something smells wrong with being able to basically inject parameters from the ui-view usage down to components. This would open up the possibility to some really bad practices and sort of "side-injecting" things in a weird way.

It seems to me that the route declaration should be solely responsible for providing models and events to the component being rendered.

I think it would be nice to have say, higher-order components which simply abstract reusable functionality, but angular 1x doesn't technically support that. What we are doing to compensate is to render a component which...renders another component... the grossness is in the dom where extraneous html tags end up being created which can complicate the CSS a little, but we haven't really found a better way yet.

@christopherthielen
Copy link
Contributor

@jmanuelrosa see af95206

@shawnlan yes, that's what i meant about resolving a container to be modified by the child state

@timothyswt I'm all ears about better way to approach this. Originally, I had a very similar opinion to yours and thought that the components shouldn't communicate beyond a ui-view boundary. However, my views have since changed. There's the line of thought that changed my view:

I am writing an app that is component driven. Data is passed down from parent to the (dumb component) child and events are fired by the child and handled by the parent component. Now I want to make the child component routable. Why then should I be forced to adopt a completely different paradigm simply because it's routed?

The child component is still a dumb component. It still receives input via input bindings and emits events via output bindings. This change allows the parent component (the smart component) to handle events from the child component as before, without demanding any shift in my component based approach.

It seems to me that the route declaration should be solely responsible for providing models and events to the component being rendered.

I used to have the same opinion. However, I've since broadened my views to accommodate developers who prefer the smart component to handle those concerns. In many cases, the STATE itself (as opposed to component) may indeed be able to "act as the smart component" by providing the proper callbacks (as we've discussed in #3111 (comment)) doing logic like saving data, then reloading states. Sometimes, though, manipulating the state machine might not be sufficient. You might need to access some component state (in the parent/smart component) to determine the right behavior.


When I look at this, something smells wrong with being able to basically inject parameters from the ui-view usage down to components.

Can you expand on this concern? I'd like to hear more arguments.

It seems to me that the route declaration should be solely responsible for providing models and events to the component being rendered.

Of course, that's still your prerogative. As I'm sure you're aware, UI-Router tries to be flexible, and as part of that flexibility, I'm trying to accommodate the component model folks who want to make their component tree routable.


I'm closing this issue because the original reported scenario is now in rc.1. I'd like to continue the discussion, however, so feel free to tell me why I'm wrong 😉

@christopherthielen christopherthielen added this to the 1.0.0-beta.4 milestone Jan 11, 2017
@jrcollins4
Copy link

Since this has been added recently, where can I find official documentation or examples on how to use this feature in rc1? Thanks!

@christopherthielen
Copy link
Contributor

where can I find official documentation or examples on how to use this feature in rc1?

https://ui-router.github.io/guide/ng1/route-to-component#routed-parentchild-component-communication

@jrcollins4
Copy link

Thanks! :)

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

7 participants