Angular best practices for splitting components - angularjs

So i have this simple listing of system users (attached screenshot), client can edit the users or create new user and a new panel is displayed accordingly.
What I'm doing is updating the different fields of the edit/create form depending on the user action. But since im coming from a Backbone background, the usual way I would do something like this is to create a new view, initialize it with a parameter indicating whether it's a create or edit mode, and then destroy this view when clicking on cancel/save changes/create user. But right now im just depending on angular event binding to update the same form elements, is this the proper way to do it in angular?
$scope.prepare_form = function (user_id) {
if (user_id) {
// edit mode
var user = filterFilter($scope.users, { id: user_id })[0];
$scope.fdata_create.name = user.name;
$scope.fdata_create.email = user.email;
$scope.fdata_create.role_name = user.role_name;
$scope.fdata_create.id = user.id;
} else {
// create mode
$scope.fdata_create.name = '';
$scope.fdata_create.email = '';
$scope.fdata_create.role_name = '';
$scope.fdata_create.id = '';
}
$scope.form_create_open = true;
}

You should think based on Models rather than forms. You should create Teammate model with its sync methods, update, create, replace, etc. It is cleaner, here is example extract from this tutorial
Book html form.
<div ng-controller="BookController">
<div ng-style="{ backgroundImage: 'url(' + book.getImageUrl(100, 100) + ')' }"></div>
Id: <span ng-bind="book.id"></span>
<br/>
Name:<input type="text" ng-model="book.name" />
<br/>
Author: <input type="text" ng-model="book.author" />
<br/>
Is Available: <span ng-bind="book.isAvailable() ? 'Yes' : 'No' "></span>
<br/>
<button ng-click="book.delete()">Delete</button>
<br/>
<button ng-click="book.update()">Update</button>
</div>
Book Model define as factory.
app.factory('Book', ['$http', function($http) {
function Book(bookData) {
if (bookData) {
this.setData(bookData):
}
// Some other initializations related to book
};
Book.prototype = {
setData: function(bookData) {
angular.extend(this, bookData);
},
load: function(id) {
var scope = this;
$http.get('ourserver/books/' + bookId).success(function(bookData) {
scope.setData(bookData);
});
},
delete: function() {
$http.delete('ourserver/books/' + bookId);
},
update: function() {
$http.put('ourserver/books/' + bookId, this);
},
getImageUrl: function(width, height) {
return 'our/image/service/' + this.book.id + '/' + width + '/' + height;
},
isAvailable: function() {
if (!this.book.stores || this.book.stores.length === 0) {
return false;
}
return this.book.stores.some(function(store) {
return store.quantity > 0;
});
}
};
return Book;
}]);
Book controller that uses Book Model
app.controller('BookController', ['$scope', 'Book', function($scope, Book) {
$scope.book = new Book();
$scope.book.load(1);
}]);
If you want to update book name for example, you dont need to manually assign the new name because the Book's name instance is binded to ng-model attribute. To update it just only need call your update function from Book Factory.
$scope.book = new Book();
if(book_id){
$scope.book.load(book_id);
$scope.submit_book = $scope.book.update // submit_book function need to be triggered after submit the form. Here it
}
else{
$scope.submit_book = $scope.book.create // submit_book function need to be triggered after submit the form. Here it will create the book
}

I would reuse the same form, but bind to different models:
app.controller('ctrl', function($scope){
$scope.addUser = function(){
$scope.add= true;
$scope.user = {};
};
$scope.editUser =function(user){
$scope.add=false;
$scope.user ={};
angular.copy($scope.user, user);
};
$scope.onOK = function(user){
...
angular.copy(user, $scope.user);
};
});
HTML:
<div ng-controller="ctrl">
<form>
<input type ="text" ng-model="user.name" />
<input type="text" ng-model="user.role" />
<button ng-click="onOK(user)"> ok </button>
</form>
</div>

Related

Angular two way data binding inside directive

