I am learning javascript. I want to make a small chat app using angular js.
all of the data will be stored in javascript data structures - arrays, objects and be brought out to the front using angular js and styled with css.
now I have a button in index.html like this
<div class="container">
<button ng-click="createuser"></button>
</div>
in my js I have a controller where I am hanging an array called test on angular's $scope
$scope.tests = [ {'username': 'I am Joe'}];
I am looping through the tests array with ng-repeat in index.html
<li ng-repeat="test in tests">
{{test.username}}
</li>
now I have a method on the $scope object
$scope.createuser = function createuser() {
$scope.tests.push({'username': 'Mandy'})
};
that takes the array and use the push method (of the array) to push a sample object into the tests
but when I go to index.html and click, its not working because Mandy is not getting added to the loop.
Please note the method should see the $scope.tests because its all under one controller.
Forgot the ()
ng-click="createuser()"
Related
I am trying to find the best way to transform a large JSON object into a view model. Previously, I had the model incorporated into the view, which is a bad practice. So, now I have the model being generated inside of a controller. I am using Lodash as a utility library.
My current design plan is to transform the JSON object into a "master" array that is accessible in the controller's scope. The JSON file is being served by Express. ModelService simply gets this file to make it available in the controller.
$scope.arr is the "master" array that I want to use in the view.
I also made the JSON data available for viewing at an external link since it is so large. Here it is.
(function() {
'use strict';
angular
.module('app')
.controller('ModelController', ModelController);
function ModelController($scope, ModelService, _) {
$scope.jsonData = ModelService.getAll();
$scope.getData = function() {
$scope.jsonData.$promise.then(function(data) {
$scope.all = data;
$scope.arr = [];
_.mapValues($scope.all.parent1.clusters, function(cluster) {
$scope.arr.push(cluster.name);
_.mapValues(cluster.subclusters, function(subcluster) {
$scope.arr.push(subcluster.name);
_.mapValues(subcluster.entities, function(entity) {
// map entities
})
});
});
});
};
$scope.getData();
}
})();
This code is just adding cluster and subcluster names to the array. I'd like the subclusters to be mapped to their parent cluster. The idea I have for doing this involves transforming each cluster element into its own array, and then adding the subclusters, and then transforming each subcluster into an array in order to map the entities to them. This seems tedious and inefficient. So, I am looking for a better way to achieve this.
It would be nice if I could add each cluster object to the array in one fell swoop without all the mapping and converting objects to arrays. Is that possible at all?
The wireframe view looks like this. The Flex Cluster Title is the name of the subcluster, and each number inside of them is an entity.
Firstly, I would move this processing into the service. It's easier to test, and keeps your view separated from your models (Controllers, are really more part of the "View" IMO when it comes to Angular especially if you're considering upgrading to Angular 2.0 in the future).
In Angular, I think the appropriate way to solve this, would be to use components (or directives) combined with ng-repeat.
The page template:
<!-- Page template, assume $ctrl is your controller, $ctrl.clusters is the data -->
<cluster ng-repeat = "cluster in $ctrl.clusters"
cluster-data="cluster" >
</cluster>
The cluster directive template:
<!-- Assume $ctrl is the controller for the cluster directive, $ctrl.cluster is the cluster object. -->
<div class="name">{{$ctrl.cluster}}</div>
<div class="subClusterNames"
ng-repeat="subCluster in $ctrl.cluster.subClusters>
{{subCluster.name}}
</div>
You might think that this is mapping your data too closely to the view, but as long as you use components to display your data (ie, don't put it all into one template) I think you'll be fine.
I have a problem with my angularjs app where my app is routing to the wrong page when using an ng-repeat array to determine the route.
data looks like this and is accessed in the person controller:
[
{
"name":"AJ lastname",
"img_name":"AJ_lastname",
"location":"Baltimore, Maryland",
"info":"stuff"
},
{
"name":"Albert lastname",
"img_name":"Albert_lastname",
"location":"Boston, Massachusetts",
"info":"stuff"
} // ... more data
]
html: (the anchor tag links to the person based on their index in the array (I believe this may be what I need to change to fix the problem, but I'm not sure)
<ul class="main-list">
<li class="list-item fade" ng-repeat="student in students | filter:filter">
<a href="/#person/{{$index}}">
<img class="portrait listimg" ng-src="/images/{{student.img_name}}.jpg" alt="portrait of {{student.name}}">
<h2>{{student.name}}</h2>
<h4>{{student.location}}</h4>
</a>
</li>
</ul>
Routing from angular: (the route with '/person/:itemId' is routing to a page specific to a specific user, where their index in the array determines their id)
app.config(function ($routeProvider, $httpProvider) {
$routeProvider
.when('/list', {
templateUrl: './js/templates/list.html',
controller: 'ListController'
})
.when('/person/:itemId', {
templateUrl: './js/templates/person.html',
controller: 'PersonController'
})
.otherwise('/list');
});
Here is the controller for the dynamic page. It works perfectly for the original array, but once I attempt to sort the array, the index no longer corresponds to the correct student.
app.controller('PersonController', function ($scope, $http, $routeParams) {
$scope.person = 'Someone\'s name';
$http.get('../js/students.json').success(function (data) {
$scope.allStudents = data;
$scope.studentId = $routeParams.itemId;
$scope.student = data[$scope.studentId];
});
So the functional problem is that the index applies to the first student in the large array of data. It appears to work perfectly, and the correct data populates the page, but when I use the html/text input to filter the list, the original indices are updated on the html side, and they do not correspond to the original array. So the routing sends them to the wrong page.
How can I make the routing work even for a filtered list?
One way you can do this is by using a function which returns you the index a student had in the original array for each student in your ng-repeat.
$scope.getIndex = function(student) {
return $scope.students.indexOf(student);
}
You can then call the function in your list like:
<a ng-href="/#person/{{getIndex(student)}}">
This though is not quite the most performant code you could imagine.
Another way would be to just temporarily store the index of the student as a property and use that one to reference it, again not quite the nicest solution:
$scope.students = $scope.students.map(function(student, index) {
student.index = index;
return student;
});
And in the list:
<a ng-href="/#person/{{student.index}}">
However, if you can somehow assign the students a unique id that would definitely be the preferred way. That way you also make sure that you always reference the same student. If your students.json somehow changes between the time you create the list and the time the user clicks on an item you may reference the wrong one again...
By the way always use ng-href when including placeholders in the link. Why you should do so is well described in the Angular API docs:
Using Angular markup like {{hash}} in an href attribute will make the link go to the wrong URL if the user clicks it before Angular has a chance to replace the {{hash}} markup with its value. Until Angular replaces the markup the link will be broken and will most likely return a 404 error. The ngHref directive solves this problem.
You are creating the ng-repeat using some object on $scope called students, correct? If this is being built from the same students.json as in your controller then their student ids should logically be equivalent. So just change the href from "/#person/{{$index}}" to "/#person/{{student.studentId}}".
If for some reason they aren't the same then when you create the students object you can add a new attribute, studentId, which holds the value of their index in the array and then use the previous suggestion.
Just remember that when using ng-repeat if you have identical objects it'll throw an error so you have to add the "track by $index" to it.
I'm attempting to feed an array of objects from an Angular Controller to an ng-repeat directive.
The objects returned in the array have several properties, a few of which may contain HTML that needs to be output by the result of ng-repeat. I can't seem to figure out how to trustAsHTML the entire object that is returned.
My view looks like this:
<li ng-repeat="user in searchedUsers" ng-bind-html="user">
I've attempted it like this:
$scope.searchedUsers = data;
for(var user in $scope.searchedUsers){
$sce.trustAsHtml($scope.searchedUsers[user].matched);
$sce.trustAsHtml($scope.searchedUsers[user].unmatched);
}
And also attempted structuring my directives like this:
<li ng-repeat="user in searchedUsers"><span ng-bind-html="user.matched">{{user.matched}}</span> <span ng-bind-html="user.unmatched">{{user.unmatched}}</span></li>
But I get the error back:
Error: [$sce:unsafe] Attempting to use an unsafe value in a safe context.
The JSON object I'm using is as follows:
[{"id":2,"name":"Jonny","email":"jonnyasmar#me.com","created_at":"2015-10-25 00:58:10","updated_at":"2015-10-25 02:11:59","matched":"jonny<span class=\"match\">as<\/span>mar#me.com","unmatched":"Jonny"}]
Any idea how this can be accomplished or do I need to rethink my implementation?
You need to include ngSanitize js and dependency:
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.0/angular-sanitize.min.js"></script>
and, on module:
angular.module('yourModule', ['ngSanitize'])
after this, the ng-bind-html will work. For example:
<div ng-bind-html="user.matched"></div>
take a look at complete code on jsbin
This is the html:
<div ng-controller="FilterController as ctrl">
<div>
All entries:
<span ng-repeat="entry in ctrl.array">{{entry.from}} </span>
</div>
<div>
Entries that contain an "a":
<span ng-repeat="entry in ctrl.filteredArray">{{entry.from}} </span>
</div>
</div>
This is my script:
angular.module('myApp', ["firebase"]).
controller('FilterController', ['filterFilter', '$firebase', function(filterFilter, $firebase) {
var ref = new Firebase("https://******.firebaseio.com/");
this.array = $firebase(ref).$asArray();
this.filteredArray = filterFilter(this.array, 'a');
}]);
The result from filteredArray is just empty. What have I done wrong? Many thanks for any insight.
The data loads in an asynchronous manner. This is JavaScript 101 and you should probably spend a little time on asynchronous ops before you try to tackle Angular and Firebase.
Your filter call happens probably even before the request is sent to the server, thus the array is still empty.
You can use the $loaded method to wait for the initial data to be downloaded. However, your filtered results will still be static, circumventing the real-time capabilities.
this.array.$loaded(function() {
console.log(this.array.length); // current length at time of loading
});
console.log(this.array.length); // 0, hasn't loaded yet
Generally, you should not be doing things like this at the code level, but instead letting Angular and Firebase work their magic; move it into the view:
<li ng-repeat="item in array | filter:searchText" />
If you find yourself trying to manipulate arrays in your controller to transform data, check out $extendFactory instead. But mostly, just leave it alone and bind it to scope, then let Angular take care of the rest.
I have the following html (which can be accessed directly or called via ajax):
<section id="content" ng-controller="setTreeDataCtrl" get-subthemes>
<dl ng-repeat="subtheme in allSubthemes">
<dt>{{subtheme.Title}}</dt>
</dl>
Then I'm using the following directive:
myApp.directive('getSubthemes', function() {
return function($scope, element, attrs) {
$scope.allSubthemes = [];
angular.forEach($scope.data.Themes, function(value, key) {
angular.forEach(value.SubThemes, function(value2, key2) {
$scope.allSubthemes.push({
'ThemeTitle': value.Title,
'ThemeUrlSlug': value.UrlSlug,
'Title': value2.Title,
'UrlSlug': value2.UrlSlug
});
});
});
}
});
$scope.allSubthemes seems ok, but the dl's don't get rendered.
I can see for a second everything rendered properly and then it get's back to {{subtheme.Title}}, almost like it's being "unrendered"... any ideas of what I'm doing wrong?
Demo jsFiddle: http://jsfiddle.net/HMp3a/
rGil fixed the jsFiddle. It was missing a ng-app="pddc" declaration on an element so Angular did not know where to begin its magic.
I'd like to mention another way to render to the data in question. I suggest using an ng-repeat within an ng-repeat. See my forked & updated fiddle here. You can actually refer to the parent theme within the ng-repeat of the subtheme, so you don't have to copy values from the parent theme into each subtheme (which effectively eliminates the need for the directive in this example).
Another reason to use a nested ng-repeat is because of async issues that could come up when pulling data from a web service asynchronously. What could happen is when the directive executes, it may not have any data to loop through and populate because the data hasn't arrived yet.
If you use two ng-repeats, Angular will watch the $scope.data and re-run the ng-repeats when the data arrives. I've added a 500 ms delay to setting the data in my example to simulate web service latency and you'll see that even with the "latency", the data eventually renders.
There are two other ways around the async issue:
Use scope.$watch() in your directive, to watch for the data manually, or
Use the "resolve" functionality from Angular's routing feature to make sure the data is retrieved prior to controller execution.
While these alternative methods work, I think both are more complicated then just using two ng-repeats.