Help - Showing dynamic data when user selects category!

Hey all, I have been taking numerous angularJS tutorials, but am still a newbie, so any guidance here would be greatly appreciated. I am creating a simple budget app, and am trying to allow a user to see category data (Recreation) when that user selects the Recreation tab. Right now, it’s all static. Unfortunately, I can have the URL point to the right ID, but none of the information shows (ie. selectedCategory.categoryData.budget goes nowhere)

I have created this plunker although it’s not perfect: https://plnkr.co/edit/XXQmFGk8JwiNSJLN6jnB?p=preview

Would love your guidance. What am I doing wrong? How do I dynamically show category data to the user?

Thanks for your help!!!
Ben

Correct me if I’m wrong, but from what I can gather from looking at your plunker, you’re trying to have a row that consists of Category | Budget | Actual | Remaining, and you want to be taken to a category screen when you click on the Category element?

If that’s so, then I wouldn’t have Category be a <a href> but rather just give the element an ng-click function.
For example, if you put <div class="whatever" ng-click="goToCategory(x.category)"> then when the div is tapped, it will call the function goToCategory with the parameter being the string of the selected category.
Then in your controller for the page with the row on, you have to define $scope.goToCategory = function(categoryString){go to category specified by string here};

Let me know if that makes sense/is what you mean and need any help!

1 Like

Hey Sammy,

Yes! That is actually what I would rather have! Wow, that’s awesome that you pulled that from my attempt.

So I added this ng-click=“goToCategory(x.category)”.

In the controllers.js (I am rather new, so I apologize), I put this

scope.goToCategory = function(){
$state.go(‘app.category’);
};

And my app.js looks like this for this state

.state(‘app.category’, {
url: ‘/category/:categoryId’,
views: {
‘menuContent’: {
templateUrl: ‘templates/category.html’,
controller: ‘CategoriesCtrl’
}
}
})

Is this somewhat what I should be doing? It didn’t work, so I’d be interested what I did wrong.

Thank you again, Sammy!

Try something like this:

<a ui-sref="app.category({categoriesId: x.id})">{{x.category}}</a>

That looks generally like the right idea, just a few things that I can see are wrong with it. Make sure you have $scope instead of scope is the first one. $scope will always be the scope of the controller you are currently in, so defines “global” functions and variables for each page - an important part of Ionic.

Secondly, in the ng-click you are passing the parameter x.category into the function. Now, assuming you have built the API yourself (meaning you specifiy what the “category” value of each object in your categoryData array is i.e. could be just a string with the category name, could be an integer ID, could be anything you like), you just have to make sure the $scope.goToCategory function handles it properly:
In your plunker, the “category” keys all have values that are strings (‘Recreation’ for example.) This is what is being passed into your goToCategory function. So you function should firstly have the parameter defined (should look like $scope.goToCategory = function(categoryString){ do stuff };)

Now firstly, you have to decide whether you want a separate screen for each category (lots of duplicate code, but probably more simple to start out with), or a single page that can load different data depending on which category is selected. I’m gong to try and run through how to make the single page now since it’s the more elegant solution.
Let me know what you want the category screen to look like and I’ll see if I can edit this to make it more sensible to your usecase.

In the controller for the screen where you select the category element (from now on, I’ll refer to this as mainCtrl), you’ll want the goToCategory function defined:

$scope.goToCategory = function(categoryString){ <- categoryString is the parameter being passed in
    categoriesService.categoryString = categoryString; <- we will define this service later
    $state.go('app.category')  <- switch to category screen
}

The categoriesService is a service which I’ll define now. Services can handle variables across scopes, so they’re great for passing data between views (such as the desired category to the ‘Category’ screen) Make sure to inject the service into your mainCtrl like so:

