AngularJS Directive-Controller-Service Interaction - angularjs

I'm trying to design a single page pagination app that displays the different pages of a document beneath each other in the same window. It has to meet the following requirements:
A pagination toolbar where the user can click next/previous/... and submit a page to go to.
The window scrolls to the right page of the document after a page has been submitted
If the user scrolls manually, the current page should update automatically
I tried some different stuff but was never really satisfied with the result. This is how I see the solution:
The app consists of 1 factory:
DocumentFactory: Stores the current page of the document and has the following methods:
setPage(page): sets the page in a factory so different controllers/directives can use this page
broadcast(pageChanged): broadcasts an event after the page has changed so the controllers/directives can listen to this even and react approprialty
2 controllers:
PaginationCtrl[DocumentFactory]: The pagination toolbar controller, updates the page by calling the setPage(method) of the DocumentFactory and listens to the pageChange event to update it's own scope when the page changes in an other controller/directive
DocumentCtrl: The controller of the document
1 Directive:
Page[DocumentFactory]: Resembles a page in the document and has the following methods/listeners
scrollToPage(): If the currentPage equals this pages number (added to the directive as an attribute, scroll to this page)
If this page is visible and the highest in the window of all visible pages, change the current page to this page's number by calling the DocumentFactory setPage(page) method.
Is this the right approach to store the page in a service and use events for the other controllers/directives to listen to it?
Should I create a controller in the directive to listen to the event or add a $watch in the link function to watch for changes in the current page (inherited from the parent Ctrl scope)?
Should I let each directive check if it's page number equals the current page on page change or should I let the DocumentCtrl scroll to the right element?

AS you already have the methods in the controller calling the factory, what you need is to use the '&' isolate scope in the directive to call the method you want.
Create the methods in the controller
var app = angular.module('app', []);
app.controller("Ctrl", function ($scope) {
$scope.goForward = function (){
alert("call to the paginator page up service");
};
$scope.goBack = function (){
alert("call to the paginator page down service");
};
});
Then set up the '&' scope isolates in the directive:
app.directive('paginator', function (){
return{
restrict: 'E',
scope:{
forward: '&',
back: '&'
},
template: '<button ng-click="forward()">Page up</button>' +
'<button ng-click="back()">Page down</button>'
}
});
Finally add the directive to the page with your attributes as defined in the directive:
<paginator forward="goForward()" back="goBack()"></paginator>
Here's the code in Plnkr.
HTH

Related

Sharing data between a directive and a pagination controller in Angular during an API request

