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

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.
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.

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

Related

Binding data into view - Is it better to pass data down from parent views to child/grandchild views or initialize/query data whenever creating views?

I am working on a Backbone/Marionette project. This project implements a way to cache data on local memory after loading them from server. Therefore data can be access anytime, anywhere within the project.
This makes me wonder what is the better way to populate data to view in my case:
const ChildView = marionette.View.extend({/*...*/});
const ParentView = marionette.View.extend({
// ...
onRender() {
// 1: pass data to child view from parent view
const childView = new ChildView({
data: this.options.data,
}));
// 2: initialize data when creating new child view
const childView = new ChildView({
data: SomeModel.new({/* some properties */}),
}));
},
// ...
});
new ParentView({
data: SomeModel.new({/* some properties */}),
}).render();
Both methods work correctly. However, the project view structure is pretty deep and complicated so I prefer the second way because with the first one I would need to go up and down a lot to check what data is and where it comes from.
Do you think if there are any possible problems with this method?
I prefer the 1st way, passing data from parent to child, but it depends on what your views are doing.
For me, a big advantage of sharing a data object is that updating it within one view updates it in all other views (this will work if you pass an existing backbone Model, or any object as data). This can save a lot of work... when a user updates their background color (for example), you can update it once in your BackgroundColorChoose view, and know that it is already updated everywhere else that data is in use.
In a sense, it doesn't matter where the data came from, only what it represents (because it can be accessed/modified from within any of your views).
I can imagine scenarios where this approach is not good, but I've found it makes a good baseline to start from (and avoids the need to trust browser-caching)

Optimal way of accessing parent view property (Ionic 2, Angular 2)

I'm testing Ionic 2 and Angular 2, and I've got a doubt about accessing to parent view's properties.
Per example, I've got a test app in which my view is a list of items, and when I click one item, I enter to their details. Pretty straightforward, huh? Well, that details view has got functions that edit the element, and then apply the changes.
For this, I use three different ways:
One is to pass the object reference and just edit it, which edits it back in the list (I guess this is pretty optimal)
Before the typical navCtrl.pop(), pass a parameter via navParam to the function "ionViewDidEnter()", which executes just when you come back to a view, and filter it there, so you can perform the task you desire. Problem: it doesn't work (probably it's a bug).
Here comes the krakken: when removing the element, this won't work, since I have to remove it from the list, per example, with the typical list.splice(index, 1);
I found two different methods of performing this: you can either pass the new view a reference of the list, or you can access it from the NavController, just as I do here:
remove(){
let list = this.navCtrl._views[0].instance.list;
for(var i=0;i<list.length;i++){
if(list[i].id === this.contact.id){
list.splice(i,1);
}
}
this.navCtrl.pop();
}
Here I have another example of this weird technique, reusing the edit view for creating a new element:
editContact(obj){
if(this.onEdit){
this.onEdit = false;
this.editBtnTxt = "Edit contact";
if(this.onCreate){
this.navCtrl._views[0].instance.list.push(this.contact);
this.navCtrl.pop();
}
}else{
this.editBtnTxt = 'Apply changes';
this.onEdit = true;
}
}
Although this works pretty nicely and isn't throwing any errors, I guess I'm just being somewhat lucky, because: how do you know the index of the view you want to access, if you're not in a simple test project like this with two views, per example? I guess there can be a lot of errors with this way of doing things.
But as it works, and it seems to be more optimal than passing tons of parameters, or using localStorage as a "global" variable, I'm sticking with this by the moment.
What I would like to know, is... which way is the most optimal of accessing parent view properties?
You should try to avoid accessing the parent view.
Use #Output()s in the child and (someEvent) bindings in the parent and notify the parent about the actions it should take on the model.
If they are not direct parent child (like when the child is added by the router) use shared services with observables instead.

Is it possible to change where AngularJS looks for variables?

Now, I know this is an off-the-wall question and that there is probably not going to be any API level access for doing this. I also understand that doing this is completely unnecessary. However, the implementation that I am aiming for requires for me to be able to make {{ variable }} look inside of the $scope.object instead of the $scope itself.
For example:
Controller($scope) {
$scope.active = ...;
}
In this case you can get the active object through {{active}} and any child elements through {{active.broken}} however for the sake of humoring me, lets assume that all of the variables I'm ever going to have to obtain is going to be part of that active object. So I'll be typing things like.. (Data not related)
{{active.title}}
{{active.author}}
{{active.content}}
You could just say "Well why not just move the title/author/content into the $scope and keep it outside of the active object, as that would achieve the desired result of this:
{{title}}
{{author}}
{{content}}
Well, that's where the problem comes in. This is a controller that is not exposed to my end-user, however the end-user does have a completely mutable object (in this example: active) that they can modify. This object has many [optional] listeners and callbacks that are invoked by the application controller when necessary.
The user's that have used my application during testing have commented on what a drag it is to have to type in active. before everything in order to get the data they wanted. Considering data from the $scope is never rendered to the screen, and only data from active is, I was wondering if perhaps there was a way to change where AngularJS looks when parsing/binding data.
If the goal is to evaluate expressions in a different context than $scope, that can be done with the $parse service.
app.controller("myVm", function($scope, $parse) {
var vm = $scope;
vm.active = { a: 5,
b: 3
};
var.myFn = $parse("a+b");
vm.total = myFn(vm.active);
console.log(vm.total);
});
The above example shows how to evaluate the expression a+b using the properties of the $scope.active object.
The DEMO on JSFiddle

