jonhobbs
jonhobbs

Reputation: 27962

Angular UI router - child of abstract state not inheriting data in $stateChangeStart

According to the top answer to this question...

Acess parameters of parent state from child state in stateChangeStart

When you inject toState into $stateChageStart it's supposed to inherit/merge data from parent states.

Unfortunately this isn't working in my app where the parent is an abstract state. Here are my routes

// Settings
    .state('wfSettings', {
        url: '/settings',
        abstract: true,
        template: '<ui-view/>',
        data:{
            customData1:  "Hello",
            customData2:  "World!"
        }
    })


    .state('wfSettings.siteSettings', {
        url: "/site-settings",
        templateUrl: "/templates/siteSettings.tpl.html",
        controller: "SiteSettingsCtrl as siteSettings",
        data:{
            customData2:  "UI-Router!"
        }
    })

When I console.log(toState) in my $atateChangeStart function and visit the child page it outputs this.

enter image description here

So you can see that the data from the parent isn't there.

The only strange things is that in the screenshot above you can see that Chrome dev tools has put "V" next to data, instead of the usual "Object" which appears there if I don't put any data on the parent state. Very strange behavior.

Edit:

I've made some progress. There seems to be a difference between version 0.2.11 and 0.2.18 of ui-router. This screenshot shows two lines of output. The first one is the data from the child route in $stateChageStart as calculated by version 0.2.11.

When I switch the library to 0.2.18 it outputs the second line instead!

Edit 2:

I've tracked it down to this change (which is not classed as a breaking change in the changelog)

https://github.com/angular-ui/ui-router/commit/c4fec8c7998113902af4152d716c42dada6eb465

So, I know what has cause it, but I still don't know how to fix this. Presumably I need to somehow go up the prototype chain and merge these values manually? That's a bit beyond my knowledge of javascript prototypical inheritance though.

Upvotes: 1

Views: 298

Answers (1)

Motin
Motin

Reputation: 5053

This is due to a breaking change in 0.2.16. The properties are still accessible by child states, but needs to be accessed directly.

In 0.2.15 and prior, properties from the parent state’s data object were copied to the child state’s data object when the state was registered. If the parent state’s data object changed at runtime, the children didn’t relect the change.

In 0.2.16+, we switched to prototypal inheritance. Changes to the parent data object are reflected in the child.

Source: https://ui-router.github.io/guide/ng1/migrate-to-1_0#prototypal-inherited-data

What is not mentioned however, is that even though the previous example still works (customData1 is available in childe states), the inherited properties won't appear in if you enumerate over $state.current.data.property - for instance the "json" angular filter will not display the inherited properties. Check out this plunkr to see these two effects: https://plnkr.co/edit/8ufkoluh1z6qmZT7VySd?p=preview

Source: Experiments resulting in the above plunkr

If you want the old behavior back, you can achieve this as well:

The ship has sailed on the decision between copying vs prototypal inheritance. Switching to deep copy would be a breaking change for many apps which expect a child state's data property to clobber a parent state's data property.

If you want non-standard (deep copy) behavior you can certainly implement it for your app:

 $stateProvider.decorator('data', function(state, parentDecoratorFn){
     var parentData = state.parent && state.parent.data || {};
     var selfData = state.self.data || {};
     return state.self.data = angular.merge({}, parentData, selfData);
 });

example: http://plnkr.co/edit/Wm9p13QRkxCjRKOwVvbQ?p=preview

Source: https://github.com/angular-ui/ui-router/issues/3045#issuecomment-249991317

Upvotes: 1

Related Questions