Sub-promises in AngularFire - selecting related children by ID? - angularjs

I have a json collection I've uploaded to my firebase, the sites and tools are related via arrays located in the former that point to keys in the latter
"sites": {
"s001": {
"name": "ACT-105",
"description": "Intro Accounting",
"type": "course",
"thumbnail": "debate",
"toolCount": 4,
"tools" : ["t001","t002","t003"]
},
"s002": {
"name": "ART-201",
"description": "Pottery Lab",
"type": "course",
"thumbnail": "sculpture",
"toolCount": 4,
"tools" : ["t001","t002","t003","t004"]
},
"tools": {
"t001": {
"name": "main-tool",
"title": "Home",
"description": "Main tool",
"thumbnail": "home.jpeg"
},
"t002": {
"name": "announce-tool",
"title": "Announcements",
"description": "System Announcements",
"thumbnail": "announcements.jpeg"
},
I open a url and promise; then grab the current site and its array of related tools in an array, then open another promise to loop through and get all the related tools. From the alert, it appears to only grab one tool then quits.
angular.module("foo", ["firebase"]).
controller("MyCtrl", ["$scope", "angularFire", function($scope, angularFire) {
var dbRef = "https://sampledb.firebaseio.com";
var siteRef = new Firebase(dbRef + "/sites/s003");
var promise = angularFire(siteRef, $scope, "site", {});
var sitetools = [];
promise.then(function() {
sitetools = $scope.site.tools;
alert("tools " + sitetools);
}).then(function () {
var toolList = [];
for (var i=0;i<sitetools.length;i++)
{
alert("tool " + sitetools[i]);
toolList.push(getTool(dbRef,toolId));
}
$scope.tools = toolList;
});
}]);
var getTool = function(dbRef,toolId) {
var toolitem;
var toolRef = new Firebase(dbRef + "/tools/" + toolId);
alert(toolRef);
var promise = angularFire(toolRef, $scope, "tool", {});
promise.then(function() {
alert("found tool " + toolId);
toolitem = $scope.tool;
});
return toolitem;
};
The fiddle is here: http://jsfiddle.net/5n9mj/1/

First of all, you should have got the alerts (3 of them) since the iterations went as expected, but the return of the getTool() function is always null: it returns before the promise is resolved and local tooitem variable is not accessible anymore.
Remember that all Firebase calls are async. Also, this code:
var promise = angularFire(toolRef, $scope, "tool", {});
promise.then(function() {
alert("found tool " + toolId);
toolitem = $scope.tool;
}
will trigger race conditions: $scope.tool is bound with the Firebase and there is no guarantee that it would be bound in a specific order and if there will be enough processor time to push it into your array before another promise is resolved. That's why it's better to listen on the value change using Firebase reference than to use angularFire and explicitly bind it to the scope variable.
I think you overcomplicated the code a little bit, you don't have to create new Firebase references every time you are binding your scope variables (unless you're going to use the reference later) with angularFire: angulerFire can accept String url as it's first param.
http://jsfiddle.net/oburakevych/5n9mj/10/
If I were you I would wrap the Tool functionality into a directive with a separate controller, so that each tool will have it's own scope, something like this:
<ul ng-repeat="toolId in tools">
<li><tool tool-id="{{toolId}}"/></li>
</ul>
var promise = angularFire(siteRef, $scope, "site", {});
promise.then(function() {
$scope.broadcast("event:SITE_INITIALIZED");
});
.controller("MyCtrl", ["$scope", "angularFire", '$timeout', function($scope, angularFire, $timeout) {
$scope.$on("event:SITE_INITIALIZED", function() {
angularFire(siteRef + "/item/" + $scope.itemId, $scope, "item", {});)
});

Related

How to convert string into JSON data in view part of angularjs

Hi I have this data in JSON form, and I want to convert this data of option into json so that i can call optionName, optionSubName in a view part of angularjs
[
{
"heading": "MakeUp Type / Selection",
"option": "{"optionName":"Bridal Makeup","optionSubName":"Engagement,Tika / Godh Bharai,Ring Ceremony","optionDesc":""}",
"values": null
},
{
"heading": "Makeup Type",
"option": "{"optionName":"Experienced","optionSubName":"","optionDesc":{"products":"Bobbie Brown,Makeup Forever and Premium Makeup Products","Makeup_Include":"Straightening,Blow Drys,Tong Curls,Updo's","Drapping":"Yes","Lashes":"Yes","Nail_Polish_Change":"Yes","Extension_Available":"Yes","Airbrush_Available":"Yes"}}",
"values": null
}
]
I have already tried this but this is not working by using ng-repeat i have using this {{data.option.optionName | JSON}} but this is not working in my view part of angularjs, Please help me out how to reach at my goal ?
Use AngularJS forEach:
angular.forEach(object, iterator, [context])
object: The object interate over.
iterator: The iterator function or the code.
context: Object to become context (using this) for the iterator function.
var app = angular.module('app', []);
app.controller('AppController', ['$scope', '$http',
function($scope, $http) {
$http.get('json/array.json').success(function(data) {
$scope.array = data;
angular.forEach($scope.array, function(x){
console.log(x.optionName);
})
});
});
}
]);

Loopback + Angular: `findById` Error. Expected response to contain an object but got an array

I'm trying to apply a filter for a chat room that makes it so that I only see messages that have a foreign key relationship to that chat room displayed, so I'm trying to pass shownMessages to the view. How do I do this effectively? The current error I'm dealing with is Error: [$resource:badcfg] Error in resource configuration for actionfindById. Expected response to contain an object but got an array. I'm searching as best as I can and still, nothing to my avail.
// for inside the room
// node - injection order is extremely important
.controller('InsideRoomController', ['$scope', '$q', 'ChatRoom', 'ChatMessage', '$stateParams', '$state', function($scope, $q,
ChatRoom, ChatMessage, $stateParams, $state) {
// we include Chatroom as a param to the controller and func since we work with that to display it's contents
// only show messages pertaining to that room
$scope.shownMessages = [];
ChatMessage
.findById({ id: $stateParams.messagesInChat })
.$promise
.then(function(showMessages) { // once we query to find chat rooms
$scope.shownMessages = shownMessages;
});
}])
relationsInChat is the name of the foriegn key relation I made in loopback between ChatRoom and ChatMessage which was generated in chat-room.json:
{
"name": "ChatRoom",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"name": {
"type": "string",
"required": true
},
"city": {
"type": "string"
}
},
"validations": [],
"relations": {
"ChatMessagers": {
"type": "hasMany",
"model": "ChatMessager",
"foreignKey": ""
},
"chatMessages": {
"type": "hasMany",
"model": "ChatMessage",
"foreignKey": "messagesInChat"
}
},
"acls": [],
"methods": {}
}
Edit: How do I get all messages belonging to the chat via foreign key? I'm trying to use stateparams but not sure how
Try printing $stateParams.messagesInChat value.
As the error shows, it contains an array rather than object(means multiple value is present instead of single ID), but findByID accept only one value as you are finding the data for only one ID.
I got what I needed. Needed an include filter! http://loopback.io/doc/en/lb2/Include-filter.html
.controller('InsideRoomController', ['$scope', '$q', 'ChatRoom', 'ChatMessage', '$stateParams', '$state', function($scope, $q,
ChatRoom, ChatMessage, $stateParams, $state) {
// we include Chatroom as a param to the controller and func since we work with that to display it's contents
// only show messages pertaining to that room
$scope.shownMessages = [];
function getMsgs() {
//User.find({where: {vip: true}, limit: 10}, cb);
return (ChatRoom.find({include: ['ChatMessages']}))
};
$scope.shownMessages = getMsgs();
console.log($scope.shownMessages);
}])

In AngularJS, $http doesn't change my select option tag data

I have an issue in AngularJS on using $http to fetch data from server.
Here is my HTML
<select ng-model="foo" ng-options="item as item.name for item in items"></select>
Here is my AngularJS Script
angular.module("myApp").controller("myCtrl", function($scope, $http) {
var $scope.items = [];
$scope.items = [
{
"id": 1,
"name": "item1"
},
{
"id": 2,
"name": "item2"
},
];
getData();
function getData() {
$http.get("ng/getData")
.then(function(response) {
if (response.status == 200) {
$scope.items = response.items;
/*
$scope.items = [
{
"id": 5,
"name": "item11"
},
{
"id": 4,
"name": "item22"
}
];
*/
}
});
}
});
What expect from this code is when $http fetches data from server, then select dropdown data will change. But it's not changing anything. I have also printed the response items in the console inside the success callback.
Maybe I don't understand $http usage well enough. Perhaps when I console out data after getData(); $scope.items doesn't change at all. And I think that maybe $http always run at last stage.
Can anybody help to explain this issue? If my assumption is correct, what is the solution that I am looking for?
I think you simply have to add a track by clause:
ng-options="item as item.name for item in items track by item.id"
Check the response object. When you are using 'then' callback to get the resolved data then the actual API result is stored in the 'data' property of the response. So change your code to
$scope.items = response.data;
Or you can use the success callback to attach the data directly.
$http.get("ng/getData")
.success(function(response) {
if (response.status == 200) {
$scope.items = response.items;
}
});

ng-view not updating upon ng-click

I am using ng-view within my Angular application. within index.html I have a button, When I click on this button I am getting the JSON data, however I cant access my updated scope back into my ng-view template. please examine the code below.
<
div class="row" style="display:none;" id="srhBox" ng-controller="SearchController">
<button
data-channelid="" data-relatedtovideoid="" data-videoduration="" data-videotype="" data-keyword="{{keyword}}"
ng-click="search($event)" class="btn btn-default" type="submit">
</div>
Here is controller
angular.module('kZoneApp').controller('SearchController', ['$location','$scope', 'dataFactory', '$window', '$routeParams', '$rootScope','$http', function ($location,$scope, dataFactory, $window, $routeParams, $rootScope, $http) {
$scope.search = function (evt) {
$location.path("search"); // update ng-view
var keyword = $(evt.currentTarget).attr("data-keyword");
var channelid = $(evt.currentTarget).attr("data-channelid");
var relatedtovideoid = $(evt.currentTarget).attr("data-relatedtovideoid");
var videoduration = $(evt.currentTarget).attr("data-videoduration");
var videotype = $(evt.currentTarget).attr("data-videotype");
var url = 'http://xxxx.com/api/Videos/Search?'
url = url + 'keyword=' + keyword
url = url + '&channelid=' + channelid
url = url + '&relatedtovideoid=' + relatedtovideoid
url = url + '&videoduration=' + videoduration
url = url + '&videotype=' + videotype
$http.get(url).
success(function (data) {
$scope.searchResults = data;
console.log(data) // Yes it shows JSON data
});
}
}]);
and here is code for ng-view template...
<div class="col-md-12 " style="padding-top:5px;">
{{searchResults.nextPageToken}} // This is BLANK
</div>
json....
{
"nextPageToken": "CBkQAA",
"items": [
{
"etag": "\"dhbhlDw5j8dK10GxeV_UG6RSReM/B_XTRBSSmBkwVbCPVJmABe4rJpo\"",
"id": {
"kind": "youtube#video",
"videoId": "n8JEY8PKCcI"
},
"snippet": {
"publishedAt": "2015-06-05T18:51:06.000Z",
"channelId": "UCY48IObMpigEGsBEtfUUepg",
"title": "WWE 2K15 The New Day vs Power Rangers Summer Slam wwe 2k15",
"description": "I created this video with the YouTube Video Editor (http://www.youtube.com/editor)",
"thumbnails": {
"default": {
"url": "https://i.ytimg.com/vi/n8JEY8PKCcI/default.jpg"
},
"medium": {
"url": "https://i.ytimg.com/vi/n8JEY8PKCcI/mqdefault.jpg"
},
"high": {
"url": "https://i.ytimg.com/vi/n8JEY8PKCcI/hqdefault.jpg"
}
},
"channelTitle": "Caqueen520",
"liveBroadcastContent": "none"
}
},..... more items
route config.
$routeProvider
.when('/search', {
templateUrl: 'pages/search.html',
controller: 'SearchController'
})
so {{searchResults.nextPageToken}} is empty, even though console.log clearly shows it has value. what could be causing this.
UPDATE
it seems like the problem is.... I have ng-controller="SearchController" for my button and then i am loading the VIEW (ng-view) using the routes, so angular does not update two controllers with same name on the same page....it updates the first controller & ignores the second one.
Though $http takes care of running digest cycle which takes care of two way biding, try adding a timeout using $timeout because it also runs the digest cycle.
$http.get(url)
.success(function (data) {
$timeout(function () {
$scope.searchResults = data;
console.log(data) // Yes it shows JSON data
}, 100);
});
As per your route config, /search view uses the same controller SearchController. So when you load the /search view using $location.path('/search') the controller is reinitialized so $scope.searchResults will be undefined. You will have to have to search login inside a different controller and use that controller in the route config.
OR you can have a hidden partial view and show it only when you have the searchResults using ng-if directive.
So in the main view have this html
<div ng-if="searchResults" class="col-md-12 " style="padding-top:5px;">
{{searchResults.nextPageToken}} // This is BLANK
</div>
And remove the $location.path('/search') from $scope.search method in the SearchController.

AngularJS - Cannot read property 'navigable' of undefined

My app.run:
app.run([
'$q', '$rootScope', '$state', 'settings', 'datacontext',
function ($q, $rootScope, $state, settings, datacontext) {
settings.currentLang = settings.languages[0];
breeze.core.extendQ($rootScope, $q);
datacontext.getSideMenuItems().then(function (data) {
var startUp = undefined;
var rootUrl = '/app/views/';
/// this is the upper level used for side menu only
angular.forEach(data, function (value) {
// now loop thru the data for the $state items
angular.forEach(value.viewStates, function (viewState, key) {
if (key == 0) {
startUp = viewState.name;
}
var state = {
"url": '/' + viewState.name,
"parent": viewState.parentName,
"abstract": viewState.isAbstract,
"views": {}
};
angular.forEach(viewState.views, function (view) {
state.views[view.name] = {
controller: view.controllerName,
templateUrl: rootUrl + view.templateUrl + '.html'
};
});
$stateProviderRef.state(viewState.name, state);
});
$state.go(startUp);
});
});
}
My Data: [as JSON representation, ignore the Capitalization, breeze renames them lowercase]
[{
"$id": "1",
"Id": 2,
"Icon": "fa-home",
"IsActive": "active",
"IsShared": false,
"OrderNum": 1000,
"Title": "Dashboards",
"FK_DbModuleId": 1,
"DBoardModule": null,
"ViewStates": [
{
"$id": "2",
"Id": 2,
"IsAbstract": false,
"Name": "PersDboards01",
"ParentName": "root",
"OrderNum": 1,
"FK_ViewGroupId": 2,
"ViewGroup": {
"$ref": "1"
},
"Views": [
{
"$id": "3",
"Id": 4,
"ControllerName": "MyDashboardCtrl",
"Name": "container#",
"TemplateUrl": "dashboards/myDashboard",
"FK_ViewStateId": 2,
"ViewState": {
"$ref": "2"
}
},
{
"$id": "4",
"Id": 5,
"ControllerName": "LeftPanelCtrl",
"Name": "leftPanel#",
"Title": "null",
"TemplateUrl": "shell/html/left-panel",
"FK_ViewStateId": 2,
"ViewState": {
"$ref": "2"
}
}
]
}
]
I know the Json has the properties capitalized but I am not really using JSON, I just copied this off of fiddler which got it from my Web API.
I understand that the error "Cannot read property 'navigable' of undefined" means I am not defining the child state after I define the parent state, but I cannot see where I am doing that.
Does anyone know a better way to structure the app.run?
UPDATE:
I found my error, see below.
Solved:
Indeed the error was due to my creating child states before establishing the parent state.
I looked at my database and noticed the parent state LAST in my que:
Id : 150
IsAbstract : 1
Name: "root"
ParentName: ""
.....
Had I made sure to make it first the problem would not have occured.
Id : 1
IsAbstract : 1
Name: "root"
ParentName: ""
.....
I recognize this situation will not help many other than as a reminder to make sure your parent state is the first state in your loop when creating dynamic states/views
The code you included above does not reference 'navigable' at all, so it cannot be the source of this error. Please either include the additional files where that reference is being made, or better still, post a Plunkr that illustrates the problem.
I'm posting this as an answer rather than a comment because there's an additional detail that may help you find the actual source of the error, and you may find that you don't need any further help when you find it. In Angular it's very common for error locations to be hard to find. Many of its mechanisms have "deep" calls stacks, especially when an error is triggered either by the injector or digest cycle. What you end up finding is that the error itself is happening somewhere else from where you think it is.
I think that it is likely you are encountering the same error reported here:
https://github.com/angular-ui/ui-router/issues/488
You appear to be using AngularJS with Angular UI Router and Breeze, all together. The error reported there is identical to yours, and if you re-examine your code and follow what the others reported on that thread I think you will find that your problem is fixable... but has little/nothing to do with your actual code above!
The issue here is related to wrong Upper and Lower case JSON property naming during the processing. There is a working plunker - which calls console.log(state) at the end of JSON processing.. to show the result (do not foget to start the console with F12)
A snippet comparison would be like this
angular.forEach(response.data, function (value) {
// NO viewStates
// but ViewStates
angular.forEach(value.ViewStates, function (viewState, key) {
if (key == 0) {
startUp = viewState.Name; // instead of name
}
var state = {
"url": '/' + viewState.Name, // instead of name
"parent": viewState.ParentName, // instead of parentName
"abstract": viewState.IsAbstract, // instead of isAbstract
"views": {}
};
// No views
// but Views
angular.forEach(viewState.Views, function (view) {
state.views[view.Name] = {
controller: view.ControllerName, // instead of controllerName
templateUrl: rootUrl + view.TemplateUrl + '.html'
};
});
$stateProviderRef.state(viewState.Name, state); // instead of name
console.log(state)
});
});
because the JSON is like that (see Upper case at the begining):
"ViewStates": [{
"IsAbstract": false,
"Name": "PersDboards01",
"ParentName": "root",
...
},
"Views": [{
"ControllerName": "MyDashboardCtrl", // ControllerName
"Name": "container#", // Name
"TemplateUrl": "dashboards/myDashboard", // Templateurl
...
The working plunker plunker (just loging the result into console) shows that the rest of the code should be working

Resources