I have a problem with understanding how two way data binding works inside directive. Seems it's working only one way cause when i update value by typing directly in input field model is not being updated even though the datasource is set with '='. When i click change item button model is also updated. Why typing inside input fields doesn't update the model?
Also if factories/services are singletons why i'am able to create new instances of Item by clicking the button and each one of the holds separate value?
I wasn't sure if those two problems are related to each other or not that's why asking it this way. PLUNKR
angular.module('singletonServicesTest', [])
.controller('pageCtrl', function(Item) {
vm = this;
vm.items = [];
vm.addItem = function() {
console.log('changeItem');
var item = new Item();
vm.items.push(item);
};
vm.addItemWithContent = function() {
var item = new Item('some content');
vm.items.push(item);
};
vm.changeItem = function() {
console.log('change');
vm.items[0].content = Math.random();
};
})
.factory('Item', function() {
function Item(data) {
if (data) {
this.content = data;
}
}
Item.prototype.content = '';
return Item;
})
.directive('itemDirective', function() {
return {
scope: {
datasource: '='
},
template: '<div><input value="{{datasource.content}}" /></div>'
}
});
<body ng-app="singletonServicesTest" ng-controller="pageCtrl as page">
<h1>Hello Plunker!</h1>
<button ng-click="page.addItem()">Add item</button>
<button ng-click="page.addItemWithContent()">Add item with content</button>
<button ng-click="page.changeItem()">change item</button>
<div ng-repeat="item in page.items">
<item-directive datasource="item"></item-directive>
</div>
{{page.items}}
</body>

View results of a search in the same View AngularJS

First sorry for my bad english. I'm developing an app to be able to study and was putting everything on the controller, all queries and all. But then I decided to pass it to a service (vi which is the right to do so), only when I queries for my search service just stopped working.
In the template I list all registered categories and it has an input, which makes the search all registered establishments in the DB. I wish that when you do a search, the list simply update with a new, only listing the results of this search.
When I try to do a search, the following error appears:
Object {message: "sqlite3_bind_null failure: bind or column index out of range", code: 0}
And so are all related to this template code and the search:
Service.js
app.service('Market', function($cordovaSQLite, DBA) {
var self = this;
self.allCategories = function() {
return DBA.query("SELECT id, category_name FROM tblCategories")
.then(function(result){
return DBA.getAll(result);
});
}
self.searchAll = function(nameSearch) {
var parameters = [nameSearch];
return DBA.query("SELECT id, place_name FROM tblPlaces WHERE place_name LIKE '%(?)%'", parameters)
.then(function(result) {
return DBA.getAll(result);
});
}
return self;
})
Controller.js
app.controller('CategoriesCtrl', function($scope, Market) {
$scope.categories = [];
$scope.categories = null;
$scope.items = [];
$scope.items = null;
var nameSearch = '';
$scope.searchkey = '';
$scope.myClick = function (search) {
nameSearch = search;
console.log(nameSearch);
$scope.searchResult = function(nameSearch) {
Market.searchAll(nameSearch).then(function(items) {
$scope.items = items;
console.log(items);
});
}
$scope.searchResult();
};
$scope.listAllCategories = function() {
Market.allCategories().then(function(categories) {
$scope.categories = categories;
});
}
$scope.listAllCategories();
})
Categories.html
<div class="bar bar-header item-input-inset">
<label class="item-input-wrapper">
<i class="icon ion-ios-search placeholder-icon"></i>
<input type="text" placeholder="Search" name="key" autocorrect="off" ng-model="searchkey">
</label>
<button class="button" ng-click="myClick(searchkey)">Buscar</button>
</div>
<ion-list>
<ion-item ng-repeat="category in categories">{{category.category_name}}</ion-item>
</ion-list>

AngularFire $remove item from Array using a variable in Firebase reference does not work

