In AngularJS how to access multiple child scopes from parent scope - javascript

I have a parent zone builder controller which has multiple US States that can have several zip codes within each state.
Here is a breakdown of the basics of the format:
BUILDER CONTROLLER
app.controller("builderController", function($scope, $http, $rootScope, $timeout) {
$scope.zones = [];
});
ZIP CONTROLLER
app.controller("zipController", function($scope, $http, $rootScope, $timeout) {
$scope.zipCodes = [];
$scope.addZipCode = function() {
$scope.zipCodes.push({code: '', distance: '25mi'});
}
$scope.removeZipCode = function(index) {
console.log(index, 'index removing');
$scope.zipCodes.splice(index, 1);
}
});
There can be multiple zipControllers in one builderController inside of the builder controller I want to come up with an object or way to iterate through all of the Zip Controllers so that I can calculate a total distance and number of zip codes used, each time one of the child controllers is updated.
What is the most efficient way to do something like this in Angular?
BASIC GOAL
So there could be 4-5 zipController elements in one builderController I want to have a variable in the builderController called something like totalZipCodes which counts the total number of zip codes in each zipCodes array for each controller, How would I do that with a service or factory? Or is there another way?

Store the data in a factory and have the controller call the factory to do the calculations.

a general approach
As #PrinayPanday suggested in his comment, you should use something like a component/directive to do this. I'm prefacing this by saying I will be using the term directive and component interchangeably as I'm not sure of your constraints. I would achieve this by doing the following:
create a parent directive that defines a directive controller
create child directives that require the parent directive controller
when the child directive initializes, it will register itself with the parent controller
use the parent controller's list of child directive controllers to do what you need to.
You could also use the child's scope to declare a callback on some event (such as $onInit) rather than defining the directive controllers. I have answered several questions like this. So you might find these answers to be helpful in understanding the above solution more:
What is the relationship between an Angular 1.X controller and directive
Angular: Looking for an explanation on custom directive syntax
specific to your needs
As I'm rereading your question, I have to ask if there may not be a better way to solve this by sharing the data in a service/model rather than needing custom directives?

Related

AngularJS : pass data from controller to controller without factory, service or broadcast?

