I am building an angular single page app, which have a structure like this.
"app.parent - parent state"
"app.parent.childState - child state"
"app.parent.childSatate" has 4 multiple named view inside it.
I have to show something on parent once all 4 views fetched their respective data.
Any suggestions how to do it?
Note: If Solution is pure independent it helps me alot, if I delete/add any controller then i need not to make changes to parent all the time.
Suppose you have 4 services that you know will be the data source for 4 different child views. You can setup your services so that they return references, but still provide access to the underlying promises. The idea is that you want your views to use the reference when rendering the view; the parent will use $q.all to wait until the individual promises are resolved before it shows something.
Factories
app.factory('service1', function($http) {
var data1 = [];
return {
promise: $http({...}).success(function(result) {
angular.copy(result, data1);
}),
getData: function() {
return data1;
}
}
});
app.factory('service2', function($http) {
var data2 = [];
return {
promise: $http({...}).success(function(result) {
angular.copy(result, data2);
}),
getData: function() {
return data2;
}
}
});
app.factory('service3', function($http) {
var data3 = [];
return {
promise: $http({...}).success(function(result) {
angular.copy(result, data3);
}),
getData: function() {
return data3;
}
}
});
app.factory('service4', function($http) {
var data4 = [];
return {
promise: $http({...}).success(function(result) {
angular.copy(result, data1);
}),
getData: function() {
return data4;
}
}
});
Child Controllers
app.controller('ctrl1', function($scope, service1) {
$scope.data1 = service1.getData();
});
app.controller('ctrl2', function($scope, service2) {
$scope.data2 = service2.getData();
});
app.controller('ctrl3', function($scope, service3) {
$scope.data3 = service3.getData();
});
app.controller('ctrl4', function($scope, service4) {
$scope.data4 = service4.getData();
});
Parent Controller
app.controller('parent', function($scope, $q, service1, service2, service3, service4) {
$q.all(
[service1.promise,
service2.promise,
service3.promise,
service4.promise])
.then(function() {
$scope.done = true;
});
});
Parent View
<div ng-show="done"> All child data loaded </div>
A More Modular Approach
As mentioned in the original post - it would be nice if the parent controller did not have to depend on the injection of the individual data sources. That way, when the child changes data sources (adds sources, or removes sources), the parent controller is not impacted.
This can be done by relying on directive-to-directive communication. The idea is that the child directives can register their data sources with the parent directive. Once all the data sources have been registered, the parent directive can use $q.all as in the first example.
Parent Directive
app.directive('parent', function($q) {
return {
restrict: 'A',
require: 'parent',
controller: function($scope) {
$scope.done = false;
var promises = [];
this.register = function(promise) {
promises.push(promise);
}
this.getPromises = function() {
return promises;
}
},
link: function(scope, element, attr, parent) {
$q.all(parent.getPromises())
.then(function() {
scope.done = true;
});
}
}
});
Child Directive
app.directive('child', function(service1) {
return {
restrict: 'A',
require: '^parent',
controller: function($scope) {
$scope.data1 = service1.getData();
},
link: function(scope, element, attr, parent) {
parent.register(service1.promise);
}
}
});
HTML
<div parent>
<div child>
<div> {{data1}} </div>
</div>
</div>
Yet Another Approach
You may have noticed that although the second approach does not impact the parent controller, the child controller has a dependency on the parent directive. How can we eliminate this view dependency from the child controller?
Use services. Create a parent service that allows child controllers to register their promises; and also expose a method from the parent service that returns a promise which is resolved when all the child promises have been resolved.
This may be the preferred approach when you have a single point of loading for the entire page.
Parent Service
app.factory('parentService', function($q) {
var promises = [];
return {
promise: function() {
return $q.all(promises);
},
register: function(promise) {
promises.push(promise);
}
}
});
Child Controller
app.controller('childCtrl1', function(parentService, service1) {
parentService.register(service1.promise);
});
app.controller('childCtrl2', function(parentService, service2) {
parentService.register(service2.promise);
});
Parent Directive
app.directive('parent', function(parentService) {
return {
restrict: 'A',
controller: function($scope) {
$scope.done = false;
},
link: function(scope) {
parentService.promise().then(function() {
scope.done = true;
});
}
}
});
HTML
<div parent>
<div ng-show="done">All child data loaded</div>
<div ng-controller="childCtrl1">
{{ data1 }}
</div>
<div ng-controller="childCtrl2">
{{ data2 }}
</div>
</div>
Use $rootScope (recommended factory/service) in all four controllers (for all 4 views), and have certain count/flag which keeps track of data fetching in all controllers and on the main page/controller keep checking for that $rootScope/factory value.
$rootScope/ facetory/services are accessible across controllers/views, so that you can modify in several controllers and access the latest value on some other controller/factory/service.
Related documentations:
https://docs.angularjs.org/api/ng/service/$rootScope
https://docs.angularjs.org/guide/services
Create a service or factory and inject it into the controllers you need it in, that way you will not have to repeat code.
You can also use this method to share data across your app by making variables avalible via dependency injection.
Related
In my angular project I'm using Angular.js material. And I want to show $mdialog with custom controller, where user changes some data and this data should be applied to my $scope variable. Example what I do now:
function myControllerFn($scope, MyService){
// I do copy of my service variable because I don't want to change it until user will click save button
$scope.name = angular.copy(MyService.name);
$scope.editCurrentProfile = function() {
$scope.showEditProfileDialog($scope.name).then(function(name){
$scope.name = name;
}
}
$scope.showEditProfileDialog = function(name) {
var deferred = $q.defer();
$mdDialog.show({
controller: 'editProfileViewCtrl',
templateUrl: 'controllers/editProfileDialog.tmpl.html',
locals: {
name: name,
deferred: deferred
}
});
return deferred.promise;
};
}
Then in dialog controller I do:
function editProfileViewCtrl($scope, name, deffered) {
deferred.resolve('newName');
}
But I think it is the wrong way. So what is the best way to communicate between two view controllers in angular without new service ? Or better create another service like: EditDialogService, where I will save results ?
When you open a modal, the show() function returns a promise.
$scope.showEditProfileDialog = function(name) {
var modalInstance = $mdDialog.show({
controller: 'editProfileViewCtrl',
templateUrl: 'controllers/editProfileDialog.tmpl.html',
locals: {
name: name
}
});
modalInstance.then(function(result){
// acces what is returned
// In your case, you would do
$scope.name = result;
}, function(error){
// Usually when you cancel your modal
});
}
Your modal controller can be injected with $mdDialog.
function editProfileViewCtrl($scope, name, $mdDialog) {
$scope.close = function() {
$mdDialog.hide('newName');
}
}
You should create a directive with your user as scope variable. Angular in itself is handling the data binding.
It is possible to create a minimal controller function that has access to $scope.
$mdDialog.show({
controller: function () { this.parent = $scope; },
templateUrl: 'controllers/editProfileDialog.tmpl.html',
locals: {
name: name,
deferred: deferred
}
});
I am trying to communicate between two directives. I tried the service way, it didn't work. Maybe I am doing something wrong.
<list-data testing="trial" template-url="grid.html" link-passed="data.json"></list-data>
My directive and service:
app.directive('listData', function($http, serVice){
return {
restrict: 'E',
scope: {
testing: '#',
linkPassed: '#'
},
templateUrl: function(elem,attrs) {
return attrs.templateUrl || 'some/path/default.html'
},
link: function($scope){
var url = 'js/' + $scope.linkPassed;
$http.get(url).then(success, error);
function success(data){
$scope.iconUrl = data.data;
}
function error(response){
console.log(response)
}
$scope.tryingToClick = function(icon){
serVice=icon.name;
console.log(serVice)
}
}
};
});
app.directive('render', function(serVice){
return {
restrict: 'E',
template: '{{rendering}}',
link: function($scope){
$scope.rendering = serVice.name;
console.log(serVice)
}
};
});
app.factory('serVice', function(){
return{items:{}};
})
grid.html is just a simple grid layout where I am trying to show the data in grid.
<div class="col-sm-6 grid" ng-repeat="icon in iconUrl">
<p ng-click="tryingToClick(icon)">{{icon.iconUrl}}</p>
</div>
I need to pass the data when I click the function tryingToClick and the icon passes to the render directive. I cannot use $rootscope here, nor make new controllers. I will be using the logic here in a pretty big enterprise app, just that I made a very simple version of it on my localhost, just to get the logic right.
Your service doesn't look quite right. I'd use
app.factory('serVice', function() {
var settings = {};
// optionally set any defaults here
//settings.name = 'me';
return settings;
});
and you're not setting the name property of the service here:
serVice=icon.name;
It should be
serVice.name = icon.name;
given that you're looking for the name property later: $scope.rendering = serVice.name;
What do you mean by not creating more controllers? Do you mean that you can't create more on the app or that you can't use controllers within the directives?
From what I understood of your question I threw this codepen together for experimentation http://codepen.io/ihinckle/pen/JWGpQj?editors=1010
<div ng-app="directiveShare">
<directive-a an-array="[1,2,3,4,5]"></directive-a>
<directive-b></directive-b>
</div>
angular.module('directiveShare', [])
.directive('directiveA', function(){
return {
restrict: 'E',
scope: {
anArray: '<'
},
controller: function($scope, aService){
$scope.clicked = aService.setIcon;
},
template: `
<ul>
<li ng-repeat="item in anArray" ng-click="clicked(item)">item</li>
</ul>`
}
})
.directive('directiveB', function(){
return {
controller: function($scope, aService){
$scope.displayIcon = aService.getIcon;
},
template: `
<h1>{{displayIcon()}}</h1>
`
}
})
.factory('aService', function(){
var srvc = {};
srvc.setIcon = function(x){
srvc.icon = x;
};
srvc.getIcon = function(){
if(srvc.icon){
return srvc.icon;
}else {
return '';
}
};
return srvc;
});
I used getters and setters in the service and controllers on the directives to expose the functions.
How to bind a directive to a controller via a service update ?
I want to create the possibility to update a cart(the service) via a directive(add to cart button) and then the controller (that display the cart) will update its view.
Despite the fact that I added a watch on the service itself my controller is not been updated.
Of course it will be good if the controller and the directive doesn't share the same scope (transclude: true in the directive)
The service:
angular.module('stamModule', [])
.factory('valueService', function () {
var factory = {
data: {value: 1000},
inc: inc,
getData: getData
};
function inc() {
this.data.value++;
}
function getData() {
return this.data;
}
return factory;
})
the directive:
.directive('buttonDirective', function (valueService) {
var directive = {
restrict: 'E',
template: '<button>Inc</button>',
link: linkFnc
};
function linkFnc(scope, el) {
el.on('click', function () {
valueService.inc();
});
}
return directive;
})
The controller:
.controller('FirstController', function ($scope, valueService) {
var vm = this;
vm.serv = valueService;
$scope.$watch('vm.serv.getData()', function (newValue) {
console.log("watch");
console.log(newValue);
});
})
The html:
<body ng-app="stamModule">
<hr>
<div ng-controller="FirstController as vm">
<p>{{vm.serv.data}}</p>
<button-directive ></button-directive>
</div>
here's a demo:
https://jsfiddle.net/07tp4d03/1/
Thanks
All your code needed was a little push. No need for event broadcasting or anything like that.
The problem was, that the click event listener was working outside Angular's digest loop, and thus Angular watch wasn't working for you.
If you change your directive's code to the following, it will work.
.directive('buttonDirective', function (valueService) {
var directive = {
restrict: 'E',
template: '<button ng-click="inc()">Inc</button>',
link: linkFnc
};
function linkFnc(scope) {
scope.inc = function() {
valueService.inc();
};
}
return directive;
})
Here is a fork of your fiddle that works
I have a function that two controllers will be using, and instead of both of them having the same source code for the same function, I want it in one place and just inject the controller parameters (or perhaps the controller itself this). These three may exist in three separate files/modules.
.controller('FirstCtrl', function() {
this.search = function(this) {
find(this);
};
});
.controller('SecondCtrl', function() {
this.seek = function(this) {
find(this);
};
});
var find = function(controller) {
.
.
.
};
Is this the best way? How about if I have services in my controllers like $http or $scope, and the function find would depend on these services? How do I inject these angular specific services to a plain JavaScript function not defined in an AngularJS module?
There are a few ways to do it; one may be:
.factory("findMixin", function() {
return {
find: function() {
// your implementation; `this` will be the controller
}
};
})
.controller("SomeCtrl", ["$scope", "findMixin", function($scope, findMixin) {
angular.extend(this, findMixin);
// here `this`, i.e. the controller, has received the methods from the mixin
...
})
The same principle (angular.extend) can be applied to the $scope, if you want find to be mixed into the scope.
You can add a service:
.factory('find', [ function() {
return function(controller, scope) {
// ...
};
}]);
And inject it into the controllers:
.controller('FirstCtrl', ['find', function(find) {
this.search = function(this) {
find(this);
};
}]);
.controller('SecondCtrl', ['find', function(find) {
this.seek = function(this) {
find(this);
};
}]);
I have a two directives
angular.module('myApp.directives', []).
directive('exampleDirective', ['version', function(version) {
return {
link:function(scope,elm,attr) {
elm.on('click',function() {
//access exampleDirective2 behaviour
});
}
}
}]).
directive('exampleDirective2', ['version', function(version) {
return {
link:function(scope,elm,attr) {
elm.on('change',function() {
//access exampleDirective behaviour
});
}
}
}]);
As you can see on exampleDirective elm.on(click) function I want to get the exampleDirective2 function and vice versa.
Is there a way in AngularJS to achieve this?
There are three solutions to this problem:
First solution: use a service
Share a service between directives, that can contain data and functions.
.service('myService', function(){
this.data = //....
this.function = function() //...
})
.directive('dir1', ['myService', function(myService) {
//...
link: function(scope, elem, attrs) {
scope.data = myService.data;
}
}])
The same for the other directive.
Second solution: directives' controllers
If your directives have a parent/child/sibling relationship:
.directive('dir1', function(){
return {
controller: function(scope, elem, attrs) {
this.sayHello = function() {
alert('hello')
}
}
}
})
.directive('dir2', function(){
return {
require: '^dir1',
link: function(scope, elem, attrs, dir1Ctrl) {
dir1Ctrl.sayHello(); //will alert hello
}
}
})
However, this won't work if you directives have isolated scope. Also, depending on the relationship of the directive (parent/child or siblings) the sintax for the require attribute changes slightly; you can find more info on the AngularJS docs for directives.
Third solution: use events
You can also emit/broadcast events from the directive scopes, or inject $rootScope and use it as an event bus:
.directive('dir1', function($rootScope){
return {
link: function(scope, elem, attrs) {
var emitEvent = function(){
$rootScope.$emit('somethingHappenedEvent', { /*you can pass optional data*/ });
}
emitEvent();
}
}
})
.directive('dir2', function($rootScope) {
return {
link: function(scope, elem, attrs) {
$rootScope.$on('somethingHappenedEvent', function(event) {
if(!event.defaultPrevented) {
//react to event here
}
})
}
}
})
You could also to the same with the normal scope instead of $rootScope, but in that case you will have to keep in mind that the event will bubble up/down all the scopes (depending on the use of $emit or $broadcast). I prefer to $emit from $rootScope, to that it will be the only scope able to catch the event, and will also be quite fast.
One way to do this is to have a wrapper directive with a controller, which can then be shared between the directives if you use require. A simpler, and perhaps better solution (as it doesn't depend on the DOM) is to have a common service that enables communication between the directives.
If you want to do this in directive way only.
Here is answer
angular.module('myApp.directives', []).
directive('exampleDirective', ['version', function(version) {
return {
link:function(scope,elm,attr) {
elm.on('click',function() {
//access exampleDirective2 behaviour
scope.exampleDirective2Function();
});
scope.exampleDirectiveFunction = function (){
//write your code here
}
}
}
}]).
directive('exampleDirective2', ['version', function(version) {
return {
link:function(scope,elm,attr) {
elm.on('change',function() {
//access exampleDirective behaviour
scope.exampleDirectiveFunction();
});
scope.exampleDirective2Function= function (){
//write your code here
}
}
}
}]);
Another way is, you can write a service, write a function in that, and use that service functions by injecting service into the directive.