I've been struggling with the following problem:
I'm trying to delete a 'Post' item from a Firebase Array with the $remove AngularFire method which I have implemented in a Angular Service (Factory). This Post is a child of 'Event', so in order to delete it I have to pass this Service a argument with the relevant Event of which I want to delete the post.
This is my controller:
app.controller('EventSignupController', function ($scope, $routeParams, EventService, AuthService) {
// Load the selected event with firebase through the eventservice
$scope.selectedEvent = EventService.events.get($routeParams.eventId);
// get user settings
$scope.user = AuthService.user;
$scope.signedIn = AuthService.signedIn;
// Message functionality
$scope.posts = EventService.posts.all($scope.selectedEvent.$id);
$scope.post = {
message: ''
};
$scope.addPost = function (){
$scope.post.creator = $scope.user.profile.username;
$scope.post.creatorUID = $scope.user.uid;
EventService.posts.createPost($scope.selectedEvent.$id, $scope.post);
};
$scope.deletePost = function(post){
EventService.posts.deletePost($scope.selectedEvent.$id, post);
// workaround for eventService bug:
// $scope.posts.$remove(post);
};
});
And this is my Service (Factory):
app.factory('EventService', function ($firebase, FIREBASE_URL) {
var ref = new Firebase(FIREBASE_URL);
var events = $firebase(ref.child('events')).$asArray();
var EventService = {
events: {
all: events,
create: function (event) {
return events.$add(event);
},
get: function (eventId) {
return $firebase(ref.child('events').child(eventId)).$asObject();
},
delete: function (event) {
return events.$remove(event);
}
},
posts: {
all: function(eventId){
var posts = $firebase(ref.child('events').child(eventId).child('posts')).$asArray();
return posts;
},
createPost: function (eventId, post) {
// this does work
var posts = $firebase(ref.child('events').child(eventId).child('posts')).$asArray();
return posts.$add(post);
},
deletePost: function (eventId, post) {
// this does not work
var posts = $firebase(ref.child('events').child(eventId).child('posts')).$asArray();
return posts.$remove(post);
}
}
};
return EventService;
});
When I try to delete the link tag just freezes and no error logging appears in the console. While if I call $remove on my $scope.posts directly in my controller it magically works.. Furthermore my Post is not removed from my Firebase DB.
Another weird thing is that 'CreatePost' works perfectly fine using the same construction.
My view:
<div class="col-xs-8 col-xs-offset-2 well">
<form ng-submit="addPost()" ng-show="signedIn()">
<input type="text" ng-model="post.message" />
<button type="submit" class="btn btn-primary btn-sm">Add Post</button>
</form>
<br>
<div class="post row" ng-repeat="post in posts">
<div>
<div class="info">
{{ post.message }}
</div>
<div>
<span>submitted by {{ post.creator }}</span>
delete
</div>
<br>
</div>
</div>
</div>
P.s. I'm not too sure that my 'Service' is implemented in the best possible way.. I couldn't find another solution for doing multiple firebase calls
var posts = $firebase(ref.child('events').child(eventId).child('posts')).$asArray();
within the Post part of my EventService, because it depends on eventId in each CRUD operation. Any ideas would be very welcome :)
The easiest way for me was to use this:
var ref= new Firebase('https://Yourapp.firebaseio.com/YourObjectName');
ref.child(postId).remove(function(error){
if (error) {
console.log("Error:", error);
} else {
console.log("Removed successfully!");
}
});
The only way I'm able to remove the item is using a loop on the array we get from firebase.
var ref= new Firebase('https://Yourapp.firebaseio.com/YourObjectName');
var arr_ref=$firebaseArray(ref);
for(var i=0;i<arr_ref.length;i++){
if(key==arr_ref[i].$id){
console.log(arr_ref[i]);
arr_ref.$remove(i);
}
}

Passing data from ionic/angular modal using separate template html file

