Deferring Angular until Firebase data received - javascript

Right now, I am having users input data on the a sign up page, which includes having the user input their "plan type". I store this user data on Firebase.
On the following page after the user has submitted the previous input page, I take the user to an output page that uses AngularJS to show all plans filtered by the user's "plan type" (in the code, it's the customFilter). So, as soon as the page loads, I want to be able to call the user's plan type from firebase and then make it the initial filter that customFilter uses.
How do I get the Angular filter to wait until I get the "plan type' from Firebase? Any examples would be much appreciated.
I've added the code below to make this easier to answer**
<body ng-app="tipOutput" ng-controller="Tips">
<div ng-controller="MainCtrl">
// Custom filter that I want to customize based on user data
<span class="select">
<select style="width:100%" ng-model="filterItem.plan" ng-options="item.name for item in filterOptions.plans"></select>
</span>
// Table using ng-repeat and above filter
<table>
<tbody>
<tr ng-repeat="tip in tips | filter:customFilter">
<td style="vertical-align:top"><span><strong>{{tip.planName}}</strong></span><span ng-show="tip.planDetail">Plan Detail: {{tip.planDetail}}</span></td>
</tr>
</tbody>
</table>
</div>
</body>
Angular app code here
angular.module('tipOutput', ['firebase', 'filters'])
.controller('Tips', ['$scope', 'angularFire',
function ($scope, angularFire) {
var ref = new Firebase('https://sitename.firebaseio.com/tips');
angularFire(ref, $scope, "tips");
}])
.controller('MainCtrl', function($scope) {
//Contains the filter options
$scope.filterOptions = {
plans: [
{id : 2, name : 'All Plans', type: 'all' },
{id : 3, name : 'Plan Type 1', type: 'plan-type-1' },
{id : 4, name : 'Plan Type 2', type: 'plan-type-2' },
{id : 5, name : 'Plan Type 3', type: 'plan-type-3' },
{id : 6, name : 'Plan Type 4', type: 'plan-type-4' },
{id : 7, name : 'Plan Type 5', type: 'plan-type-5' },
{id : 8, name : 'Plan Type 6', type: 'plan-type-6' }
]
};
// Here's where the initial value of the filter is set. Currently, it's not dynamic, but I
// want it to be based off a variable that comes in asynchronously (i.e. likely after this
// code would otherwise run)
$scope.filterItem = {
plan: $scope.filterOptions.plans[0]
}
//Custom filter - filter based on the plan type selected
$scope.customFilter = function (tip) {
if (tip.servicesReceived === $scope.filterItem.plan.type) {
return true;
} else if ($scope.filterItem.plan.type === 'all') {
return true;
} else {
return false;
}
};
})

I tried to simulate your call to your firebase.
DEMO: http://plnkr.co/edit/VDmTCmR82IyaKnfaT1CP?p=preview
html
<body ng-controller="MainCtrl">
<p>Hello {{name}}!</p>
<div>
<span class="select">
<select ng-model="filterItem.plan" ng-options="item.name for item in filterOptions.plans"></select>
</span>
<table border="1">
<tbody>
<tr ng-repeat="tip in (filtered = (tips | filter:customFilter))">
<td>
<span><strong>{{tip.planName}}</strong></span>
<span>Plan Detail: {{tip.planDetail}}</span>
</td>
</tr>
<tr ng-show="filtered.length==0">
<td>None</td>
</tr>
</tbody>
</table>
</div>
</body>
I keep the filtered list to be able to display a message if there is no items.
js
var app = angular.module('plunker', ['firebase']);
app.controller('MainCtrl', function($scope, $timeout, angularFire) {
$scope.name = 'World';
$scope.tips = [];
/*
// since we dont have access to your firebase, i used a $timeout
var ref = new Firebase('https://sitename.firebaseio.com/tips');
// we wait for the callback of angularFire
angularFire(ref, $scope, "tips").then(function(response) {
var index = 1; // find the good index in filterOptions
$scope.filterItem.plan = $scope.filterOptions.plans[index];
});*/
// simulate the response
$timeout(function() {
$scope.tips = [
{planName: '213', planDetail:'534',servicesReceived:'plan-type-1'},
{planName: '123', planDetail:'345',servicesReceived:'plan-type-2'},
{planName: '321', planDetail:'643'} // this one has no serviceReceived
];
// set it to the response receive from the server
var response = 1;
$scope.filterItem.plan = $scope.filterOptions.plans[response];
}, 1000);
$scope.filterOptions = {
plans: [
{id : 2, name : 'All Plans', type: 'all' },
{id : 3, name : 'Plan Type 1', type: 'plan-type-1' },
{id : 4, name : 'Plan Type 2', type: 'plan-type-2' },
{id : 5, name : 'Plan Type 3', type: 'plan-type-3' },
{id : 6, name : 'Plan Type 4', type: 'plan-type-4' },
{id : 7, name : 'Plan Type 5', type: 'plan-type-5' },
{id : 8, name : 'Plan Type 6', type: 'plan-type-6' }
]
};
// default value
$scope.filterItem = {
plan: $scope.filterOptions.plans[0] // Do something with response
}
$scope.customFilter = function (tip) {
return (tip.servicesReceived || 'all') === $scope.filterItem.plan.type;
};
});