.controller('mainCtrl', function($scope, $state, categoriesService) { do stuff }

Then this is your service in services.js:

angular.module('app.services', [])

.service('categoriesService', [function(){
    this.categoryString; <- this stores the selected category
    this.categoriesList = [ <- this stores array of categories, budgets etc.
                              {category: 'Recreation', budget: "" etc...}
                              { etc... }
                          ];
}])

I would also consider storing your categoryData in this service, so that it can be accessed from anywhere, such as I have done above. Then to get this data into your mainCtrl, simply do $scope.selectedCategory = categoriesService.categoriesList

So, now you can access the list of categories from anywhere, and also send parameters to your Category view. In CategoriesCtrl, you can now get this variable by doing $scope.categoryString = categoriesService.categoryString (you also have to inject the service into this controller), which will set $scope.categoryString to the string value you defined in your array - such as ‘Recreation’.

You should then be able to do what you want with that information, change the title etc. To get the other information that is relevant to the category (e.g. the value of budget for the category ‘Recreation’), you will have to search through your array to find the index of the object where the category key is equal to whichever string you’ve passed in. This can be done by iterating over the array as so:

$scope.selectedCategory = categoriesService.categoriesList <- Get array
$scope.categoryString = categoriesService.categoryString <- Get string of selected category

var arrayItem = $scope.selectedCategory.find(function(item, i){
    if(item.category === $scope.categoryString){ <- finds array item where category = categoryString
        return item <- returns the entire item
    };
});

Using this and your example, if you select the category ‘Recreation’, the object that is returned (arrayItem) will be:
{category: 'Recreation', budget: '$400', actual: '$200', remaining: '$100', id: 4 } so you can then use all the data.

However, if your selectedCategory list gets huge, this iteration could be very slow, so I’d consider refactoring your selectedCategory array to make it easy to index via the categoryString. Let me know if this makes sense or if you need anything else!

EDIT: Here’s a plunker with code that should work in an Ionic environment if you copy all the main parts. Doesn’t work on Plunker as-is because can’t XS load angular and UI-Router

1 Like

WOW! I cannot thank you enough, Sammy. This is so informative that I wanted to read it a couple times. I’ll plan to study it some more as well.

You’re right that I’d much rather have a single page that loads different data depending on the data selected, so thank you for walking me through the following used case.

The button now works! Woot! Okay, so I did have a minor question - A couple days ago, I made a categories.json file and essentially pulled the category information for the category page from the json file instead of adding the data to a categoryList within a Service.

Is that an okay solution? If so, Is it possible to have the ng-click navigate to the selected category page (i.e. recreation) pulling the categories.json file? I added the json file to the plunker: https://plnkr.co/edit/wQDHTWAZEGCpsvISqB00?p=info.

Thank you again for your help, Sammy!

Best wishes,
Ben

Hi Ben,

No problem at all! Apologies for the length, I get carried away haha

Glad the button is working as planned! Storing the json as a file makes more sense if you want it to persist on the device and be updated!

Reading the json from a file is simple using the Cordova File plugin to read the file as text, then parsing the result using native JS.
Only thing about that is being careful where you store the file. The API for Cordova File lists all the directories you can read/write to, but the file you package with the app can’t be read by $cordovaFile (it can be read using other methods, but not updated…). I have got around this in the past by either pulling the data from an HTTP request on launch and saving it to a file using $cordovaFile, or putting a base object in your controller that gets written to a file using $cordovaFile on the first launch of your app. (you can use $cordovaFile.checkFile to see if it already exists, and if not then create the file.) and then just updating that file whenever you receive new data (assuming you’ll be getting data from an API?)

So to get the object from the file, where before you had the call to your service, simply call to:

$cordovaFile.readAsText( <<file directory>> , "categories.json")
    .then(function(success){ //<- ".then" because file is read asynchronously
        $scope.selectedCategory = JSON.parse(success);
    });

I recommend just having a play around with various methods now you have the basic parts set up! But do let me know if you run into any more issues!

Best of luck with the project,
Sam