I'm developing a simple Ionic mobile app although the answer likely lies with Angular. The app is really simple, displays a list of employees with an Add button which displays a modal, lets the user enter some details, click Save and it's persists the data to a back-end Firebase store. It has 1 controller and a simple service. Initially I had the template html for the modal inside script tags inside the index.html and it all worked fine. When I decided to structure things out and put the modal template in a separate html file, suddenly the data object assigned to ng-modal via the input boxes no longer passes any data to the event handler to save the data, instead it's always undefined. Everything else works as it should, the modal displays ok, the event handlers are calling the right functions etc. The only change is moving the input template to a separate file. I know it's likely something really simple but can't for the life of me work out why and can't find any info about it anywhere else.
Template HTML file for the modal :
<ion-list>
<h1>Add Employee</h1>
<div class="list list-inset">
<ion-item>
<label class="item item-input">
<input type="text" placeholder="Employee Name" ng-model="data.employeeName">
</label>
<label class="item item-input">
<input type="text" placeholder="Employee Age" ng-model="data.employeeAge">
</label>
</ion-item>
<button class="button button-outline button-block button-balanced"
ng-click="addEmployee(true, data)">
Save & Add Another
</button>
<button class="button button-outline button-block button-positive"
ng-click="addEmployee(false, data)">
Save
</button>
<button class="button button-outline button-block button-assertive"
ng-click="closeAddModal()">
Cancel
</button>
</ion-list>
</ion-modal-view>
addEmployee event - data parameter is now always undefined. Worked fine with embedded template :
$scope.addEmployee = function(retainModal, data) {
var employee = {employeeName:data.employeeName,
employeeAge:data.employeeAge};
employeeService.saveEmployee(employee);
if (! retainModal) {
$scope.closeAddModal();
};
data.employeeName = "";
data.employeeAge = "";
};
Based on this question and other needs I create a service that can be useful.
See this post: Ionic modal service or see in operation: CodePen
(function () {
'use strict';
var serviceId = 'appModalService';
angular.module('app').factory(serviceId, [
'$ionicModal', '$rootScope', '$q', '$injector', '$controller', appModalService
]);
function appModalService($ionicModal, $rootScope, $q, $injector, $controller) {
return {
show: show
}
function show(templateUrl, controller, parameters) {
// Grab the injector and create a new scope
var deferred = $q.defer(),
ctrlInstance,
modalScope = $rootScope.$new(),
thisScopeId = modalScope.$id;
$ionicModal.fromTemplateUrl(templateUrl, {
scope: modalScope,
animation: 'slide-in-up'
}).then(function (modal) {
modalScope.modal = modal;
modalScope.openModal = function () {
modalScope.modal.show();
};
modalScope.closeModal = function (result) {
deferred.resolve(result);
modalScope.modal.hide();
};
modalScope.$on('modal.hidden', function (thisModal) {
if (thisModal.currentScope) {
var modalScopeId = thisModal.currentScope.$id;
if (thisScopeId === modalScopeId) {
deferred.resolve(null);
_cleanup(thisModal.currentScope);
}
}
});
// Invoke the controller
var locals = { '$scope': modalScope, 'parameters': parameters };
var ctrlEval = _evalController(controller);
ctrlInstance = $controller(controller, locals);
if (ctrlEval.isControllerAs) {
ctrlInstance.openModal = modalScope.openModal;
ctrlInstance.closeModal = modalScope.closeModal;
}
modalScope.modal.show();
}, function (err) {
deferred.reject(err);
});
return deferred.promise;
}
function _cleanup(scope) {
scope.$destroy();
if (scope.modal) {
scope.modal.remove();
}
}
function _evalController(ctrlName) {
var result = {
isControllerAs: false,
controllerName: '',
propName: ''
};
var fragments = (ctrlName || '').trim().split(/\s+/);
result.isControllerAs = fragments.length === 3 && (fragments[1] || '').toLowerCase() === 'as';
if (result.isControllerAs) {
result.controllerName = fragments[0];
result.propName = fragments[2];
} else {
result.controllerName = ctrlName;
}
return result;
}
} // end
})();
Usage:
appModalService
.show('<templateUrl>', '<controllerName> or <controllerName as ..>', <parameters obj>)
.then(function(result) {
// result from modal controller: $scope.closeModal(result) or <as name here>.closeModal(result) [Only on template]
}, function(err) {
// error
});
You can use another service to centralize the configuration of all modals:
angular.module('app')
.factory('myModals', ['appModalService', function (appModalService){
var service = {
showLogin: showLogin,
showEditUser: showEditUser
};
function showLogin(userInfo){
// return promise resolved by '$scope.closeModal(data)'
// Use:
// myModals.showLogin(userParameters) // get this inject 'parameters' on 'loginModalCtrl'
// .then(function (result) {
// // result from closeModal parameter
// });
return appModalService.show('templates/modals/login.html', 'loginModalCtrl as vm', userInfo)
// or not 'as controller'
// return appModalService.show('templates/modals/login.html', 'loginModalCtrl', userInfo)
}
function showEditUser(address){
// return appModalService....
}
}]);
You need to attach your models to the scope:
$scope.data.employeeName = "";
$scope.data.employeeAge = "";
...and similar every time you reference them.