I don't want to use a service or a factory and would like to pass for example an array of data. I would like to access data in my parent controller from my child component.
Factory and service are excluded since i eventually want to migrate my app to angular 2, and i don't want to use ngclick which seems inseperable with broadcast/up/on.
If anyone knows how to pass data on the background (without user interaction, like input or ngclick) using broadcasting, it would work aswell :)
What are my options ?
Thank you !
If you have nested components, you can access the parent's data with require
var child = {
bindings: {},
require: {
parent: '^^parent'
},
controller: childController,
templateUrl: 'child.template.html'
};
Now in your child controller you have access to an instance of the parent controller and thus can call methods and access it's properties:
this.parent.parentMethod();
You have some more detailed code in a previous answer here:
Where should I place code to be used across components/controllers for an AngularJS app?
Your other choices:
bindings
Just like directives' scope or bindToController you can bind data and methods through html attributes using the bindings propety of your component
<component-x shared="$ctrl.shared"></component-x>
var componentX = {
bindings: { shared: '=' }
...
$rootScope
Never use it to store data. It works but it's not made for that purpose and will lead to unmaintainable code.
Services
It's a common misconception that shared data should be done through services.
It was true and good practice before 1.5 though.
Controller inheritance
Another bad practice (imo).
In a classic MVC app nested controllers can inherit parents with the $controller service:
.controller('MainController', function ($scope) {
$scope.logMessage = function(message) {
console.log("Message: " + message);
}
})
.controller('ServicesController', function($scope, $controller) {
$controller('MainController', {$scope: $scope});
});
Broadcast and emit Events
It's the way to go if the event you're broadcasting makes sense application wide (login, logout...etc.) If you're updating a variable in a component, don't use it.
I don't want to use a service or a factory and would like to pass for
example an array of data
You can use localStorage/sessionStorage to store and fetch the data

What are some approaches to reducing controller complexity in angular (with ui-router)?

I am using angular with ui-router. I have a very event driven form which corresponds to a controller which currently holds many $scope callback functions as such:
controller: function($scope, $localForage, $stateParams) {
$scope.addMaterialRow = function(){
...
};
$scope.save = function() {
...
};
...
}
What are some approaches I could take to reduce the complexity in the controller? It is getting quite big.
It might help to see more functions from your controller. Offhand, though, the obvious ones are:
Offload logic to a factory/service. This is especially convenient if your form data needs to be shared or persisted across states.
Go the more Angular 2 / React route and componentize a lot of the view by using directives. For example, turn your complex form into a directive which maintains the logic for all the events and validation on the form. If your form is event-driven, then communicating with other components should be a simple task.
Then your main view controller is sort of the traffic cop for the different directives on the view.
The one that stands out from your example is to use the controllerAs syntax. This eliminates the need to bind directly to the $scope.
As well, define your controller separately from the ui-router state configuration.
// ui-router state config
controller: "YourController",
controllerAs: "vm"
Then in a file all on its own your controller looks like:
YourController.$inject = ['$localForage', '$stateParams']
function YourController($localForage, $stateParams) {
this.$localForage = $localForage;
this.$stateParams = $stateParams;
};
YourController.prototype.addMaterialRow = function() {...}
YourController.prototype.save = function() {...}
angular.module('yourModule').controller('YourController', YourController);
In your view, you will prefix the controller actions with vm. like <a ng-click="vm.save()"></a>

In AngularJS, how does $scope get passed to scope?

I'm a bit confused with the use of $scope in controllers and of scope in directives. Please verify if my understanding is correct (and also provide some alternative ways how to do this).
Let's say I have an html:
<div ng-controller="app1_Ctrl">
.
.
.
<input type="text" ng-model="value"/>
<input type="checkbox" />
<button ng-click="submit()"></button>
</div>
And my main.js
(function() {
angular.module('mainApp', ['app1']);
})();
And my app1 looks like this (based on official AngularJS documentation here)
(function() {
var app = angular.module('app1', []);
app.controller('app1_Ctrl', ["$scope", function($scope) {
.
.
.
}]);
app.directive('app1_Dir1', [function() {
function link(scope, element, attr) {
scope.$watch(attr.someAttrOfCheckBox, function() {
// some logic here
});
function submit() {
// some logic here
}
}
return link;
}]);
})();
How does $scope.value passed in scope in directive so that I can do some manipulations there? Will ng-click fire the function submit() in the directive link? Is it correct to use scope.$watch to listen for an action (ticked or unticked of course) in checkbox element?
Many thanks to those who can explain.
By default, directive scope is controller $scope; but it means the directive is directly dependent on your controller and you need a different controller for each instance of the directive you want to use. It is usually considered a best practice to isolate your directive scope and specifically define the variables you wish to pass it from your controller.
For this, you will need to add a scope statement to your directive :
scope {
label :'#',
context : '=',
function : '&'
}
and update your view :
<my-directive label="labelFromController" context="ctxtFromController" function="myFunction()" ></my-directive>
The symbols denote the kind of thing you wish to pass through : # is for one-way binding (as a string in your directive), = is for two-way binding of an object (which enables the directive to update something in your controller), and & is for passing a function.
There are a lot of additional options and subtleties that are best explained by the Angular doc https://docs.angularjs.org/guide/directive. There are also some nice tutorials out there (e.g. http://www.sitepoint.com/practical-guide-angularjs-directives/)
Your submit() function is not attached to anything, so you won't be able to call if from your viewer. You need to define it as scope.submit = function() ... in your link function if you wish to access it.
You can use $watch for this kind of thing, but there are usually other more elegant ways to achieve this by leveraging the fact that angular already "watches" the variables it is aware of and monitors any changes he can (this can be an issue when some external service changes data for exemple, because angular cannot listen to events it is not made aware of). Here, you can probably simply associate the ng-model directive to your input checkbox to store its true/fale (checked/unchecked) value, and the ng-change or ng-click directives to act on it. The optimal solution will mostly depend on the exact nature of your business logic.
Some additional thoughts :
The HTML insides of your directive should be packaged in an inline template field, or in a separate HTML file referenced by the templateUrl field in your directive.
In your HTML code above, your directive is not referenced anywhere. It should be an element, attribute or class (and your directive definition should reflect the way it can be called, with the restrict field). Maybe you have omitted the line containing the directive HTML, but as it stands, your directive doesn't do anything.
To my knowledge, you don't need to return link. Think of it as the "body" of your directive, where you define the variables and functions you will call in the HTML.
Your directive doesn't actually need HTML code and the above thoughts might be irrelevant if you are going in a different direction, but encapsulating some kind of view behaviour that you want to reuse is probably the most common use of directives.

How to structure angular factor/service that manipulates DOM

I'm building a growl like UI in angular. I'd like to expose it as a factory (or service) to make it available in my controllers. Calling growl.add will result in a change in the DOM, so it seems like I should have a directive take care of that, rather than doing direct DOM manipulation in the factory. Assuming that a factory-directive combo is the best option (and please correct me if that is not a good assumption), the question is:
How best to communicate between the factory and the directive?
Specifically, how best to send messages from the factory to the directive? Other questions have well covered sending information the other way, with onetime callback.
See below the working example. I suspect there is a better way though..
For reference, I have played with other options:
A) have the directive watch the service, e.g.
$scope.$watch(function(){
growl.someFunctionThatGetsNewData()},
function(newValue){
//update scope
})
But this means that someFunctionThatGetsNewData gets called in every digest cycle, which seem wasteful, since we know that the data only gets changed on growl.add
B) send an 'event', either via routescope, or via event bindings on the dom/window. Seem un-angular
Since neither of those options seem good, I'm using the one below, but it still feels hacky. The register function means that the directive and the factory are tightly coupled. But then again from usage perspective they are tightly bound - one is no good w/o the other.
It seem like the ideal solution would involve declaring a factory (or service) that includes the directive in its declaration (and perhaps functional scope) so that it exposes a single public interface. It seems icky to have two separate publicly declared components that entirely depend on each other, and which have tight coupling in the interfaces.
Working example - but there must be a better way..
vpModule.directive('vpGrowl',['$timeout', 'growl', function ($timeout, growl) {
return {
template: '<div>[[msg]]</div.',
link: function($scope, elm, attrs) {
growl.register(function(){
$scope.msg = growl.msg;
});
$scope.msg = growl.msg;
}
};
}]);
vpModule.factory('growl', ['$rootScope', '$sce', function($rootScope, $sce) {
var growl = {};
growl.msg = '';
var updateCallback = function(){};
growl.add = function(msg){
growl.msg = msg;
updateCallback();
};
growl.register = function(callback){
updateCallback = callback;
};
return growl;
}]);
I would have your growl service decide what to show, not the directive. So, the service handles any timers, state, etc. to decide when to hide/show messages. The service then exposes a collection of messages which the directive simply binds to.
The directive can inject the service and simply place it in scope, and then bind an ng-repeat to the service's collection. Yes, this does involve a watch, but you really don't need to worry about the performance of a single watch like this.
link: function(scope, elm, attrs) {
scope.growl = growl; // where 'growl' is the injected service
}
and then in the directive template:
<div ng-repeat="msg in growl.messages">
...
</div>
I would implement following logic:
Service growl defines some property growlProp on $rootScope & update it on each call of growl.add
Directive set watcher on $rootScope.growlProp
So directive knows nothing about service & service knows nothing about directve.
And additional overhead related to watcher is minimum.

