AngularJS - how can my components pass info to each other? - javascript

How can I get a value taken by one of my components to be passed to another component in the same module?
I'll try to simplify the code I'm working with to focus entirely on the problem. So say we have two js files:
infoGetter.js
angular.module('info-system')
.component('info-getter', {
controller: function(){
this.info = ["info1", "info2", "info3", "info4"];
this.indexGot;
this.getIndex = function($index){
this.indexGot = $index;
}
},
template: `
<li class="info-list"
ng-click="$ctrl.getIndex($index)"
ng-repeat="info in $ctrl.info"
>
</li>
`
});
and
infoDisplayer.js
angular.module('info-system')
.component('info-displayer', {
controller: function(){
this.info = /* item at index indexGot from the info array in infoGetter.js */;
},
template: `
<pre>{{$ctrl.info}}</pre>`
});
So the intent is to display in the infoDisplayer "info[$index]" taken from the clicked list element in infoGetter and the info array from infoGetter. How do I make that work?
Well, I know there should probably be an easier way to do this, but I tried just putting the info array and $index from the on-click function into the global window object to access it there, but it doesn't seem to work; for one thing it's not updating on-click like I would expect, and for another it seems to kinda glitch out when the page first loads even when I make this.indexGot = 0 in the infoGetter.js controller; it seems like "this.indexGot" is undefined when the page loads (but not afterwards?), so it doesn't actually pull anything from the array.

Related

Ember: Getting model data in the controller

