I'm in the middle of migrating to Angular based front-end. I have a function in JavaScript to pass some data to directive, after some research found that using Service and $broadcast could be a good solution. but doesn't work for me ...
here is my code :
var app = angular.module('app', []);
app.factory('broadcastService', ['$rootScope',
function($rootScope) {
var factory = {};
factory.sendAjaxResut = function(name, obj) {
console.log(' -- $broadcast ');
$rootScope.$broadcast(name, obj);
}
return factory;
}
]);
app.directive("bill", [directive]);
function directive() {
return {
restrict: "E",
link: function($scope) {
$scope.$on("b1", function(e, a) {
console.log('-- directive')
});
}
}
}
code for passing data to service :
function _ajaxCUSTOMER(e) {
angular
.injector(['ng' ,'app'])
.get("broadcastService")
.sendAjaxResut("b1", e);
}
<button onclick="_ajaxCUSTOMER('foo')" >Send</button>
Question 1 : what is ng in .injector(['ng' ,'app'])
Question 2 : at this time console only shows -- $broadcast. what is the problem of my code that can't catching event in directive
jsfiddle
Your directive is not getting the $broadcast because you are creating a new injector with a new $rootScope. Instead use the injector of your app.
function _ajaxCUSTOMER1(e) {
var rawElem = document.getElementById("app");
var elem = angular.element(rawElem);
var _injector = elem.injector();
var _broadcastService = _injector.get("broadcastService");
_broadcastService.sendAjaxResut("b1",e);
}
This example finds the element of your app and uses angular.element to retrieve its injector.
The working JSFiddle.
YOu can try this solution:
<button onclick="_ajaxCUSTOMER('foo', 'e')" >Send</button>
function _ajaxCUSTOMER(name,obj) {
angular.element(document).find('body').injector().get('broadcastService').sendAjaxResut(name, obj);
console.log('ok');
}
myApp.factory('broadcastService', ['$rootScope',
function($rootScope) {
console.log('broadcastService');
var factory = {};
factory.sendAjaxResut = function(name, obj) {
console.log(' -- $broadcast ');
$rootScope.$broadcast('newEvent', name, obj);
}
return factory;
}
]);
myApp.directive('bill', function () {
return {
restrict: 'EAC',
controller: function($scope) {},
link: function(scope, element, attrs) {
scope.$on("newEvent", function(event, data, name, obj) {
console.log('-- directive')
});
}
};
});
You need define controller in your html.
Live example on jsfiddle.
var app = angular.module('app', [])
.controller('ExampleController', function($scope, broadcastService) {
});
app.factory('broadcastService', ['$rootScope',
function($rootScope) {
var factory = {};
factory.sendAjaxResut = function(name, obj) {
console.log(' -- $broadcast ');
$rootScope.$broadcast(name, obj);
}
return factory;
}
]);
app.directive("bill", function($rootScope) {
return {
restrict: "E",
template:'<div>My bill</div>',
link: function($scope) {
$rootScope.$on("b1", function(e, a) {
console.log('-- directive',a)
});
}
}
});
function _ajaxCUSTOMER1(e) {
angular.element(document).find('body').injector().get('broadcastService').sendAjaxResut('b1', e);
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body id="app" ng-app="app">
<div ng-controller="ExampleController">
<button onclick="_ajaxCUSTOMER1('5')">
Click Here to send 5
</button>
<bill>
</bill>
</div>
</body>
Related
Please have a look at this example, since it is the best way to explain the problem.
In this example if you click the directive link, it does not compile the template, but instead displays it as "{{1+1}}".
On the other hand if you click the "Simple link" it compiles the template and displays "2" instead.
angular.module('myApp', [])
.provider('$popup', function() {
var service = {};
this.$get = ['$compile', '$rootScope', function($compile, $rootScope) {
var template = $('<div>{{1+1}}</div>');
service.go = function() {
$(document.body).append(template);
$compile(template)($rootScope);
}
return service;
}];
})
.directive('popupLink', ['$popup', function($popup) {
return {
restrict: 'A',
scope: {},
link: function link(scope, element, attrs) {
element.click(function() {
$popup.go();
return false;
});
}
};
}])
.controller('mainCtrl', ['$scope', '$popup', function($scope, $popup) {
$scope.go = function() {
$popup.go();
};
}])
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>
<div ng-app="myApp" ng-controller="mainCtrl">
<a ng-href="/test" popup-link>Directive link</a>
Simple link
</div>
My question is why isn't the template compiling with the directive? (but it does in the controller)
And how do I fix it so that it compiles in the directive also?
P.S. Here is the jsbin link in case you want to play around with the code:
http://jsbin.com/vuzutipedu/edit?html,js,output
The directive needs to do scope.$apply():
link: function link(scope, element, attrs) {
element.click(function() {
$popup.go();
//ADD apply
scope.$apply();
return false;
});
The click event handler executes outside the AngularJS framework. A framework digest cycle needs to be performed to execute the watcher for the {{1+1}} interpolation.
It works with the ng-click directive because that directive includes scope.$apply.
For more information, see
AngularJS Developer Guide - Integration with the browser event loop
DEMO
angular.module('myApp', [])
.provider('$popup', function() {
var service = {};
this.$get = ['$compile', '$rootScope', function($compile, $rootScope) {
var template = $('<div>{{1+1}}</div>');
service.go = function() {
$(document.body).append(template);
$compile(template)($rootScope);
}
return service;
}];
})
.directive('popupLink', ['$popup', function($popup) {
return {
restrict: 'A',
scope: {},
link: function link(scope, element, attrs) {
element.click(function() {
$popup.go();
//ADD apply
scope.$apply();
return false;
});
}
};
}])
.controller('mainCtrl', ['$scope', '$popup', function($scope, $popup) {
$scope.go = function() {
$popup.go();
};
}])
<script src="//unpkg.com/jquery"></script>
<script src="//unpkg.com/angular/angular.js"></script>
<div ng-app="myApp" ng-controller="mainCtrl">
<a ng-href="/test" popup-link>Directive link</a>
Simple link
</div>
Try this in $get, instead of $compile(template)($rootScope)
$compile(angular.element(template))(angular.element(template).scope());
Let me know if it works
HTML:
<div ng-repeat="item in productArr">
{{ item.title }}
</div>
<div category-page-navigation current-page='currentPage' category-products-count='productsCount'></div>
JS:
.controller('categoryController', ['$scope', '$location', '$http', '$q', '$window', '$stateParams', function($scope, $location, $http, $q, $window, $stateParams) {
$scope.currentPage = 1;
$scope.productsCount = 0;
var GET = {
getProductData: function() {
var defer = $q.defer();
$http.post('services/loadProduct.php', {
'id' :1,
}).then(function(response) {
defer.resolve(response);
}, function(response) {
defer.resolve([]);
});
return defer.promise;
}
};
var getData = {
getProduct: function() {
var productData = GET.getProductData();
$q.all([productData]).then(
function(response) {
$scope.productArr = response[0].data.products;
$scope.productsCount = response[0].data.products.length;
});
}
};
getData.getProduct();
}])
.directive('categoryPageNavigation', function($compile, $parse) {
return {
scope: {
currentPage: '=currentPage',
categoryProductsCount: '=categoryProductsCount'
},
link: function (scope, element, attrs) {
debugger;
// Here scope.categoryProductsCount = undefined
// ...
$scope.$watch(scope.currentPage, function(value) {
// ...
});
}
};
});
I try to form new HTML for navigation to manipulate with HTML I get from ng-repeat.
In directive I need currentPage(from start =1) and total count of items from ng-repeat(length of array) witch I get from service. How I can pass variables to directive? First I need to get variables from service(ajax request or something else) then pass variables(some ather data) to directive.
If I understood correctly what you mean. Here is a code pen example on how to shared data between you controller and your directive.
A good read to understand the code below:https://docs.angularjs.org/guide/providers
http://codepen.io/chocobowings/full/Xmzxmo/
var app = angular.module('app', []);
//-------------------------------------------------------//
app.factory('Shared', function() {
return {
sharedValue: {
value: '',
}
};
});
//-------------------------------------------------------//
app.controller('ctrl', function($scope, Shared) {
$scope.model = Shared.sharedValue;
});
//-------------------------------------------------------//
app.directive('directive', ['Shared',
function(Shared) {
return {
restrict: 'E',
link: function(scope) {
scope.model = Shared.sharedValue;
},
template: '<div><input type="text" ng-model="model.value"/></div>'
}
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
Ctrl:
<div ng-controller="ctrl">
<input type="text" ng-model="model.value" />
<br/>
</div>
Directive:
<directive value="model.value"></directive>
</div>
I already have seem other topics with this kind of issue, but no one could help me... So here is my issue:
I have a navbar with a button for search, this buttons makes and get request from a webservice and returns a json object which must be apply to fill an table list. The problem is, my button and my table are in separated controllers, and it does work like I expected.
var app = angular.module('clientRest', []).controller('lista', ['$scope', 'loadLista', function($scope, loadLista) {
$scope.contatos = loadLista.getContatos();
}]).controller('pesquisa', ['$scope', '$http', 'loadLista', function($scope, $http, loadLista) {
$scope.listar = function() {
$http.get("http://localhost/wsRest/index.php/contato").success(function(response) {
loadLista.setContatos(response);
});
};
}]).service('loadLista', function() {
var contatos = [];
return {
getContatos: function() {
return contatos;
},
setContatos: function(c) {
contatos = c;
}
};
});
My code...
When I call listar() from pesquisa controller I need to send received data to $scope.contatos from lista controller to make my ng-repeat work, everything with a single click.
How can I do it?
Thanks everyone
Better to use a service to share data between two controllers / modules as this might be the best approach. You can refer the code segment given below to understand the concept.
angular.module('app.A', [])
.service('ServiceA', function() {
this.getValue = function() {
return this.myValue;
};
this.setValue = function(newValue) {
this.myValue = newValue;
}
});
angular.module('app.B', ['app.A'])
.service('ServiceB', function(ServiceA) {
this.getValue = function() {
return ServiceA.getValue();
};
this.setValue = function() {
ServiceA.setValue('New value');
}
});
In order to trigger the data receipt event, you may use
Broadcast / emit messages - with #broadcast / #emit
An angular promise with a call back
Controller initiation function to reload the previously read information from a service
.controller('MyController', function($scope, ServiceA) {
$scope.init = function() {
$scope.myValue = ServiceA.getValue();
};
// Call the function to initialize during Controller instantiation
$scope.init();
});
Use $rootScope.$emit to emit a change event when setting the variable and use $on to get the value in the lista controller. I used customListAr here just to demostrate a button click. Does this help?
var app = angular.module('clientRest', [])
.controller('lista', ['$scope', 'loadLista', '$rootScope',
function($scope, loadLista, $rootScope) {
console.log(loadLista);
$scope.contatos = loadLista.getContatos();
$rootScope.$on('change', function() {
$scope.contatos = loadLista.getContatos();
});
}
])
.controller('pesquisa', ['$scope', '$http', 'loadLista',
function($scope, $http, loadLista) {
$scope.listar = function() {
$http.get("http://localhost/wsRest/index.php/contato").success(function(response) {
loadLista.setContatos(response);
});
};
$scope.customListAr = function() {
loadLista.setContatos(["item 1" , "item 2", "item 3"]);
}
}
])
.service('loadLista', ['$rootScope',
function($rootScope) {
var contatos = [];
return {
getContatos: function() {
return contatos;
},
setContatos: function(c) {
contatos = c;
$rootScope.$emit('change');
}
};
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="clientRest">
<div ng-controller="lista">
<ul>
<li ng-repeat="a in contatos">{{a}}</li>
</ul>
</div>
<div ng-controller="pesquisa">
<button ng-click="customListAr()">Click Me</button>
</div>
</div>
Your problem is that when you do $scope.contatos = loadLista.getContatos(); you are setting a static value, and angular is unable to effectively create a watcher for that object because your setContatos method is creating a new object each time. To get around this, have the controller's scope hold a reference to the parent object and then it will automatically have a watcher on that object.
var app = angular.module('clientRest', [])
.controller('lista', ['$scope', 'loadLista', function($scope, loadLista) {
$scope.contatos = loadLista.contatos;
}])
.controller('pesquisa', ['$scope', '$http', 'loadLista', function($scope, $http, loadLista) {
$scope.listar = function() {
$http.get("http://localhost/wsRest/index.php/contato"
).success(function (response) {
loadLista.contatos.data = response;
});
};
}])
.service('loadLista', function() {
var lista = {
contatos: {},
};
return lista;
});
// view:
<ul>
<li ng-repeat="contato in contatos.data">
{{ contato }}
</li>
</ul>
Hi I am trying to create a display loading box implementation but I seem to have some problems.Here is what I have so far:
I have created an httpInterceptor:
mainModule.factory('httpLoadingInterceptorSvc', ['$q', '$injector', 'EVENTS', function ($q, $injector, EVENTS) {
var httpInterceptorSvc = {};
httpInterceptorSvc.request = function (request) {
var rootScope = $injector.get('$rootScope');
rootScope.$broadcast(EVENTS.LOADING_SHOW);
return $q.when(request);
};
httpInterceptorSvc.requestError = function (rejection) {
hideLoadingBox();
return $q.reject(rejection);
};
httpInterceptorSvc.response = function (response) {
hideLoadingBox();
return $q.when(response);
};
httpInterceptorSvc.responseError = function (rejection) {
hideLoadingBox();
return $q.reject(rejection);
};
function hideLoadingBox() {
var $http = $injector.get('$http');
if ($http.pendingRequests.length < 1) {
var rootScope = $injector.get('$rootScope');
rootScope.$broadcast(EVENTS.LOADING_HIDE);
}
}
return httpInterceptorSvc;
}]);
I have then added the directive to the interceptors of the httpProvideR:
$httpProvider.interceptors.push('httpLoadingInterceptorSvc');
I then created a directive:
mainModule.directive('loadingDir', ['EVENTS', function (EVENTS) {
var loadingDir = {
restrict: 'E',
templateUrl: 'App/scripts/main/directives/loading/LoadingDir.html'
};
loadingDir.link = function (scope, element) {
element.hide();
scope.$on(EVENTS.LOADING_SHOW, function () {
element.show();
});
scope.$on(EVENTS.LOADING_HIDE, function () {
element.hide();
});
};
return loadingDir;
}]);
And then added a simple ajaxCall that alerts a message on the controller:
dataSvc.getCurrentDate().then(function(currentDate) {
alert(currentDate);
});
I put th edirective on the html page:
<loading-dir></loading-dir>
Now my problem is that the directive code gets executed after the controller code this makes the dierective relatively useles until the page is loaded.Is there any way to make the directive code execute before the controller?
You can prepend a div to your page:
<body>
<div controller="beforeController">
<loading-dir></loading-dir>
</div>
[Rest of the page]
</body>
And beforeController should be loaded instantly.
I've got directive and service in my app (declared in separate files):
Service:
(function(){
angular.module('core', [])
.factory('api', function() {
return {
serviceField: 100
};
})
})();
Directive:
(function(){
angular.module('ui', ['core'])
.directive('apiFieldWatcher', function (api) {
return {
restrict: 'E',
replace: true,
scope: true,
template: '<div>+{{apiField}}+</div>',
controller: function($scope) {
$scope.apiField = 0;
},
link: function (scope) {
scope.$watch(function(){return api.serviceField}, function(apiFld){
scope.apiField = apiFld;
});
}
}
});
})();
And in another separate file I have native model:
function Model() { this.fld = 0; }
Model.prototype.setFld = function(a) { this.fld = a; }
Model.prototype.getFld = function() { return this.fld; }
How can I bind (two way) my native this.fld field to value in my AngularJS service?
The solution is in using this code:
Model.prototype.setFld = function(a) {
this.fld = a;
injector.invoke(['$rootScope', 'api', function($rootScope, api){
api.setField(a);
$rootScope.$digest();
}]);
};
Model.prototype.getFldFromApi = function() {
var self = this;
injector.invoke(['api', function(api){
self.fld = api.getField();
}]);
};
http://plnkr.co/edit/nitAVuOtzGsdJ49H4uyl
i think it's bad idea to use $digest on $rootScope, so we can maybe use
var scope = angular.element( elementObject ).scope();
to get needed scope and call $digest for it