4

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.

enter image description here

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.

enter image description here


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.

enter image description here

enter image description here

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:

enter image description here


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

enter image description here

Leon Gaban
  • 36,509
  • 115
  • 332
  • 529
  • http://stackoverflow.com/questions/24575491/angular-ui-router-passing-data-beetween-states-with-go-function-does-not-work , Please check this link, it should solve your problem. Basically you need to resolve your params. – Pramod_Para Mar 20 '17 at 19:53
  • @Pramod_Para I'm not sure that is it, for one I have my params named. Also, in my plnkr even though I go to state `dashboard` on the tag button click, the state never inits, nor does the component again. – Leon Gaban Mar 20 '17 at 20:03
  • 1
    Is it okay, if I provide you with a different example which will guide you on how to pass params from parent to sibling states.? – Pramod_Para Mar 20 '17 at 20:07
  • That would be awesome, also what I was trying to do is make sure that only the sibling states I want to update refresh, not the ones that I don't want to update. IE: the `feed` in my example. ticker -> updates tag -> updates social, feed stays un-touched and does not re-init. – Leon Gaban Mar 20 '17 at 20:20
  • @Pramod_Para btw I re-created a cleaner version of this app here if that can help! https://plnkr.co/edit/21WarkWHVbS6YkI0w88W?p=preview – Leon Gaban Mar 20 '17 at 20:54
  • please check this plunker http://plnkr.co/edit/8M1zXN0W5ybiB8KyxvqW?p=preview, An alternative you could come up with is to use an angular service, and use the singleton across multiple states. – Pramod_Para Mar 20 '17 at 21:02
  • Thanks, but that plnkr isn't using any state params or navigating with those params. I've forked it and adding that stuff in now. Also yeah my original app I used services/factories and the URL to manage the state, but keep running into so many bugs. With ui-router I know exactly what the state of the ticker, tags etc should be. Just trying to prevent all components from refreshing... – Leon Gaban Mar 20 '17 at 21:08
  • I have gone through your code, felt you have come a long way to completion, so I have suggested using state params. But it can be made even simpler, so I have sent a different example :) – Pramod_Para Mar 20 '17 at 21:12
  • @Pramod_Para here check this out, you can see what I'm trying to do here :) http://plnkr.co/edit/UOOtHiTnOARdBBZoFPsl?p=preview – Leon Gaban Mar 20 '17 at 21:16
  • 1
    Sorry, it's been a long day for me, and its 3 AM in the morning will update your plunkr for sure today. – Pramod_Para Mar 20 '17 at 21:19
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/138570/discussion-between-leon-gaban-and-pramod-para). – Leon Gaban Mar 20 '17 at 21:20

3 Answers3

2

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
  })

})
maurycy
  • 8,455
  • 1
  • 27
  • 44
  • Hey Maury, I see you are passing the ticker, but how do you pass the tag from the tags.component in? Another thing, I do not see the controller logs for the social or view components in your plnkr. – Leon Gaban Mar 21 '17 at 14:02
  • `$state.go('dashboard', { tag: tag });` This is the action that needs to update the tag `$stateParams` in `social` and `view`. Currently that `tag` object isn't getting there. Any ieas? – Leon Gaban Mar 21 '17 at 14:28
  • 1
    Sure, give me few minutes, just got back from lunch ;) – maurycy Mar 21 '17 at 14:34
  • No probs! :D I'm still hacking away at this myself. Created 2 new plnkrs https://plnkr.co/edit/P0DIEDxLU4Wlm9L0bcTi?p=preview and a cleaner one here: https://plnkr.co/edit/2h6fV5yTjeUqLP3SvbvO?p=preview Also see the dashboard.html. I think the problem is that when I go to state `dashboard` it replaces the tags component in the ui-view with the entire dashboard again for a split sec. You have to set a break point in the social.component controller to see. – Leon Gaban Mar 21 '17 at 14:36
  • Yes, the configuration of router makes it flicker, the root problems is in ticker component where you have $state.go in the controller initialisation – maurycy Mar 21 '17 at 15:11
  • Mind a look at this new question about states and loading more states? http://stackoverflow.com/questions/42956873/how-to-load-a-state-view-into-a-nested-view-of-a-top-level-state – Leon Gaban Mar 22 '17 at 16:21
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: enter image description here

Community
  • 1
  • 1
clever_bassi
  • 2,392
  • 2
  • 24
  • 43
  • Heya, I still can't get the tag from the tags component into the view or social, how do you do that? https://plnkr.co/edit/P0DIEDxLU4Wlm9L0bcTi?p=preview I am not trying to pass the ticker through. And I don't see the tag in the $stateParams, even after making sure it's a param in all the states. – Leon Gaban Mar 21 '17 at 14:15
  • Ok I see that the tag object is getting passed into the socialMedia component. The `$scope.tagObj` gets set. But then it is lost... again when I click on the tag button. And pause the debugger I can see the Dashboard being loaded twice. I think this is my issue, any way around that? – Leon Gaban Mar 21 '17 at 14:32
  • 1
    I'm looking at it. – clever_bassi Mar 21 '17 at 15:22
  • Ok yeah the click function works now, but see at the start the tags component is missing. Also clicking a tag, now we can see the dashboard.html being reloaded inside of the dashboard.html. Sorry I think this app is way too complicated now. Check out this plnkr, its simplified, I'm not using ui-view to load tags, no needless state configs. If we can get this working, this will also close this question out: https://plnkr.co/edit/2h6fV5yTjeUqLP3SvbvO?p=preview – Leon Gaban Mar 21 '17 at 16:00
  • This has a 'cannot read property term of undefined' issue that I resolved earlier. Please fix that and update the plunker before I can try to resolve this issue. – clever_bassi Mar 21 '17 at 16:13
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/138657/discussion-between-clever-bassi-and-leon-gaban). – clever_bassi Mar 21 '17 at 16:32
  • Btw I want to check your answer, but I can't if it's just the Ticker getting into the View and Social. The Tag needs to be sent into those components. – Leon Gaban Mar 21 '17 at 19:11
  • It was doing that, guess I updated it and messed it up. Let me fix that. – clever_bassi Mar 21 '17 at 19:16
  • Check this out. https://plnkr.co/edit/JYlhtkWmsTGHuJWvnydw?p=preview See browser console, both ticker and tag component are being passed to both view and social component. – clever_bassi Mar 21 '17 at 19:18
  • Mind a look at this new question about states and loading more states? http://stackoverflow.com/questions/42956873/how-to-load-a-state-view-into-a-nested-view-of-a-top-level-state – Leon Gaban Mar 22 '17 at 16:21
  • It did not actually, because when I selected a Tag, it broke the app. It loads dashboard.html in place of the tags module. So I selected Maury's. I did give +1 for the effort though. – Leon Gaban Mar 22 '17 at 19:32
0

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.

enter image description here

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 });
}
Leon Gaban
  • 36,509
  • 115
  • 332
  • 529