Resolve the fireBase Data on the route. This will prohibit the controller from loading before the data is present. Then just inject the data into the controller and continue forward with your normal process.
I'm not going to write it using your variables, but an exmaple of such a config file would look like:
'use strict';
angular.module('someModule.dashboard')
.config(function ($stateProvider){
$stateProvider
.state('dashboard', {
url: '/dashboard',
templateUrl: '/app/dashboard/html/dashboard.html',
controller: 'dashBoardCtrl',
resolve: {
currentAuth: function(fireBaseAuth){
return fireBaseAuth.auth().$requireAuth();
},
currentUser: function($fireBaseUser, Session){
return $fireBaseUser.user(Session.id).$asObject();
},
userList: function($fireBaseUser){
return $fireBaseUser.userList().$asArray();
}
}
});
});
Then Your controller would look something like this:
'use strict';
angular.module('someModule.dashboard')
.controller('dashBoardCtrl', function($scope, currentUser, userList){
$scope.userList = userList;
});

Related

Angular Directive Element dynamic Template fields with its updated model value from controller?

I have <superhero> directive which has two directive
web-buttons to take of the form validation and post the updated
ngModel value to respective controller
fieldMap directive to generate the dynamic fields by object we are passing from respective controller
Here is the example which i have worked
directive attribute called saveFormFn will tell the button to call which function to invoked using enter attribute directive.
For example. After click save button it will call the function 'Ctrl1saveFormFn' from controller Ctrl1 .This function will make ajax post to save the form fields.
After updating the text fields with some content and click save,I have passed the current scope of the directive to respective controller (see console log). i could not get the updated fielddata value from current Scope.
$scope.Ctrl1saveFormFn = function(item){
_.each(item,function(currentScope){
console.log(currentScope)
// here i want to collect the form data with updated fielddata values
})
}
I am beginner.Am i on right path? Please advice
I've re-written your code because it was pretty hard to understand.
I would do it like this:
Use ng-include to load the template of your buttons. That's loading the control buttons edit and save.
Save your data in a variable in the superhero directive. Maybe it would be even better to store it in a separate service/factory.
Create a directive customForm that will create a form based on the supplied model that you're passing to its scope.
The main application logic is in the superhero directive because it is adding the controls save/edit to the DOM. If saving/editing is not only related to the superhero it would be better to do it in your main controller.
Please have a look at the demo below or in this jsfiddle.
angular.module('demoApp', [])
.directive('superhero', Superhero)
.directive('customForm', CustomForm)
.controller('mainController', MainController);
function Superhero() {
return {
restrict: 'E',
scope: {
formModel: "=",
},
template: '<div class="hero"><div ng-include="\'web-buttons.html\'"></div><custom-form model="formModel"></custom-form></div>',
controllerAs: 'superHeroCtrl',
controller: function ($scope) {
var self = this;
console.log('controller directive');
angular.extend(this, {
abilities: [],
editMode: false,
addStrength: function (data) {
self.abilities.push(data);
},
getStrength: function () {
return self.abilities;
},
showSave: function() {
self.editMode = true;
$scope.formModel.editMode = true;
},
hideSave: function() {
self.editMode = false;
$scope.formModel.editMode = false;
},
save: function() {
self.addStrength('can fly');
console.log(self.getStrength());
console.log('saving data now of form now...', $scope.formModel.data);
alert('saving data of form now: ' + self.getStrength()[0] + ' - ' + JSON.stringify( $scope.formModel.data, null, 2));
self.hideSave();
}
});
}
}
}
function CustomForm() {
return {
restrict: 'EA',
scope: {
model: '='
},
template: '<div ng-if="model.editMode" ng-repeat="formElement in model.fields" ng-include="formElement.template.url"></div>'
}
}
function MainController() {
this.normalForm = {
editMode: false,
data: {
},
fields: {
'NAME':{
template: {
url: 'customForms/text.html',
type: 'edit' // not sure for what it is needed
},
label: 'First name',
id: "NAME",
placeholder : "First Name",
fieldData: "NAME",
key : 'first_name'
},
'LNAME': {
template: {
url: 'customForms/text.html',
type: 'edit' // not sure for what it is needed
},
label: "Last Name",
placeholder : "Last Name",
id: "LNAME",
key : 'last_name'
}
}
};
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="demoApp" ng-controller="mainController as mainCtrl">
<script type="text/ng-template" id="customForms/text.html">
<label for="{{formElement.id}}">{{formElement.label}}</label>
<input ng-model="model.data[formElement.key]" placeholder="{{formElement.placeholder}}" id="formElement.id"/>
</script>
<script type="text/ng-template" id="web-buttons.html">
<button ng-click="superHeroCtrl.showSave()" ng-if="!superHeroCtrl.editMode">edit</button>
<button ng-if="superHeroCtrl.editMode" ng-click="superHeroCtrl.save()">save</button>
</script>
<superhero form-model="mainCtrl.normalForm"></superhero>
<h3>debug output:</h3>
<pre>
{{mainCtrl.normalForm |json}}
</pre>
</div>

Common filter for two different routes

var app = angular.module("myApp", ['ngRoute', 'ngSanitize']);
app.controller('landingPageController', function($scope) {
$scope.countries = [{
name: "India",
Id: "1"
}, {
name: "Nepal",
Id: '2'
}];
});
I have same filter criteria on two diff. pages in angular, So I have used single controller with 2 routes, and two diff. html for filter part. I need if i select any country on home page, the same selection should be reflected to the about page(instead of again select same country) . I mean it should be common filter across 2 page not individual.
Here is url: http://plnkr.co/edit/VMYYBDy4doWCzUa4d6Uq?p=preview
need help to sort it out...
You can share data between different controllers (or different instances of same controller) using e.g. services. So in your scotchApp (sounds tasty, BTW!) you could have something like
scotchApp.service('countryService', function() {
var current;
return {
// set selected country
set: function(country) {
current = country;
console.log('set', current);
},
// get selected country
get: function() {
console.log('get', current);
return current;
}
};
});
And your mainController as
scotchApp.controller('mainController', function($scope, countryService) {
$scope.countries = [{
name: 'India',
id: 1 // note the property casing here
},{
name: 'Nepal',
id: 2
}];
// get selected country
$scope.selectedCountry = countryService.get();
// set selected country
$scope.set = function(country) {
countryService.set(country);
};
});
And the template for your routes
<div>
<select ng-options="country.name for country in countries track by country.id"
ng-model="selectedCountry"
ng-change="set(selectedCountry)">
<option value="">Select country</option>
</select>
</div>
That should do it.

AngularJS search array of objects

I have an array of objects in my controller eg:
$scope.fields = [
{fieldName:'houseNum',fieldLabel:'House Number',disabled:false},
{fieldName:'street',fieldLabel:'Street',disabled:false},
{fieldName:'city',fieldLabel:'City',disabled:true},
{fieldName:'state',fieldLabel:'State',disabled:true},
]
In the HTML I would like to be able to get a fieldLabel where fieldName=='street'. The AJS documentation presumes that every filter case should be in the context of ng-repeat - but not so in my case as I am just trying to pluck one 'fieldLabel' from the 'fields' array based on 'fieldName'
eg: HTML
{{ fieldLabel in fields | filter : {fieldName:'street'} : true}}
How can I make something like this work - or do I need to create my own directive and pass the $scope.fields to the directive and loop through manually?
You could do:
{{ (fields | filter : {fieldName:"street"} : true)[0].fieldLabel}}
(fields | filter : {fieldName:"street"} : true) returns an array of filtered items get the first one [0] and access fieldLabel property out of that object.
angular.module('app', []).controller('ctrl', function($scope) {
$scope.fields = [{
fieldName: 'houseNum',
fieldLabel: 'House Number',
disabled: false
}, {
fieldName: 'street',
fieldLabel: 'Street',
disabled: false
}, {
fieldName: 'city',
fieldLabel: 'City',
disabled: true
}, {
fieldName: 'state',
fieldLabel: 'State',
disabled: true
}, ]
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
{{ (fields | filter : {fieldName:"street"} : true)[0].fieldLabel}}
</div>
Though better option would be to set the property from the controller itself, so that the filter does not run during every digest cycle.
function getFieldByName(prop){
var field = {};
//Or just use a for loop and break once you find a match
$scope.fields.some(function(itm){
if(itm.fieldName === prop){
field = itm;
return true;
}
});
//Or you could inject $filter as well an do as below
//return $filter('filter')($scope.fields,{fieldName:"street"})[0] || {}
return field;
}
//Somewhere
$scope.streetField = getFieldByName('street');
In the view:
{{streetField.fieldLabel}}
Array.some

Angular: how to hide DOM element based on the number of filtered elements in model?

Here's a simple controller that contains a list of users:
<script type="text/javascript">
angular.module('project', [])
.controller('UsersController', ['$scope', function ($scope) {
$scope.users = [
{ text: 'User 1', done: true, extension: 123 },
{ text: 'Another user', done: false, extension: 456 }];
}]);
$scope.selectedUsers = function () {
var results = [];
for (i = 0; i < $scope.users.length; i++) {
if ($scope.users[i].done) {
results.push($scope.users[i]);
}
}
return results;
};
</script>
And a simple HTML that just shows a checkbox for each user, and a message to select something if no user is selected.
<div ng-app="project" ng-controller="UsersController">
<input type="checkbox" ng-repeat="user in users" ng-model="user.done" id="selectUser{{$index}}" />
<div ng-hide="selectedUsers().length">Select something</div>
</div>
The code works, but it looks kind of ugly.
Is there a less procedural way of getting to the same result?
You can simply filter down the set of users based on the done property and check the length of it.
<div ng-hide="(users | filter:{done:true}).length">Select something</div>
angular.module('project', [])
.controller('UsersController', ['$scope', function ($scope) {
$scope.users = [
{ text: 'User 1', done: true, extension: 123 },
{ text: 'Another user', done: false, extension: 456 }];
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="project" ng-controller="UsersController">
<input type="checkbox" ng-repeat="user in users" ng-model="user.done" id="selectUser{{$index}}" />
<div ng-hide="(users | filter:{done:true}).length">Select something</div>
</div>
The way you have it implemented is rather inefficient as the selectedUsers function, which iterates over all users, runs on every digest. It might be acceptable if you a small number of users.
For completeness sake, however, another approach would be to calculate the number of user.done === true and keep track of the count (rather than re-count):
$scope.users = [
{ text: 'User 1', done: true, extension: 123 },
{ text: 'Another user', done: false, extension: 456 }];
$scope.selection = {count: 0};
for (var i=0; i<$scope.users.length; i++){
if ($scope.users[i].done) $scope.selection.count++;
}
And amend selection.count on each ng-change of selection:
<div ng-app="project" ng-controller="UsersController">
<input type="checkbox" ng-repeat="user in users"
ng-model="user.done"
ng-change="selection.count = selection.count + user.done * 2 - 1">
<div ng-hide="selection.count">Select something</div>
</div>
(Note, that it had I had to use an object selection that holds that property count; just using $scope.selectionCount would not have worked due to how prototypical inheritance works and the fact that ng-repeat creates a child scope)

Remove object from array when attribute 0 or below - Angular

I have a controller that lets a user edit an object. Part of this decrements an attribute of the object – the quantity of my model. When the model's quantity reaches 0 or below, ideally I'd like to delete the whole object.
HTML
<div ng-app='basket'>
<div ng-controller='BasketController as basket'>
<div class='product' ng-repeat='product in cart.products'>{{ product.name }} Count: {{ product.quantity }} <a ng-click='product.quantity = product.quantity - 1'>Remove</a></div>
</div>
</div>
JS
(function(){
var app = angular.module('basket', []);
var cart;
app.controller('BasketController', function($scope, $http){
$scope.getTimes=function(n){
return new Array(n);
};
$scope.cart = {};
$scope.cart.products = [{
'name':'item 1',
'quantity':3
},{
'name':'item 2',
'quantity':3
},{
'name':'item 3',
'quantity':3
}];
});
})();
Live demo
http://codepen.io/EightArmsHQ/pen/bNBmXm
So for instance, in the above if you click 'remove' again and again on the first object, when you get to 0 I'd like to just have an array like the following:
$scope.cart.products = [{
'name':'item 2',
'quantity':3
},{
'name':'item 3',
'quantity':3
}];
You could just write a remove method to check for quantity and remove the item from the list.
In your controller:-
$scope.remove = function(product){
var products = $scope.cart.products;
product.quantity -= 1;
if(!product.quantity){
/*Splice the object from the array based on the index*/
products.splice(products.indexOf(product), 1);
}
}
and on click just call it as:
<a ng-click='remove(product)'>Remove</a>
Demo
Change your link to:
<a ng-click='remove(product)'>Remove</a>
And add the following method to the controller:
$scope.remove = remove;
function remove(product) {
$scope.cart.products.splice($scope.cart.products.indexOf(product), 1);
}

Categories