AngularJS - ng-click does not remove previous click's modifications when clicked again

So I am trying to acomplish this example: http://jsfiddle.net/pkozlowski_opensource/WXJ3p/15/
However, for some reason, when I click on one div, and then on another, it does not remove the "active" class from the previous div so it highlights both , hence all my divs end up with the class active if I click all of them. I want to make it to where it will actually remove the class if I click anywhere else on the body and also if I click on any other div, like the fiddle example.
My jsFIddle
http://jsfiddle.net/GeorgiAngelov/jUj56/4/
<div ng-controller="MyCtrl">
<button class="addQuestionButton btn btn-primary" ng-click="AddRootQuestion(questions)">Add node</button>
<div ng-repeat="question in questions" ng-include="question"> </div>
<script type="text/ng-template" id="question">
<!-- Single question starts here -->
<div ng-controller="QuestionController" ng-class="{active : isSelected(question)}">
<button class="btn btn-primary" ng-click="AddSubQuestion(question)">Add node</button>
<button class="btn btn-primary" ng-click = "editQuestion(question)">Edit Question</button>
</div>
<div ng-repeat="question in question.nodes" ng-include="question">
</div>
</script>
</div>
Since each single question has its own QuestionController, and QuestionControlleris where $scope.selected is being set, they don't interact with each other. That is to say, when you click edit, it sets selected for that individual controller.
The easy way to fix it would be to set a property on a parent scope (the containing controller) when clicking edit:
function MyCtrl($scope) {
$scope.questions = [];
$scope.AddRootQuestion = function(questions) {
questions.push({name: 'Question', nodes: []});
};
// added this method --v
$scope.setSelected = function(q) {
$scope.selected = q;
};
}
function QuestionController($scope) {
$scope.choices = [];
$scope.choice = {text: ''};
$scope.AddSubQuestion = function(question, $element) {
var newName = 'Name of question goes here';
question.nodes.push({name: newName, id: 'it goes in here', nodes: []});
};
// modified this method --v
$scope.editQuestion = function(question){
$scope.setSelected(question);
};
$scope.isSelected = function(question) {
return $scope.selected === question;
};
}
But this makes QuestionController dependent upon a parent controller. A much better design would be to move all the data and data manipulation methods into a service:
var myApp = angular.module('myApp',[]);
myApp.factory('Question', function() {
return {
questions: [],
addRootQuestion: function() {
this.questions.push({name: 'Question', nodes: []});
},
addSubQuestion: function(question, data) {
question.nodes.push(data);
},
setSelectedQuestion: function(question) {
this.selectedQuestion = question;
},
isSelectedQuestion: function(question) {
return this.selectedQuestion == question;
}
};
});
You can then inject the service into your controllers:
function MyCtrl($scope, Question) {
$scope.questions = Question.questions;
$scope.AddRootQuestion = function() {
Question.addRootQuestion();
};
}
function QuestionController($scope, Question) {
$scope.choices = [];
$scope.choice = {text: ''};
$scope.AddSubQuestion = function(question, $element) {
var newName = 'Name of question goes here';
Question.addSubQuestion(question, {
name: newName, id: 'it goes in here', nodes: []
});
};
$scope.editQuestion = function(question){
Question.setSelectedQuestion(question);
};
$scope.isSelected = function(question) {
return Question.isSelectedQuestion(question);
};
}
Example: http://jsfiddle.net/BinaryMuse/pZkBv/
If you wanted, you could build up a richer data model using JavaScript OOP principles; for example, you could have Question instances with methods for manipulating the questions, which themselves live inside an instance of QuestionContainer (or Survey or Test or whatever).

Resources