How to properly notify data changes between siblings in polymer

I would like to have a polymer element with two sub-elements, one that produces data, and the other that performs some action when the data changes (in my case: sending a notification to a server).
To implement this, I wrote a polymer element, namely root, with the following structure (names changed to simplify the discussion):
<producer data={{foo.bar}}></producer>
<consumer data=[[foo]]></consumer>
The producer changes the data using the set('property', 'value') method, so that the root element sees the notifications. The problem is that the consumer element won't notice the changes to foo since they involve a sub-property.
To solve this, I tried using a computed binding as follows:
<producer data={{foo.bar}}></producer>
<consumer data=[[_compute(foo)]]></consumer>
...
_compute: function() {
return this.foo;
}
However this won't cause the consumer to be notified. I think the reason for this is that the returned object is the same reference (only a sub-attribute changed). Currently the workaround I've used is to use the following version of the compute function:
_compute: function() {
return Object.assign({}, this.foo);
}
This works (the consumer element gets notified), however I'm affraid it might not be the most efficient (I'm creating an object at every call of _compute) and/or elegant way. Then my question is: what is the proper way to achieve this behavior in Polymer?
Do you have access to modify the consumer element?
The best way to fix this is to have the consumer element have a multi-property observer that listens for sub-property changes on the data property.
It might look something like this:
Polymer({
is: 'consumer',
properties: {
data: Object
},
observers: ['consumeData(data, data.*)'],
consumeData: function (data) {
//Do whatever you were planning on doing with data here
}
});
The advantage of an approach like this is that your 'consumer' element just 'knows' how to consume the data object when a sub-property on it changes. Because of the lighter weight approach to data binding in Polymer, trying to implement this behavior outside of the 'consumer' element will necessarily be more expensive and more complicated, since it requires either tricking the data binding into thinking the data object is new by supplying it with a new reference to a copy or forgoing the data binding altogether and building an approach on top of events and calling methods on the consumer in response to events. So if at all possible, I would recommend trying the approach above.
Polymer's data binding does not work the same way as some other two-way enabled data binding implementations, like what you might find in AngularJS. Rather than using dirty-checking, which is extremely expensive, Polymer uses an event based 'path notification' approach. When a sub-property on a property changes, a Polymer element which has that property will fire an event to it's immediate children bound to that property, notifying them that the path 'property.subProperty' has changed. In order for consumer to act on those changes, it has to be told to listen to changes along that 'property.subProperty' path. We specify paths in our polymer observers by using the syntax above. In this case, putting data.* in our observer means we want to listen to any path off of data, so that any notified property change on the data property will trigger the observer.
As you have noticed there isn't an elegant way of doing this. The way you got it working is interesting.
An alternative way which I would expect to work would be to fire an event from within the producer element.
this.fire('data', {data: this.foo.bar});
and then have the parent/root element listen for this event and then update the data property of the consumer element.
<producer on-data="handleData"></producer>
<consumer id="consumer"></consumer>
handleData: function(e) {
self.$.consumer.data = e.detail.data;
}
Edit:
You could make a new property that you compute within the producer element. Then you won't have to do a computed function everytime you want to access foo.bar
Producer element
properties: {
foo: {},
bar: {
computed: 'computeBar(foo)'
}
}
Root element:
<produce bar="{{bar}}"></producer>
<consumer data="[[bar]]"></consumer>

How to manage ui-router or share $scope in different $state

I have an AngularJS site, the object-resource I want to show is:
each user has a basic account, that will show in a single page (named basic-page);
user has several sub-account, each sub-account will show in a diffent page (named app-page);
basic-page will show the summer info about the sub-account, so app-page can share the loaded $http data of basic-page is better for code reusing.
As the purpose, I use ui-router define state below:
.state('user', {
url: '/user/{id}',
title: 'User-Page',
templateUrl: helper.basepath('user.html')
})
.state('user.app', {
url: '/{app}',
title: 'App-Page',
emplateUrl: helper.basepath('app.html')
})
Notice that state user.app is the child of user.
What I want is when I enter the user.app, it can reuse the data in user, ecen if it's a different page, that the user need not to contain a ui-view to include user.app's template.
But actually I enter user.app, and it doesn't show the app.html(because I didn't include ui-view in user.html).
Maybe this is not the correct usage of ui-router.
So, how can I share data in different $state? Anyone can give me a detailed example? Thank you.
Sharing data across controllers
Any time you need to share data across states you will need to create a service/factory that you can instantiate in your controllers associated with those states.
The factory will consist of basic getters and setter for the different data you need to share. Just like when you build getters and setters in java to share across classes.
Example Code
.factory('yourFactory', function ($scope) {
return {
get: function () {
return $scope.someValue;
},
set: function(value){
$scope.someValue = value;
}
};
})
Disclaimer: I've not tested this code but it should do the job for getting and setting some values you need to access across your app.
Demo : Working plunker with this approach.
Alternative: 1
This is the "Dirty" alternative, you can set a global variable with $rootScope. It will be accessible everywhere since its global, I strongly advise you don't do this but though I would point it out to you anyway.
Alternative: 2
When a state is "active"—all of its ancestor states are implicitly active as well.So you can build your states considering the parent-child relationship and share data across scopes in hierarchical manner.
Official Docs and working plunker with mentioned approach.

Categories

Resources