I am working on creating a custom directive which creates a button within an ng-repeat and will bind to the current item. Once the button is clicked, a $dialog opens with a select2 box to select and change the user. On save the user replaces the current user within the ng-repeat. I have all of this working (though would appreciate any code review/revision help since this is my first directive), the part where I am stuck is how to now incorporate a call back to an angular service to push the updated user back to the database.
Here is my current plunker: http://plnkr.co/edit/nzR3mjeLK3kPynSIjE7t?p=preview
Is the best option here to call a function in the parent controller to call the service update?
Is there a way the directive itself should handle this?
Any help or direction would be greatly appreciated.
You can inject a service module with the data persistence implemented inside the service in order to separate the logic from the directive.
.factory('service', function () {
var service = {
doSomething: function(result){
//logic to do the data persistence
console.log('saved!');
}
}
return service;
})
.directive('delegateApproval', function($dialog, service) { //inject the 'service'
$scope.openDelegate = function(approval) {
d.open().then(function(result) {
$scope.approval._approver._user._lastName = '';
$scope.approval._approver._user._firstName = result.empName;
service.doSomething(approval); // consume the service
});
};
Demo
Related
To continue my previous post: ui-calendar is not working, not rendering events
I am using angularjs ui-calendar that accepts an array of events as ng-model to display them in the calendar.
The problem is that the events must be ready and defined before the browser renders the calendar.
I have events defined like this in the controller:
events2 = [{
title: 'Event1',
start: '2017-09-27',
end: '2017-09-27'
}];
events3 = [{
title: 'Event2',
start: '2017-09-28',
end: '2017-09-28'
}];
Then in $onInit (or in the controller's constructor) I set:
this.eventSources = [this.events2, this.events3];
And in HTML I define component as:
<div ng-model="vm.eventSources" ui-calendar></div>
Everything works fine.
However, as you see, the events are hardcoded and are available before the calendar is rendered.
In my real situation I get event by calling Google Calendar API and the events are returned with a delay (via a promise), so, when I get the events from a Google Service, I need to trigger a REDRAW of the calendar.
How can I do that? ui-calendar is not a simple HTML control that will display changes when ng-model is changed, but a third party component, so, in this situation I need to trigger a re-render of the calendar after the events arrive.
Any idea how to do that?
This is the method that calls Google Service to get the events:
getCalendarEvents() {
console.log('getCalendarEvents');
this.service.getCalendarEvents().then(events => {
//this.events = <CalendarEvent[]>events;
this.eventSources = [this.events2, this.events3];
});
}
Even if I use hardcoded events after then the calendar does not display them, it needs to be redrawn with a new value in this.eventSources
Please advice! using $scope.$apply() has no effect.
Use Resolve in angularJS.
Resolve will call your service bring the data before loading/executing controller, so you can get your data before your view and controller are loaded/executed and then you can inject it as a dependency in your controller to get the data.
Here is my working code snippet.
.state('abc', {
controller : 'controller',
controllerAs : 'controllerAs',
resolve : {
getProjectData : ['MyService', '$state',
function(MyService, $state) {
return MyService.getProjectDetails();
}]
}
app.controller('controller', ['getProjectData', function(getProjectData) {
console.log(getProjectData); // Here you will get all your data
vm.data = getProjectData;
//setting schedule list in fullcalendar
vm.eventSources.push({events : vm.data});
}]);
<div ui-calendar="controller.uiConfig.calendar" id="calendar" ng-model="controller.eventSources"></div>
I am very much new in AngularJS and due to which I am facing an issue and not able to understand the exact problem. The code which I tried is worked in normal javascript code but now I want to use custom service (factory function). Actually, I have a textarea where user can input their text and then they can do the formating by selecting any of the text. (discovertheweb.in/woweditor - this is existing site which I have created now I want to apply angular in this code). Now, I have created a custom service to check whether the user select any content or not. I have used the below code in my custom services and try to get the selection start, end point so that I can get the selected text from the textarea field. But, the problem is that I am getting 0 value for both selection start and end point. When i used the same code inside directive it works but try to get the value through service it is showing 0 for both. Please find the below code and let me know the code which I missed out here.
Custom Service:
(function(){
"use strict";
var wsApp = angular.module("WorkApp");
wsApp.factory("InputCheckService", function(){
var defaultText = document.getElementById("input-text-area");
var selStart = defaultText.selectionStart;
var selEnd = defaultText.selectionEnd;
var selectedText;
if(selStart != selEnd){
selectedText = defaultText.value.substring(selStart, selEnd);
}else{
selectedText = null;
}
return {
selStart: selStart,
defaultText: defaultText,
selEnd: selEnd,
selectedText: selectedText
};
});
}());
The directive where I called this services. I already included the service inside the main controller in different file.
(function(){
"use strict";
var wsApp = angular.module("WorkApp");
wsApp.directive("generalDirective", generalDirective);
function generalDirective(){
return {
scope: true,
controller:function($scope, InputCheckService){
$scope.collapsed = false;
$scope.CollpasePanel = function(){
$scope.collapsed = !$scope.collapsed;
};
$scope.updatePara = function(){
alert(InputCheckService.defaultText+"Selection start: "+InputCheckService.selStart+" Selection End: "+ InputCheckService);
/**
* defaultText: defaultText,
selStart: selStart,
selEnd: selEnd,
selectedText: selectedText
*/
}
},
templateUrl: 'directive/general-directive.html'
};
}
}());
If you need any more details, please let me know.
Thanks in advance for your time and suggestion.
Regards,
Robin
You should not use service to manipulate DOM element. You should manipulate DOM only at directives. Your problem is you DONT have anywhere to listen to TEXTAREA SELECTION EVENT and your service will not update the data inside. I have created a fiddle for your problem. The watchSelection directive is based on this answer from stackoverflow. Something you should notice :
I use service only to store data. Something like selStart, selEnd or paragraphContent and provide some API to retrieve the data
.factory("InputCheckService", function () {
return {
setSelStart: function (start) {
selStart = start;
},
.....
},
});
On the watchSelection directive, you watch for the mouseup event and will perform update the service so that it will store value you need and later you can retrieve it from other directives or controllers.
elem.on('mouseup', function () {
var start = elem[0].selectionStart;
//store it to the service
InputCheckService.setSelStart(start);
});
In your generalDirective directive you can get value from your service and angular will auto update the view for you.
Hope it helps.
I have problem with bootstraping my angular application. I'm wondering, where should I place interface actions? Should I create special controller or maybe a service with these actions? For example: I want to create object responsible for showing alerts and call MyUiModule.showAlert(message) from any place within app.
Which approach is better - controller, service, something else?
As you want to use the showAlert functionality from any place within the app, the best place to put it is in service.
Now in any controller, where you want to avail this service, just inject it as dependency and call the method.
app.factory("MyUiModule", function() {
var UiModule = {};
UiModule.showAlert = function(message) {
// construct the interface to show the alert
// It could be angular-ui modal window
};
return UiModule;
});
In your controller where you want to use it:
app.controller("MyController", function($scope, MyUiModule) {
$scope.login = function() {
MyUiModule.showAlert("Please enter the username");
}
});
When using AngularJS and doing a redirect using $location.path('/path') the new page takes a while to load, especially on mobile.
Is there a way to add a progress bar for loading? Maybe something like YouTube has?
For a progress bar as YouTube has, you can take a look at ngprogress. Then just after the configuration of your app (for example), you can intercept route's events.
And do something like:
app.run(function($rootScope, ngProgress) {
$rootScope.$on('$routeChangeStart', function() {
ngProgress.start();
});
$rootScope.$on('$routeChangeSuccess', function() {
ngProgress.complete();
});
// Do the same with $routeChangeError
});
Since #Luc's anwser ngProgress changed a bit, and now you can only inject ngProgressFactory, that has to be used to create ngProgress instance. Also contrary to #Ketan Patil's answer you should only instantiate ngProgress once:
angular.module('appRoutes', ['ngProgress']).run(function ($rootScope, ngProgressFactory) {
// first create instance when app starts
$rootScope.progressbar = ngProgressFactory.createInstance();
$rootScope.$on("$routeChangeStart", function () {
$rootScope.progressbar.start();
});
$rootScope.$on("$routeChangeSuccess", function () {
$rootScope.progressbar.complete();
});
});
if it is the next route that takes time to load e.g. making ajax call before the controller is run (resolve config on route) then make use of $route service's $routeChangeStart, $routeChangeSuccess and $routeChangeError events.
register a top level controller (outside ng-view) that listens to these events and manages a boolean variable in its $scope.
use this variable with ng-show to overlay a "loading, please wait" div.
if the next route loads fast (i.e. its controller runs quickly) but data that are requested by the controller take a long to load then, i'm afraid, you have to manage the visibility state of spinners in your controller and view.
something like:
$scope.data = null;
$http.get("/whatever").success(function(data) {
$scope.data = data;
});
<div ng-show="data !== null">...</div>
<div ng-show="data === null" class="spinner"></div>
use angular-loading-bar
Standalone demo here ..
https://github.com/danday74/angular-loading-bar-standalone-demo
Here is a working solution which I am using in my application. ngProgress is the best library out there for showing load-bars when changing urls.
Remember to inject the ngProgressFactory instead of ngProgress, as opposed to Luc's solution.
angular.module('appRoutes', []).run(function ($rootScope, ngProgressFactory) {
$rootScope.$on("$routeChangeStart", function () {
$rootScope.progressbar = ngProgressFactory.createInstance();
$rootScope.progressbar.start();
});
$rootScope.$on("$routeChangeSuccess", function () {
$rootScope.progressbar.complete();
});
});
Update Nov-2015 - After analyzing this approach with chrome timings, I have observed that this would not be the correct way for adding a loading bar. Sure, the loading bar will be visible to visitors,but it will not be in sync with actual page load timings.
Currently I have an Angular.js page that allows searching and displays results. User clicks on a search result, then clicks back button. I want the search results to be displayed again but I can't work out how to trigger the search to execute. Here's the detail:
My Angular.js page is a search page, with a search field and a search
button. The user can manually type in a query and press a button and
and ajax query is fired and the results are displayed. I update the URL with the search term. That all works fine.
User clicks on a result of the search and is taken to a different page - that works fine too.
User clicks back button, and goes back to my angular search page, and the correct URL is displayed, including the search term. All works fine.
I have bound the search field value to the search term in the URL, so it contains the expected search term. All works fine.
How do I get the search function to execute again without the user having to press the "search button"? If it was jquery then I would execute a function in the documentready function. I can't see the Angular.js equivalent.
On the one hand as #Mark-Rajcok said you can just get away with private inner function:
// at the bottom of your controller
var init = function () {
// check if there is query in url
// and fire search in case its value is not empty
};
// and fire it after definition
init();
Also you can take a look at ng-init directive. Implementation will be much like:
// register controller in html
<div data-ng-controller="myCtrl" data-ng-init="init()"></div>
// in controller
$scope.init = function () {
// check if there is query in url
// and fire search in case its value is not empty
};
But take care about it as angular documentation implies (since v1.2) to NOT use ng-init for that. However imo it depends on architecture of your app.
I used ng-init when I wanted to pass a value from back-end into angular app:
<div data-ng-controller="myCtrl" data-ng-init="init('%some_backend_value%')"></div>
Try this?
$scope.$on('$viewContentLoaded', function() {
//call it here
});
I could never get $viewContentLoaded to work for me, and ng-init should really only be used in an ng-repeat (according to the documentation), and also calling a function directly in a controller can cause errors if the code relies on an element that hasn't been defined yet.
This is what I do and it works for me:
$scope.$on('$routeChangeSuccess', function () {
// do something
});
Unless you're using ui-router. Then it's:
$scope.$on('$stateChangeSuccess', function () {
// do something
});
angular.element(document).ready(function () {
// your code here
});
Dimitri's/Mark's solution didn't work for me but using the $timeout function seems to work well to ensure your code only runs after the markup is rendered.
# Your controller, including $timeout
var $scope.init = function(){
//your code
}
$timeout($scope.init)
Hope it helps.
You can do this if you want to watch the viewContentLoaded DOM object to change and then do something. using $scope.$on works too but differently especially when you have one page mode on your routing.
$scope.$watch('$viewContentLoaded', function(){
// do something
});
You can use angular's $window object:
$window.onload = function(e) {
//your magic here
}
Another alternative:
var myInit = function () {
//...
};
angular.element(document).ready(myInit);
(via https://stackoverflow.com/a/30258904/148412)
Yet another alternative if you have a controller just specific to that page:
(function(){
//code to run
}());
When using $routeProvider you can resolve on .state and bootstrap your service. This is to say, you are going to load Controller and View, only after resolve your Service:
ui-routes
.state('nn', {
url: "/nn",
templateUrl: "views/home/n.html",
controller: 'nnCtrl',
resolve: {
initialised: function (ourBootstrapService, $q) {
var deferred = $q.defer();
ourBootstrapService.init().then(function(initialised) {
deferred.resolve(initialised);
});
return deferred.promise;
}
}
})
Service
function ourBootstrapService() {
function init(){
// this is what we need
}
}
Found Dmitry Evseev answer quite useful.
Case 1 : Using angularJs alone:
To execute a method on page load, you can use ng-init in the view and declare init method in controller, having said that use of heavier function is not recommended, as per the angular Docs on ng-init:
This directive can be abused to add unnecessary amounts of logic into your templates. There are only a few appropriate uses of ngInit, such as for aliasing special properties of ngRepeat, as seen in the demo below; and for injecting data via server side scripting. Besides these few cases, you should use controllers rather than ngInit to initialize values on a scope.
HTML:
<div ng-controller="searchController()">
<!-- renaming view code here, including the search box and the buttons -->
</div>
Controller:
app.controller('SearchCtrl', function(){
var doSearch = function(keyword){
//Search code here
}
doSearch($routeParams.searchKeyword);
})
Warning : Do not use this controller for another view meant for a different intention as it will cause the search method be executed there too.
Case 2 : Using Ionic:
The above code will work, just make sure the view cache is disabled in the route.js as:
route.js
.state('app', {
url : '/search',
cache : false, //disable caching of the view here
templateUrl : 'templates/search.html' ,
controller : 'SearchCtrl'
})
Hope this helps
I had the same problem and only this solution worked for me (it runs a function after a complete DOM has been loaded). I use this for scroll to anchor after page has been loaded:
angular.element(window.document.body).ready(function () {
// Your function that runs after all DOM is loaded
});
You can save the search results in a common service which can use from anywhere and doesn't clear when navigate to another page, and then you can set the search results with the saved data for the click of back button
function search(searchTerm) {
// retrieve the data here;
RetrievedData = CallService();
CommonFunctionalityService.saveSerachResults(RetrievedData);
}
For your backbutton
function Backbutton() {
RetrievedData = CommonFunctionalityService.retrieveResults();
}
call initial methods inside self initialize function.
(function initController() {
// do your initialize here
})();