I'm sure similar questions have been asked many times on Stack Overflow, but I am still confused.
I have an Angular app that shows a list of articles. The list is long, so it needs to be paginated. For pagination, I'm using UI Bootstrap.
So the app is currently organized like this:
there is a directive ("home directive") that draws the list of articles
there is a UI Bootrstrap directive for pagination with its own controller
or, schematically:
Now, when I change the page in the paginator, I need to send the request to the the server, get a new batch of articles and show them in the home directive:
So the paginator's controller is sending the request, but the home directive needs to be aware of the new response and to use it for drawing a new list of articles.
How is this done in Angular? I've read about services/factories, but am not entirely clear how the home directive becomes aware of the new response from the server. Will I need watchers in the home directory, or is this done differently? Could you please explain?
UPDATE:
OK, here is my (failing) attempt to write a service:
home-directive.js
angular.module('myApp')
.directive("homeDirective", [ 'ArticleService', function(ArticleService){
return {
restrict: 'E',
templateUrl: "home.html",
scope: { },
link: function(scope, element, attrs){
ArticleService.getArticles();
scope.$watch('ArticleService.articles', function () {
console.log(ArticleService.articles);
scope.articles = ArticleService.articles;
});
}
}]);
article-service.js
angular.module('myApp')
.service('ArticleService', ['$http', function($http){
var that = this;
this.getArticles = function(){
$http.get('/path/to/api').success(function(data) {
console.log('working');
that.articles = data;
console.log(that.articles);
});
};
}]);
The call to ArticleService.getArticles() from the directive starts the getArticles() function, as I can see from the logger in the console. The server sends its response. that.articles changes. However, the directive fails to register the change of ArticleService.articles and doesn't get the data.
UPDATE 2
OK, if in the directive, before setting a watcher, I add this line in the directive:
scope.ArticleService = ArticleService;
then it will work.

$locationChangeSuccess triggers four times

I am new to angular Js.
My application flow is as below:
1) I have a view controller wherein, each view controller sets the breadcrumb data with the help of Breadcrumbs factory.
2) Breadcrumbs factory takes data from view controller and attaches data to $location.$$state object.(reason for storing in state object is if back button is pressed, view controller doesn't instantiate so I can refer history data for breadcrumbs ) below is code to attach data to state object:
var state = $location.state();
state.breadcrumb = breadcrumbData;
$location.replace().state(state);
3) I have also created breadcrumb directive on global header which will display breadcrumbs on $locationChangeSuccess event. Directive will take data from $location.state(); which was set in factory.
My problem is when location is changed, $locationChangeSuccess event callback function executes four times.
below is my directive code:
angular.module('cw-ui')
.directive('cwBreadcrumbs', function($location, Breadcrumbs, $rootScope) {
return {
restrict: 'E',
replace: true,
templateUrl: 'UI/Directives/breadcrumb',
link: function($scope, element){
//some code for element...
$rootScope.$on('$locationChangeSuccess', function(event, url, oldUrl, state, oldState){
// get data from history of location state
var data = $location.state();
console.log(data);
});
}
};
});
output is as below:
Object {}
Object {key: "Core/Views/dash:1", view: "Core/Views/dash", parameters: Array[0], breadcrumb: Array[2]}
Object {key: "Core/Views/dash:1", view: "Core/Views/dash", parameters: Array[0]}
Object {key: "Core/Views/dash:1", view: "Core/Views/dash", parameters: Array[0]}
breadcrumb: Array[2] disappears 1st, 3rd and 4th times. I really don't know what is causing this callback function execute four times, and I have no clue about an issue and don't know how to debug. Please help guys!
After running into this myself, the problem lies in the fact you are using the root scope to bind the locationChangeSuccess event from within a directive that is either encountered multiple times on a single page, or encountered multiple times as you revisit the page:
$rootScope.$on('$locationChangeSuccess', function(event, url, oldUrl, state, oldState){
Since you are binding to the rootScope, and the rootScope does not go out of scope, the event binding is not cleaned up for you.
Inside your link function, you should add a listener for the element $destroy, as well as capture the return value from the original bind, so you can later unbind it.
First: capture return value:
var unbindChangeSuccess = $rootScope.$on('$locationChangeSuccess' ...
Next, unbind that value in your destroy method:
element.on('$destroy', function() {
unbindChangeSuccess();
});
That should solve the multiple calls to your locationChangeSuccess! :)

Create search modal directive from angularui modal

I have an application that uses angular-ui bootstrap modal (http://angular-ui.github.io/bootstrap/#/modal) for search from a list and select one of row.
In common using of angular-ui bootstrap modal, we must create two controller (for example ModalDemoCtrl for main modal and ModalInstanceCtrl for modal window).
In second controller, we have two method:
$scope.ok = function () {
$modalInstance.close($scope.selected.item);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
that repeat in several place (when I create several search modal).
How to I create a directive (or service), that contains these two controller and create these two methods inside it.
You are wrong - there is one controller for modal window. Second one is for main page and is not related to modals at all. So if u want to have 10 buttons on page to open 10 different modals, then u will have 11 controllers.
For modals that just displays message or ask to confirm actions it is good to have service, so u can write Myservice.showModal('Are you sure?', function callbackafterok() {...}).
If u just dont like repeating $modalInstance.close - make simple directive for buttons with ng-click binding, to write:
<button closeModalWithData="" >Ok</cancel>
or just
<modalOk/> with template <button ng-click="ok()">Ok</button>...
Petr Averyanov's answer was right. I created a directive and towards the end of the documentation for Angular UI modal directive, When we create a modal window, some property adds to $scope.
One of property is $close that we can use it to close modal.
Like this:
.directive('closeModal', function() {
return {
restrict: 'E',
template: '<button ng-click="$close()">Cancel</button>'
};
})

AngularJS loading message/please wait

How can I achieve the following in AngularJS:
If I have my page that will show some widgets and each widget has a button called refresh. When the button is clicked then the content of that widget is reloaded from server. While the content is reloaded I want to show the user a message within that widget, please wait ... with a possible fading effect.
How can I achieve that in AngularJS?
I kind of taught about having a common service for that purpose and then somehow each widget controller will use that service or something like that, maybe also a directive that will show the actual loading/please wait message?
What is your advise?
P.S. There should be also a loading/please wait message with fading for the whole page, when the route is changing ... like switching between pages.
In my recent project I'm using https://github.com/cgross/angular-busy
Very nice thing, all you have to do is put your promise into $scope, and then add cg-busy attr to your element which should have spinner (beside registering module obviously):
Controller:
$scope.myPromise = restangular.get('something',12).then(function(response) { ... })
Html:
<div cg-busy="myPromise"></div>
You can also customize template that's gonna be displayed (which includes spinner and text message).
there are implementations of this available on github : angular-spinner or angular-sham-spinner. Read this BLOG which details how the spinner works with angularjs
if you want to implement it yourself to be reusable...
app.directive("spinner", function(){
return: {
restrict: 'E',
scope: { enable: "=" },
template: '<div class="spinner" ng-show="enable"><img src="content/spinner.gif"></div>'
}
});
i havent tested the code but directive wont be more complex than above...
As harish point out the right way would be a directive, but theres no need if you want of include another dependency, you could do something like this
You can create a nice CSS3 only loading (so not images required) animation with the help of CssLoad
Create a directive with a linking function so you can call and stop the animations within your controller the angular way:
.directive('appLoading', function(){
return {
restrict: 'E',
templateUrl: 'template-file.html', // or template: 'template html code inline' Display none to the code is important so is not visible if youre not caling the methods
replace: true,
link: function(scope, elem) {
scope.$on('app-start-loading', function(){
elem.fadeIn(); //asumming you have jquery otherwise play with toggleClass and visible and invisible classes
});
scope.$on('app-finish-loading', function(){
elem.fadeOut();
});
}
}
})
Include in your html code the directive: <app-loading></app-loading>
Now all you need to do is call the scope methods in your controller like this:
$scope.$broadcast('app-start-loading'); // to start the loading animation
$scope.$broadcast('app-finish-loading'); // to stop the animation
NOTE: if all your widgets share a scope, the loading may be triggered in all of them

AngularJs - how to handle multiple events

I have an angular application.on click of a tag, I am populating another div. Now I have a requirement to change the route as well on the click of the tag. how can I do this using Angularjs?
You can call a function on the scope as the click event handler.
For example: In your controller you have
$scope.myClickHandler = function() {
populateDiv();
changeRoute();
}
In your HTML template:
<div ng-click="myClickHandler()></div>

Resources