Angular 1 - Sharing data between parent and child controller - Need advice - javascript

Im working on 2 apps that will simply display all active incidents and then all closed incidents. The 2 apps share similar logic when it comes to create, modify, save and delete.
So im trying to figure the best aproach to share the CRUD logic between the 2 applications. I tought maybe it would be best to set a parent - child controller setup like so:
Common_CRUD_file.js:
var Common_Application = .module('Common_Application ',[app, app1, app2, app3])
Parent_controller.controller('Parent_controller'), function ($scope, $http, $filter, $timeout) {
//all my CRUD logic goes in here
$scope.edit = function(data) { //edit logic goes here }
$scope.save = function(data) { //save logic goes here }
$scope.cancel = function(data) { //cancel logic goes here }
$scope.delete = function(data) { //delete logic goes here }
}
Child_Show_closed_incidents.js:
var Common_Application = angular.module('Common_Application');
Child_controller.controller('Child_controller'), function ($scope, $http, $filter, $timeout) {
//All of the app logic goes here
$scope.get_data = function(data) { //store fetched ajax data in $scope.All_closed_incidents }
}
Quick excerpt of the HTML file:
<div ng-app="Common_Application" ng-controller="Parent_controller">
<div ng-controller="Child_controller">
<table directive_for_angular_app_here>
<tr>
<td></td>
<td>Description</td>
<td>Status</td>
</tr>
<tr ng-repeat="incident in All_closed_incidents">
<td><button type="button" ng-click="edit(incident)">Edit</button></td>
<td>{{incident.Description}}</td>
<td>{{incident.Status}}</td>
</tr>
</div>
</div>
So this setup is able to load my table but the edit function dosent seem to fire at all when I click on the button. No errors in the console either. Seems to ignoring my Parent functions all together when I was expecting it to share all of its scopes. Would anyone have a better aproch to this?

I would discard the parent/child setup you have. Turn the parent into a service with functions:
//all my CRUD logic goes in here
$scope.edit = function(data) { //edit logic goes here }
$scope.save = function(data) { //save logic goes here }
$scope.cancel = function(data) { //cancel logic goes here }
$scope.delete = function(data) { //delete logic goes here }
then inject that service into the child controller and call the functions. Reduces complexity and enhances reusability.

Related

Angular: Best practice for updating a variable from a function?

Within an ng-repeat block I have textboxes. To detect when the content differs from the original, the original data is stored in a variable.
<tr data-ng-repeat="p in products">
<td>
<textarea data-elastic data-ng-model="p.comment" data-ng-change="hasChanged=checkChange(original, rnd.comment);" data-ng-init="original=rnd.comment; hasChanged=false"></textarea>
</td>
<td class="save" ng-show="hasChanged" ng-click="save(p, original)">Save</td>
A save button is shown only when the content has changed. After a successful save the original value should be updated to the new value.
I can do it like this with a function in the controller:
$scope.save = function (p, original) {
//...successful save
this.original = p.comment; //this works
original = p.comment; //this does not
}
Relying on some implicit scope in the form of 'this' doesn't seem sensible.
Why doesn't updating the variable (original = ...) work? What's a smarter way to do this?
Based on comments I've updated it as follows:
ng-click="save(p, this)"
$scope.save = function (p, scope) {
//...successful save
scope.original = p.comment; //this works
}
This seems failrly sensible now. Is passing scope around like this considered bad practice or acceptable?
Products is defined as follows:
productStatusApp.controller('productStatusCtrl', function ($scope, $http, cid) {
$http.get('api/company/products/' + cid).success(function (data) {
$scope.products = data.products;
});
I've found the best way to avoid this kind of problems, is to use services
https://docs.angularjs.org/guide/services
http://viralpatel.net/blogs/angularjs-service-factory-tutorial/
some rough code(use it just for pointers, not tested at all)
<tr data-ng-repeat="p in ProductsService.products">
<td>
<textarea data-elastic data-ng-model="p.comment"></textarea>
</td>
<td class="save" ng-show="p.originalComment!==p.comment" ng-click="ProductsService.save(p)">Save</td>
</tr>
and
var module = angular.module('app', []);
module.service('ProductsService', function () {
var products = [postA,postB,...,PostC];
products = products.map(function(p){p.originalComment=p.comment});
var save = function(p){
p.originalComment=p.comment;
someAjaxRequest(function _callback(err,response){....})
}
return {products:products,save:save};
});
and
module.controller('ProductsController', function ($scope, ProductsService) {
$scope.ProductsService= ProductsService;
});
They also allow better readability , WIN WIN

Can't get the datas in angularJs

I have html page like
<div ng-controller="userListControl">
...
</div>
<div ng-controller="userDetailsControl">
....
</div>
And i have angular Js code is
var userDirectory = angular.module('userDirectory',[]);
userDirectory.controller("userListControl", ['$scope','$http', function($scope, $http)
{
$http.get('data/userData.json').success (function(data){
$scope.users = data;
$scope.users.doClick = function(user,event) {
userInfo(user);
}
});
}]);
function userInfo(users)
{
console.log(user);
userDirectory.controller("userDetailsControl", function($scope)
{
console.log('well')
$scope.user = users;
console.log($scope.user)
});
}
Here Everything is working fine. But when we are calling click event, That userInfo called with particular Data. But Second controller gives an error(angular js Error).
I am new one in angular jS. I dont know this logic is correct or not.
I have list items in first Controller. When we are clicking on list, It gets data from particular list and passed to another design. That design have detailed data. So the 2nd controller shows particular list detailed Section
First, There is no need to declare your controller inside a function - I don't think that you're trying to lazy-load controllers. Make it available to your app when it starts.
Second, you need to pass data to the userDetailsControl controller. There are various ways to do this, but here you could just use the $rootScope.
var userDirectory = angular.module('userDirectory',[]);
userDirectory.controller("userListControl", function($scope, $rootScope, $http)
{
$scope.selectUser = function(user){
$rootScope.selectedUser = user;
}
$http.get('data/userData.json')
.success (function(data){
$scope.users = data;
});
})
.controller("userDetailsControl", function($scope, $rootScope){
$rootScope.$watch("selectedUser", function(newVal){
$scope.user = newVal;
}
}
and in your HTML:
<div ng-controller="userListControl">
<button ng-repeat="user in users" ng-click="selectUser(user)">{{user.name}}</button>
</div>
<div ng-controller="userDetailsControl">
<div>{{user.name}}</div>
<div>{{user.otherDetails}}</div>
</div>

Angular JS | Passing data to 2 differents controllers on click

I've got some projects displayed in divs via ng-repeat.
Each div contains a link with the id of the project.
Basically I want to bind my project id on ng-click in order to update a factory. This will allow me to share the clicked project's id with another controller who will load the project details data only.
Here is my projects view:
<div ng-repeat="detail in projects.details">
<a ng-href="#!/project/project-details" ng-click="setId(detail.project_id)"><span></span></a>
</div>
Here is my factory:
app.factory('getGoodIdProjectDetails', function (){
var savedId = 1; //Default à 1
return {
getId : function () {
return savedId;
},
setId:function(idGet){
savedId = idGet;
return savedId;
}
}
});
Here are my controllers:
function ProjectCtrl($scope, $http, $timeout, getGoodIdProjectDetails) {
$scope.setId = function (idGet) {
$scope.idProjectDetails = getGoodIdProjectDetails.setId(idGet);
};
}
function ProjectDetailsCtrl($scope, $http, $timeout, getGoodIdProjectDetails) {
$scope.idProjectDetails = getGoodIdProjectDetails.getId();
}
In my view the console is displaying an error like I can't bind ng-click this way, but when I inspect the view it's sorting me the good thing, like ng-click="setId(8)"
I want the factory to update with the good id on click. Then I want to access this id in projectDetailsCtrl in order to load the project.
|| EDIT || : I changed the ng-click, that works fine, everything's good. Thx all
try this:
<div ng-repeat="detail in projects.details">
<a ng-href="#!/project/project-details" ng-click="setId(detail.project_id)"><span>Voir le projet ></span></a>
</div>
you don't need to use binding expression {{}} to pass values to functions

Populate jQuery UI accordion after AngularJS service call

I'm currently trying to build an AngularJS app where I'm using a jQuery UI accordion control.
The problem is, that the jQuery UI accordion is initiated before my AngularJS service is done loading data from the server. In other words: the accordion doesn't have any data when it's initiated and thus does not show when the data from AngularJS is populated.
The view looks like this:
<!-- Pretty standard accordion markup omitted -->
$("#b2b-line-accordion").togglepanels();
My AngularJS controller looks like this:
app.controller('orderController', function ($scope, orderService, userService) {
// Constructor for this controller
init();
function init() {
$scope.selected = {};
$scope.totalSum = 0.00;
$scope.shippingDate = "";
$scope.selectedShippingAddress = "";
$scope.orderComment = "";
$scope.agreements = false;
$scope.passwordResetSuccess = false;
$scope.passwordResetError = true;
userService.getCurrentUser(2).then(function (response) {
$scope.user = response.data;
orderService.getProductCategoriesWithProducts($scope.user).then(function (d) {
$scope.categories = d.data;
});
});
}
// Other methods omitted
});
And my AngularJS services looks like this:
app.service('orderService', function ($http) {
this.getProductCategoriesWithProducts = function (user) {
return $http.post('url to my service', user);
};
});
app.service('userService', function ($http) {
this.getCurrentUser = function(companyId) {
return $http.get('url to my service' + companyId + '.aspx');
};
this.resetPassword = function() {
return true;
};
});
Is there any way to tell the accordion to "wait" to initialise until the data is returned from the service? :-)
Thanks in advance!
Update
I tried chaining the methods and added some logging and it seems that the accordion is in fact initiated after the JSON is returned from the service.
userService.getCurrentUser(2).then(function(response) {
$scope.user = response.data;
}).then(function() {
orderService.getProductCategoriesWithProducts($scope.user).then(function(d) {
$scope.categories = d.data;
console.log("categories loaded");
}).then(function () {
$("#b2b-line-accordion").accordion();
console.log("accordion loaded");
});
});
However, it doesn't display the accordion :-( The first accordion div looks fine in the generated DOM:
<div id="b2b-line-accordion" class="ui-accordion ui-widget ui-helper-reset" role="tablist">
...
</div>
But the rest of the markup (which is databound with angular) itsn't initiated.
Complete markup:
<div id="b2b-line-accordion">
<div ng-repeat="productCategory in categories">
<h3>{{ productCategory.CategoryName }}</h3>
<div class="b2b-line-wrapper">
<table>
<tr>
<th>Betegnelse</th>
<th>Str.</th>
<th>Enhed</th>
<th>HF varenr.</th>
<th>Antal</th>
<th>Bemærkninger</th>
<th>Beløb</th>
</tr>
<tr ng-repeat="product in productCategory.Products">
<td>{{ product.ItemGroupName }}</td>
<td>{{ product.ItemAttribute }}</td>
<td>
<select ng-model="product.SelectedVariant"
ng-options="variant as variant.VariantUnit for variant in product.Variants"
ng-init="product.SelectedVariant = product.Variants[0]"
ng-change="calculateLinePrice(product); calculateTotalPrice();">
</select>
</td>
<td>{{ product.ItemNumber }}</td>
<td class="line-amount">
<span class="ensure-number-label" ng-show="product.IsNumOfSelectedItemsValid">Indtast venligst et tal</span>
<input type="number" class="line-amount" name="amount" min="0" ng-change="ensureNumber(product); calculateLinePrice(product); calculateTotalPrice();" ng-model="product.NumOfSelectedItems" value="{{ product.NumOfSelectedItems }}" />
<td>
<input type="text" name="line-comments" ng-model="product.UserComment" value="{{ product.UserComment }}" /></td>
<td><span class="line-sum">{{ product.LinePrice | currency:"" }}</span></td>
</tr>
</table>
</div>
</div>
</div>
SOLUTION
Finally I found a way around this! I'm not entirely sure if it's that pretty and if it's the Angular-way of doing stuff (I guess it isn't)
Made a directive with the following code:
app.directive('accordion', function () {
return {
restrict: 'A',
link: function ($scope, $element, attrs) {
$(document).ready(function () {
$scope.$watch('categories', function () {
if ($scope.categories != null) {
$element.accordion();
}
});
});
}
};
});
So basically when the DOM is ready and when the categories array changes (which it does when the data has been loaded), I'm initiating the jQuery UI accordion.
Thanks a lot t #Sgoldy for pointing me in the right direction here!
Yes you need a directive and you can handle this more angular way !
In HTML define the directive
<div ui-accordion="accordionData" ></div>
Return promise from your service and pass the promise to the directive.
In controller
$scope.accordionData = myService.getAccordionData();
The ui-accordion directive looks like
.directive('uiAccordion', function($timeout) {
return {
scope:{
myAccordionData: '=uiAccordion'
},
template: '<div ng-repeat="item in myData"><h3 ng-bind="item.title"></h3><div><p ng-bind="item.data"></p></div></div>',
link: function(scope, element) {
scope.myAccordionData.then(function(data) {
scope.myData = data;
generateAccordion();
});
var generateAccordion = function() {
$timeout(function() { //<--- used $timeout to make sure ng-repeat is REALLY finished
$(element).accordion({
header: "> div > h3"
});
});
}
}
}
})
When your service call succeed then you create your accordion. Here you can define your own accordion-template like
<div ng-repeat="item in myData">
<h3 ng-bind="item.title"></h3>
<div>
<p ng-bind="item.data"></p>
</div>
</div>
Template binds with your model data myData. I use ng-repeat inside the template to create accordion-header and accordion-body HTML.
In the generateAccordion method i use $timeout to make sure the ng-repeat is really finished rendering because $timeout will execute at the end of the current digest cycle.
Check the Demo
My best practice is to resolve your asynchronous services before controller is initiated.
As you can see in the document, http://docs.angularjs.org/api/ngRoute.$routeProvider
resolve - {Object.=} - An optional map of
dependencies which should be injected into the controller. If any of
these dependencies are promises, the router will wait for them all to
be resolved or one to be rejected before the controller is
instantiated. If all the promises are resolved successfully, the
values of the resolved promises are injected and $routeChangeSuccess
event is fired. If any of the promises are rejected the
$routeChangeError event is fired.
Your controller and view won't be even started before your service is resolved or rejected.
There is a good video tutorial about this, https://egghead.io/lessons/angularjs-resolve
In your case, you can config routes like the following
var myApp = angular.module('myApp', ['ngRoute']);
myApp.config(function($routeProvider) {
$routeProvider.when('/', {
templateUrl: 'main.html',
controller: orderController,
resolve: {
categories: function(orderService) {
return orderService.getProductCategoriesWithProducts();
},
user: function(userService) {
return userService.getCurrentUser();
}
}
});
Then, with your controller
app.controller('orderController', function($scope, categories, user) {
//categories and user is always here, so use it.
});
I have also found a similar question and answer here

Angularjs pass data in between services that exist on different pages

I have a simple book store example that I am working through for angularjs and I am trying to pass a book id from a home page into a service on an edit page so that the book details can be rendered. What I have happen is I can see the rest call being hit from my home' page with the correct book id being passed into the book service. However, I cannot seem to think of a way to have theBookCtrl` load that data when a different page invokes the rest service. The order I am expecting is:
1) User enters a book ID to edit
2) User presses Search button
3) book.html page is loaded
4) BookEdit service is invoked with ID from Steps 1 and 2
5) ng-model for book loads data.
Apologies in advance, there may be some errors as I was modifying this code from a different computer, so I couldn't copy/paste
code below:
home.html
<div ng-controller="HomeCtrl">
<div>
<label for="query">Book to edit</label>
<input id="query" ng-model ="editBook.query">
<button ng-click="loadBookById()">Search</button>
</div>
</div>
home.js:
var homeApp = angular.module('bookHome',['bookEdit']);
homeApp.controller('HomeCtrl',function($scope,$http,bookEditService)
{
$http.get('http://get/your/books/rest').success(function(data){
$scope.library = data;
});
$scope.editBook = {
query: '',
service:'bookEditService'
} ;
$scope.loadBookById = function()
{
$scope.$emit('loadBookById',{
query:$scope.editBook.query,
$service: $scope.editBook .service
}
$scope.$on('loadBookById', function(ev,search){
bookEditService.loadBook({
bookId: $scope.editBook.query
},
$scope.searchComplete,
$scope.errorSearching
);
});
$scope.searchComplete = function(results) {
$scope.results = results;
};
$scope.errorSearch= function(data,status,headers,config){
console.log(data);
// ...
};
}
book.html
<div ng-controller="BookCtrl" >
<div ng-model="details.title"></div>
<div ng-model="details.author"></div>
</div>
bookEdit.js
var bookEditApp = angular.module('bookEdit',[]);
bookEditApp.service('loadBook',function($http){
return{
loadBookById: function(params,success,error){
$http({
url: 'http://path/to/book/editing',
method: 'GET',
params:{bookId: params.bookId}).success(function(data,status,headers,config)
{
var results = data;
success(results || []);
}).error(function(){
error(arguments);
});
}
};
});
bookEditApp.controller('BookCtrl',function($scope){
$scope.details = {
title: "",
author: ""
};
});
An alternative that follows the order you are expecting is:
1) User enters book id and presses button
2) HomeCtrl routes to EditCtrl with the entered id as a route parameter (no need to use the book service yet):
app.controller('HomeCtrl', function ($scope, $location) {
$scope.editBook = function () {
$location.path('/edit/' + $scope.id);
};
});
3) EditCtrl is loaded, retrieves the route parameter and asks the book service for the correct book:
app.controller('EditCtrl', function EditCtrl($scope, $routeParams, bookService, $location) {
$scope.loading = true;
bookService.getBookById($routeParams.id)
.then(function (result) {
$scope.book = result;
$scope.loading = false;
});
4) When book is loaded the model ($scope.book) is populated and the html is updated
Here is a working example that hopefully will give some further guidance and ideas: http://plnkr.co/edit/fpxtAU?p=preview

Categories

Resources