AngularJS - Parameter binding in controllers

I've got the following code sample and apparently I once again lack some background information.
When I use the greet directive, there is a binding happening on the controller level. For some reason it binds the element and the attribute values of the element to those corresponding variables ($attr, $element).
Is there a list with all the existing bindings which exist for the controllers?
I've done some research, but couldn't come up with anything in this direction.
directives.js
angular.module('TodoApp.directives', []).
directive('greet', function () {
return {
template: '<h2>Greetings from {{from}} to {{to}}<h2>',
controller: function($scope, $element, $attrs) {
$scope.from = $attrs.from;
$scope.to = $attrs.greet;
}
};
});
list.html
....
<div greet="Test1" from="Test2"></div>
...
Directive controllers have some special bindings. You can find them in the docs of the $compile service (here), search for "controller". Repeating here for completeness:
The controller is injectable (and supports bracket notation) with the following locals:
$scope - Current scope associated with the element
$element - Current element
$attrs - Current attributes object for the element
$transclude - A transclude linking function pre-bound to the correct transclusion scope. The scope can be overridden by an optional first argument. function([scope], cloneLinkingFn).
This is called Dependency Injection. Rather than having a fixed parameter list, Angular uses a DI container to inject the parameter when you need it. For example, if you need the $http service, just add it as a parameter - the order of the parameters does not matter.
The type of parameters you can inject are constants, values, factories, services, and providers.
Some are available to you 'out-of-the-box' from the Angular library:
Look here for Angular Providers and Services
Others, you get from third-party modules, or modules that you develop yourself.

Categories

Resources