问题
Plnkr: https://plnkr.co/edit/Brmcxd2LjGVJ6DXwuJtF?p=preview
Architecture:
$login
-> $container
(contains $dashboard
and $feed
), dashboard
contains: $tickers
, $tags
, $viewHeader
and $socialMedia
components.
Goal:
tags
needs to communicate and send a tag object into the viewHeader
and socialMedia
states.
Expected:
After Login, selecting a button in the Tags list should send that tag
into the ViewHeader and SocialMedia states/components.
Results:
After Login, selecting a button in the Tags and going to $state dashboard
the $states for the ViewHeader and SocialMedia refresh but the view model variable {{ term }}
does not update with the appropriate tag in either component.
Design explanation:
The problem I'm trying to solve with a mix of states, components, sibling components and views is that every component in the app always refreshes/re-onInit when any state changes. $state.go('dashboard'...
This is not ideal, what I want is to make sure that only the components that need to refresh, refresh.
IE: When I select a Ticker or a Tag I do NOT want the Feed component to refresh every single time. However the user should be able to take an action in the Feed component and it will update all the others.
This is why I created a container
state to hold both the dashboard
and feed
states. Originally I had everything in the dashboard
state, and anytime $state.go('dashboard'
happened the feed
component would also refresh. If there is a better way to solve this issue lmk! :)
PS: Started learning about state management using ui-router to remove the reliance on $rootScope. So far my real app is much more stable, however has the annoying feature that every component refreshes/re-onInits.
In this screenshot below I can clearly see that the correct tag
is being passed into the correct $states, but yet {{ term }}
does not update.
Code snippets (Full code in Plnkr)
app.container.html (container state)
<div>
<dashboard-module></dashboard-module>
<feed-module></feed-module>
<!--<div ui-view="dashboard"></div>-->
<!--<div ui-view="feed"></div>-->
</div>
dashboard.html (dashboard state)
<div class="jumbotron text-center">
<h1>The Dashboard</h1>
</div>
<div class="row">
<tickers-module></tickers-module>
<!-- Tags component goes here -->
<div ui-view></div>
<view-module></view-module>
<social-module></social-module>
</div>
^ The reason I'm using a <div ui-view>
above to load the tags state is that so far has been the only way I could initialize the tags module and show the tags list from the tickers component.
tickers component controller:
tickers.component('tickersModule', {
templateUrl: 'tickers-module-template.html',
controller: function($scope, $state) {
console.log('Tickers component', $state.params);
$scope.tickers = [
{ id: 1, ticker: 'AAPL' },
{ id: 2, ticker: 'GOOG' },
{ id: 3, ticker: 'TWTR' }
];
$state.go('tags', { ticker: $scope.tickers[0] }); // <---
$scope.clickTicker = function(ticker) {
$state.go('tags', { ticker: ticker });
}
}
^ That <--- above allows the tags state to load the tags component element and wire up the tags list, based on the state of ticker
var tags = angular.module('tags', ['ui.router'])
tags.config(function($stateProvider) {
const tags = {
parent: 'container',
name: 'tags',
url: '/tags',
params: {
ticker: {}
},
template: '<tags-module></tags-module>',
controller: function($scope, $state) {
console.log('Tags state', $state.params);
}
}
$stateProvider.state(tags);
})
tags.component('tagsModule', {
templateUrl: 'tags-module-template.html',
controller: function($scope, $state) {
console.log('Tags component', $state.params);
const tags_model = [
{
ticker: 'AAPL',
tags : [{ id: 1, term: 'iPhone 7' }, { id: 2, term: 'iPhone 8' }, { id: 3, term: 'Tim Cook' }]
},
{
ticker: 'GOOG',
tags : [{ id: 4, term: 'Pixel' }, { id: 5, term: 'Pixel XL' }, { id: 6, term: 'Chrome Book' }]
},
{
ticker: 'TWTR',
tags : [{ id: 7, term: 'tweet' }, { id: 8, term: 'retweet' }, { id: 9, term: 'moments' }]
}
];
function matchTags(ticker, model) {
return model.filter(function(obj){
if (obj.ticker === ticker) { return obj; }
});
}
$state.params.ticker = $state.params.ticker || {};
$scope.tags_model = matchTags($state.params.ticker.ticker, tags_model)[0];
$scope.clickTag = function(tag) {
console.log(' Tag clicked', $state);
$state.go('dashboard', { tag: tag });
}
}
});
The click function in the Tags list:
$scope.clickTag = function(tag) {
console.log(' Tag clicked', $state);
$state.go('dashboard', { tag: tag });
}
socialMedia controller:
social.component('socialModule', {
templateUrl: 'social-module-template.html',
controller: function($scope, $state) {
console.log('Social component', $state.params);
$scope.term = $state.params.tag.term;
}
});
viewHeader controller:
view.component('viewModule', {
templateUrl: 'view-module-template.html',
controller: function($scope, $state) {
console.log('View component', $state.params);
$scope.term = $state.params.tag.term;
}
});
Strange doubling up of components
Just noticed this, after clicking on a tag and breaking inside of the social.component. It looks like the dashboard component is being re-rendering in place of the tags component for a split second:
Updated, solved the issue here by adding { refresh: true }
to $state.go in tags. Note however this still did not solve my original mission, which was preventing the feed.component from refreshing. So will end up using a service.
https://plnkr.co/edit/R0iwJznOgEwOL7AtU5wP?p=preview
回答1:
I've updated your plunker
https://plnkr.co/edit/ywyH7wOmR6loNiAkH9Yb?p=preview
The solution is fairly simple. Instead of using $state
use $stateParams
in the dependency injection which is a singleton so if you reference it like I did in the updated plunker:
$scope.params = $stateParams
then the display value
{{params.ticker.ticker}}
will be 2 way bound to the $stateParams and update accordingly every time you change state
---edit---
I've added another solution through service https://plnkr.co/edit/oQNAHv7tAS8LR9njOUAX?p=preview
.service('awesomeTag', function($rootScope){
var self = this;
$rootScope.$on('$stateChangeSuccess', function(_event, _toState, toParams){
self.tag = toParams.ticker.ticker
})
})
回答2:
I updated your plunker here. The issue in your code was here:
view.component('viewModule', {
templateUrl: 'view-module-template.html',
controller: function($scope, $state) {
console.log('View component', $state.params);
$scope.term = $state.params.tag.term; //culprit line
}
});
What you need to instead do is use $stateParams to retrieve parameters. I changed the code in viewModule as follows:
controller: function($scope, $stateParams) {
console.log('View component', $stateParams);
$scope.tickerObj = $stateParams;
}
And then accordingly updated the view-module-template.html
to use the tickerObj as follows:
{{ tickerObj.ticker.ticker }}
Same thing for social-module as well.
A simple example to understand how this works is as follows:
$stateProvider.state('stateName', {
url: '/stateName',
controller: 'myCtrl',
params: { //this can also be used to give default values for route params
obj: null
}
});
function myCtrl($stateParams) {
console.log($stateParams); //will display the obj parameter passed while transitioning to this state
}
$state.go('stateName', {obj:yourObj}); //passing obj as param to stateName state, this can be retrieved using $stateParams.
This is similar to the issue happening in your last question.
I noticed another issue with your code. On clicking any of the tags like retweet,moments,tweet, it changes the ticker to "AAPL" again. Is this intended behavior? (If not, this is happening because when you transition to dashboard state after clicking on a ticker, all modules are being re-initialized. The line causing this is
$state.go('tags', { ticker: $scope.tickers[0] });
Hope this helps!
Update:
I updated the plunker here. I commented out this line:
$state.go('tags', { ticker: $scope.tickers[0] });
which was by default setting ticker to apple each time we were coming to dashboard state. I am also passing ticker and tag object now to both view and social state. If you look at the console, you're able to get the correct values to tag and tickers to both those states. Let me know if this works for you.
You can verify that the tag and ticker values are being passed into the view and social components by looking at the console logs. Here is a snippet:
回答3:
Posting this because this is the correct way to resolve this issue, and properly architect the app:
https://plnkr.co/edit/MztpsHj9qoRCFUDrREH7?p=preview
Here I'm correctly using child-states and named views.
container-template.html
<div>
<div class="fl w100">
<em>Container component module scope</em>
</div>
<div ui-view="dashboard"></div>
<div ui-view="feed" class="fl"></div>
</div>
The tags state
const tags = {
name: 'container.dashboard.tickers.tags',
url: '/tags',
params: {
ticker: {},
tag: {}
},
views: {
'tags' : {
templateUrl: 'tags-list.html',
controller: function($scope, $state) {
console.log('tags-list controller', $state)
$scope.ticker = $state.params.ticker;
And state navigation to child.child states:
$scope.clickTicker = function(ticker) {
$state.go('container.dashboard.tickers.tags', { ticker: ticker });
}
来源:https://stackoverflow.com/questions/42865321/how-to-pass-state-variable-into-2-other-sibling-states