I am new to ember and I am building a DnD character sheet to learn and practice. Right now I am having a lot of trouble getting access to model data in a controller. After hours and hours of reading related posts it is just not clicking and I think I am misunderstanding what I am passing to the controller.
Basically what I am trying to do is grab data from a model so I can do calculations on them and then display the results of those calculations on the page.
Here is my transforms/router.js:
Router.map(function() {
this.route('characters', { path: '/'})
this.route('new', {path: 'new'});
this.route('view', {path: '/:character_id'});
});
So here I am trying to pull up the view page which has the URL of the character id. Next here is my route for the view page:
export default Route.extend({
character: function () {
return this.store.findRecord('character', id)
}
});
So this is finding the record of the character with the id I pass. My link looks like this and is coming from a component:
<h5 class="card-title">{{#link-to 'view' id}}{{name}}{{/link-to}}</h5>
Now I have my controller for the view page, which looks like this:
init(id){
let character = this.get('character')
}
When I try to log character, it is still undefined. When looking ember information in dev tools it seems the page is getting the information from the model after I refresh the page, but I just can't seem to be figure out how to grab that info in the controller itself to manipulate it.
I've been trying to figure this out for quite a while now, and its getting pretty frustrating. I currently have a work around where I do the calculations beforehand and just store all the calculated results in the model as well, but while I am learning I would like to understand how this works. Thanks is advance.
Edit: As pointed out in comments below I was missing let when defining character.
Your model hook seems wrong. You're using id but never define it. Probably what you want is more like this:
character(params) {
return this.store.findRecord('character', params.character_id);
}
Next your init hook makes no sense:
init(id){
let character = this.get('character')
}
First there is no id passed to the init hook. Second you're missing this._super(...arguments) which should always be called when you override init.
Last is that your controller is first created and later populated with the model. Also the model is populated as model property, not character.
So you could place this in your routes template and it will work:
This is character {{model.id}}
Or if you want to change something before you pass it to the template you should use a computed property in your controller:
foo: computed('model.id', function() {
return this.get('model.id') + ' is the id of the character';
}),
However for this code to run you need to use. The easiest way to use it is to put it into your template:
{{foo}}

Ionic, List populating via search

I am using a backemnd service (parse in this case but that doesn't really matter for this question) and wanted to simply search it. I have a textbox that upon text being entered searches the server and returns an array of matchs.
My next step is to simply display my returned objects nicely in a list. Easy enough with ng-repeat but because the view has already been loaded the UI won't update to reflect the array being loading into the list. Does that make sense?
I was wondering if there was a technique to Refresh the list and show the returned search elements, and hopefully I am not being to greedy here but doing it in a way that looks good and not clunky.
I did a lot of googling with NO luck :( any advice would be amazing.
Without any code provided it is hard to guess what is wrong. Angular has two-way binding, so view should be updated automatically after changing content of an array. If it's not, it means that you probably did something wrong in your code. I present an example code which should work in this case.
Controller
angular.module('moduleName')
.controller('ViewController', ['ViewService', ViewController]);
function ViewController(ViewService) {
var self = this;
self.arrayWithData = [];
self.searchText = "";
// ---- Public functions ----
self.searchData = searchData;
// Function which loads data from service
function searchData(searchText) {
ViewService.getData(searchText).then(function(dataResponse) {
// Clear the array with data
self.arrayWithData.splice(0);
// Fill it again with new data from response
angular.forEach(dataResponse, function(item) {
self.arrayWithData.push(item);
});
});
}
// --- Private functions ---
// Controller initialization
function _initialize() {
self.searchData(self.searchText);
}
_initialize();
}
View
<div ng-controller="ViewController as view">
<input type="text" ng-model="view.searchText" />
<input type="button" value="Search!" ng-click="view.searchData(view.searchText)" />
<!-- A simple ngRepeat -->
<div ng-repeat="item in view.arrayWithData">
<!-- Do what you want with the item -->
</div>
</div>
Conclusion
By using splice() and push() you make sure that reference to your array is not changed. If you are using controllerAs syntax (as in the example), assigning new data with '=' would probably work. However, if you are using $scope to store your data in controller, losing reference to the array is the most probable reason why your code doesn't work.

In Angular, how can I use ng-repeat to pass a variable to that triggers it repeat number?

In my app I do a database call to get a list of elements to display using ng-reat, this works fine. Inside that list is another list. That depends on another DB call.
I'd like to pass an id and repeat an element the number of results I get back and output their data. Something like this:
<div ng-repeat="library in libraries">
<p>Read our books:</p>
<p ng-repeat="book in books">{{book.title}}</p>
</div>
I thought about this:
<p ng-repeat="book in getBooks({library.id}) track by $index">{{book.title}}</p>
this use something like this to trigger the number of repeats and the values:
$scope.getBooks = function(id){
...run my database query using the id...
...update my scope to include the books...
....**magic**...
}
but I'm not sure if that will work and how to update my scope to include the book title. I've used this to repeat an x number of times but not include an updated $scope
Along with Corbin, my solution would be to pre-load the data. Do that with an Angular factory object.
angular.module('appName')
.factory('DataManager', DataManager);
function DataManager($log, $timeout, DataService) {
var mngr, config = getConfig(); // any default values
$log.debug('DataManager Init');
mngr = {
CurrentSearchTerm: null,
Libraries: [],
Abort: abort,
GetData: getData // Function call to get data.
};
// This call is your main ajax call, it could be one call to get the
// full dataset of lib + books in json format. Or multiple calls.
function getData(){
DataService.getData().then(function(response){
// ...parse data, etc...loop and :
mngr.Libraries.push(parsedDataItem);
};
}
return mngr;
}
Inject that into your controller. Then you loop over that data.
<ul>
<li ng-repeat="library in mngr.Libraries">
<!-- now you should have library.Books which was already sent down in the factory payload. -->
</li>
</ul>
Again to make your life a bit easier use a directive, each repeat push it's data into a directive:
<library data="library">
Which allows you to have a nice template and scope into just that one library.
angular('appName').directive('library',function(){
return{
restrict:'E',
replace:true,
templateUrl:'library.view.html',
scope:{
data:'='
},
controller:function($scope,$log){
// Here $scope.data === library from your ngRepeat!
}
};
});
Lastly, if you wanted an ondemand loading of the books, in the directive you can add a method to your DataManager to 'getBooks' for that one library object.
Does that make more sense?

Creating filters in EmberJS - Concept

I need some ideas on how to create a search filter in EmberJS where the search results can persist across views. Lets say I have a list of contacts and I filtered the list. Assume this route is called Contacts/Index
Now I have a second route called Contacts/Details. The user will be directed to this route once they select a result from the contact list. Now when they click 'Back To Contacts', I want the previous filter to be still applied instead of showing all the Contacts.
I didn't write any code yet, so I can't provide a JSFiddle. All I can think of now is probably to create a global variable to keep track of the text that is used to filter and apply that when transitioning back to the Contacts/Index view but I'm not sure if it is the right way to do it.
This is just pseudo-code that doesn't really care what your filter type is, but you could apply a filter property to the ContactsIndexController:
App.ContactsIndexController = Ember.ArrayController.extend({
//...
filter: 'name=bro',
filteredContent: function () {
if(this.get('filter')){
return this.get('content').filter//...;
} else {
return this.get('content');
}
}.property('filter')
//...//
});
Whenever you change the filter, make sure to update the filter property:
this.set('filter', 'foo=bar');
Then in your handlebars template you always loop over filteredContent:
{{#each filteredContent}}
{{/each}}
When you transition back and forth between the Contacts inner routes, it should retain the filter when you return to the index.
You can also see how this pattern could be used to take this one step further and manipulate the filter from literally anywhere in the application. If you aren't in that controller's context, you can still update that property and bindings will appropriately render the computed property next time you visit.
From another controller:
this.set('controllers.contacts-index.filter', 'year=20x6')
From a route:
this.controllerFor('contacts-index').set('filter', 'year=20x6')
From a view within the index controller:
this.set('controller.filter', 'year=20x6')
I'm sure you get the idea.
This is, of course, one of several approaches you could take. I prefer this particular pattern.
Hope that helps and good luck!

In Ember how do I update a view after the ArrayController content is modified?

I'm just starting with ember so forgive me if this question seems daft. I have an ArrayController that holds a list of people with the attribute name. I'm using the following handlebars call to create textfields for each name in the content array:
{{#each App.Controller}}
{{view Em.TextField placeholder="Name" valueBinding="name" }}
{{/each}}
this is working as expected. I first initialize my content array with several names and a textfield appears for each entry. However when I use pushObject to add a person to the content array a new textfield is not added! Using the console I can see that the people are being added to content, the view is simply not changing. Stranger still, if I add people in the following manner everything works as expected:
this.pushObject(newPerson);
var copy = this.get('content');
this.set('content',[]);
this.set('content',copy);
When I add people this way I get another textfield for the newly added person. The ember documentation says pushObject() is KVO-compliant, doesn't that mean it should update the dependencies and the view should update? Do I need to call rerender() or some other function to make pushObject() update the view or do I have to copy the array each time I want to update the view? Thanks!
So I figured out the issue. In my init function for the Controller I was simply adding elements to the content array like this
App.Controller = Em.ArrayController.create({
content: [],
init: function(){
this.insertNew();
},
insertNew: function(){
var t = App.Person.create({
name:"test"
});
this.pushObject(t);
},
});
This code did not change the view when insertNew was called (though the initialization worked). By adding this.set("content",[]) to my init function insertNew behaved as expected. Here's the working code:
App.Controller = Em.ArrayController.create({
content: [],
init: function(){
this.set("content",[]);
this.insertNew();
},
insertNew: function(){
var t = App.Person.create({
name:"test"
});
this.pushObject(t);
},
});
I had thought the first content:[] line meant I did not need to set content again later, but it seems that when initializing the array you have to set it again.
I encounter similar problems, when I use this.pushObject to add a new item, the view only updates if the item is the first object.
Later I tried with this.insertAt, it works well without any problem.

Categories