I couldn't come up with normal title. Sorry for that.
So the problem is that in a big project, with big business logic there are a lot of modals. And every new modal is the same code with little changes, like templateUrl, controller and such things. This is how pop up is getting called now:
return uibModal.open({
templateUrl: current.path + 'url.html',
controller: 'AppController',
windowClass: 'PopUp',
size: 'md',
resolve: {
disabled: [function () {
return scope.disabled;
}]
}
}).result.then(function( comment ){
record.comment = comment;
})
And this routine never ends. So what I'm interested in is - what is the best practice to reduce the same code(same to this situation) in your project? Should you use service? Or just create global function?
In AngularJS you should always avoid global functions. Services were created for this purpose. I also use $uibModal and was tired of writing the same thing over and over again.
I made a ModalService which allowed me to abstract away a lot of the repetitive code:
function ModalService() {
var ModalService = this;
ModalService.basicModal = function(options) {
var _options = options || {};
return $uibModal.open({
animation: angular.isDefined(_options.animation) ? _options.animation : true,
keyboard: angular.isDefined(_options.keyboard) ? _options.keyboard : true,
backdrop: _options.backdrop || 'static',
size: _options.size || 'sm',
templateUrl: _options.templateUrl || 'templates/modal-message.html', //default template in case user does not provide one
controller: _options.controller || ModalMessageCtrl, // a default controller in case user does not provide one
controllerAs: _options.controllerAs || 'vm',
resolve: options.resolve || {}
});
};
ModalService.simpleModal = function(options) {
...
};
}
You can define many varieties of modals that can be invoked easily from the controller:
ModalService.basicModal();
ModalService.simpleModal();
// etc...
And all of these can accept optional parameters to customize the modal:
ModalService.basicModal({
size: 'lg'
});
ModalService.simpleModal({
templateUrl: "my-custom-template.html",
controller: function($scope) {
//some custom inline controller
}
}).result.then(function() { //do something });
// etc...
You can use $uibModalProvider.options to set the default options during the configuration phase of the application.
app.config(function($uibModalProvider) {
$uibModalProvider.options = {
windowClass: 'PopUp',
size: 'md'
};
});
If you're using UI Bootstrap 2.1.0 or greater you can also leverage angular's component based architecture to remove some of the extra cruft when opening a modal.
Given a component defined as follows:
app.component('myModal', {
bindings: {close: '&', dismiss: '&', resolve: '<'},
controller: MyModalController,
templateUrl: 'myModal.html'
});
You can leverage the component in a modal as follows:
$uibModal.open({
component: 'myModal',
resolve: {
// pass data to component
}
}).result.then(function() {
// Modal closed
}).catch(function() {
// Modal dismissed
});
Related
is it possible to translate title and description of an adf-wiget?
i've tried something like this, but it did not work:
angular.module('adf.widget.myWidget', ['adf.provider'])
.config(function(dashboardProvider, $filter){
dashboardProvider
.widget('myWidget', {
title: $filter('translate')('MYWIDGET.NAME'),
description: $filter('translate')('MYWIDGET.DESCRIPTION'),
templateUrl: '{widgetsPath}/myWidget/src/view.html',
controller: 'myWidgetCtrl',
edit: {
templateUrl: '{widgetsPath}/myWidget/src/edit.html'
}
});
})
.Controller [...]
or is there a way to update those informations in a controller using the $filter?
Thanks a lot
// EDIT:
I've tried an other solution, but it still wont work:
angular.module('adf.widget.myWidget', ['adf.provider'])
.config(function(dashboardProvider){
function getName($filter) { var dcName = $filter('translate')('MYWIDGET.NAME'); return dcName; };
dashboardProvider
.widget('myWidget', {
title: getName(),
description: 'test',
templateUrl: '{widgetsPath}/myWidget/src/view.html',
controller: 'myWidgetCtrl',
edit: {
templateUrl: '{widgetsPath}/myWidget/src/edit.html'
}
});
})
Here is an example of how to use the resolve block
angular.module('adf.widget.myWidget', ['adf.provider'])
.config(function(dashboardProvider, MYWIDGET){
dashboardProvider
.widget('myWidget', {
templateUrl: '{widgetsPath}/myWidget/src/view.html',
controller: 'myWidgetCtrl',
edit: {
templateUrl: '{widgetsPath}/myWidget/src/edit.html'
},
resolve: {
description: function ($filter) {
return $filter('translate')(MYWIDGET.DESCRIPTION)
},
name : function ($filter) {
return $filter('translate')(MYWIDGET.NAME)
}
}
});
})
Then in your controller myWidgetCtrl, inject in the translated name and description values as normal injectables
MYWIDGET should be defined as a constant with values for its NAME and DESCRIPTION properties and then injected into the config block, just the way you injected $filter
Take a look at angular module system to see how to create constants
I solved this issue by translating the values in custom templates.
dashboardProvider.customWidgetTemplatePath('src/...pathToTemplate')
I'am also using custom templates for all edit-templates like editTemplateUrl etc.
(Deleted the duplicated question..)
Im using md-dialog from Material Design and I came across a small issue which causes me a lot of trouble.
I'm using this dialog as a form for creating a new record in db and I need its controller to be loaded from external file. The reason is that I'm using the same dialog in many places of the app (in many other controllers) and I dont want to copy and paste it to each one of them.
I've tried to write it as a service, but the problem is, as I'm binding data from form to the controller I'm using $scope and that way i got "$scope is not defined". When I add $scope as dependencies in that service, I'got injection error.
Do you have any ideas how to load modal controller externally so it will work even with using of $scope?
$scope.showNewContactDialog = function($event) {
var parentEl = angular.element(document.body);
$mdDialog.show({
parent: parentEl,
targetEvent: $event,
templateUrl: 'app/Pages/directory/contacts/newContact.dialog.html',
controller: NewCompanyContactDialogCtrl,
clickOutsideToClose: true,
hasBackdrop: true
});
};
// New User dialog controller
function NewCompanyContactDialogCtrl($scope, $mdDialog) {
var self = this;
$scope.modalIcon = "add";
$scope.modalTitle = 'Nová položka';
$scope.modalAdvanced = true;
// Country Selector
apiCalls.getData(countryUrl, function(response){
$scope.countries = response;
})
// Add New Object
$scope.newItem = function() {
var url = baseUrl + 'new/';
var data = JSON.stringify({
code: $scope.newItem.contactCode,
first_name: $scope.newItem.contactFirstName,
last_name: $scope.newItem.contactLastName,
street: $scope.newItem.contactStreet,
city: $scope.newItem.contactCity,
country: $scope.newItem.contactCountry,
postal: $scope.newItem.contactPostal,
pobox: $scope.newItem.contactPobox,
price_lvl: $scope.newItem.contactPriceLvl,
orgid: $cookies.get('orgid')
});
apiCalls.postData(url, data, function(response){
console.log(response);
// Toast
if(response.status == 201){
$mdToast.show(
$mdToast.simple()
.textContent('Záznam bol vytvorený.')
.position('bottom right')
.action('Skryť')
.highlightAction(true)
.highlightClass('md-warn')
);
$mdDialog.cancel();
}
});
}
To use as service you can do something like:
angular.module('myApp').factory('newCompModal', function($mdDialog){
var parentEl = angular.element(document.body);
function show($event){
return $mdDialog.show({
parent: parentEl,
targetEvent: $event,
templateUrl: 'app/Pages/directory/contacts/newContact.dialog.html',
controller: 'NewCompanyContactDialogCtrl',
clickOutsideToClose: true,
hasBackdrop: true
});
}
return {
show: show
}
});
Then in any controller:
angular.module('myApp').controller('someController',function($scope,newCompModal){
$scope.newCompanyModalShow = newCompModal.show;
})
And pass event in from view
<button ng-click="newCompanyModalShow($event)">New Company</button>
If you need to pass data also from controller to modal you can add another argument and pass it to locals property of $mdDialog or share through another service property
Example of a dialog with external controller:
$mdDialog.show({
scope : scope,
preserveScope : true,
templateUrl : 'template/search.html',
targetEvent : event,
clickOutsideToClose : true,
fullscreen : true,
controller : 'DialogController'
});
And the controller search.js:
(function() {
angular.module('myApp')
.controller('DialogController', DialogController);
DialogController.$inject = ['$scope', '$mdDialog'];
function DialogController($scope, $mdDialog) {
$scope.closeOpenedDialog = closeOpenedDialog;
function closeOpenedDialog() {
$mdDialog.hide();
}
}
})();
If your mdDialog config doesn't recognize your controller name because it belongs to an external file, then instead of doing this:
controller : 'DialogController'
You should load your controller as a directive in your dialog's view:
<md-dialog ng-controller="DialogController">
...
</md-dialog>
I have 2 routes:
app.config(['$routeProvider', '$locationProvider', function(
$routeProvider, $locationProvider) {
$routeProvider.
when("/list/:class", {
controller: "listController",
templateUrl: "DatabaseObject",
reloadOnSearch: true
}).
when("/edit/:class/:id?", {
templateUrl: "editortemplate"
}).
otherwise("/custombrowsingstartpage");
}]);
They both work fine!
What I would like is to be able to render the "editortemplate" of one of the routes within a modal window from the "/list/:class" route.
Within my "listController" I have the modal opening function:
$scope.showEditorPanel = function (size, a, b) {
//console.log($scope);
var modalInstance = $modal.open({
animation: true,
//templateUrl: '#/edit/'+b+'/'+a,
templateUrl: 'editortemplate',
controller: 'editorController',
size: size,
backdrop: true,
scope: $scope
});
The template renders well but I don't know how to pass it the class and id variables the template requires(as shown in its route).
I tried defining route with variables(class== var b, id== var a) instead of template url but no luck:
//templateUrl: '#/edit/'+b+'/'+a,
If you add a 'resolve' object you can send what you want, I believe this is the "Angular" way to do this with $modal :
$scope.showEditorPanel = function (size, a, b) {
var modalInstance = $modal.open({
animation: true,
templateUrl: 'editortemplate',
controller: 'editorController',
size: size,
backdrop: true,
resolve: {
scope: function () {
$scope.properties.class = a;
$scope.properties.id = b;
return $scope.properties;
}
}
});
To pass data to the modal window, you need to use the resolve method of the modal instance. It works just like the same property in the router:
$scope.showEditorPanel = function (size, a, b) {
var modalInstance = $modal.open({
...
resolve: {
class_id: function () { return $scope.class_id }
}
});
Then you need to require that in the the modal window's controller:
function editorController ($modalInstance, $scope, class_id) {
// use the class_id ...
$scope.class_id = class_id;
}
This fixes the 'class_id' in the explicit requirements of the modal controller and will help with troubleshooting down the road. You could have passed it in "secretly" via the $scope, but this is not a good idea (I categorically avoid the $scope property in $modal!). Explicit code is good code!
I just had to define the expected variables as $routParams:
$scope.showEditorPanel = function (size, a, b) {
//console.log($scope);
$routeParams.class=b;
$routeParams.id=a;
var modalInstance = $modal.open({
animation: true,
//templateUrl: '#/edit/TissueSample/'+a,
templateUrl: 'editortemplate',
controller: 'editorController',
size: size,
backdrop: true,
scope: $scope
});
I have a small problem, I am using a route like
.when('/beta/profiles/new', {
controller: 'ProfilesController',
controllerAs: 'vm',
templateUrl: 'profiles/new.html',
resolve: {
action: function() { return "new"; }
}
})
.when('/beta/profiles/index', {
controller: 'ProfilesController',
controllerAs: 'vm',
templateUrl: 'profiles/index.html',
resolve: {
action: function() { return "index"; }
}
})
now in my controller I have something like this:
function ProfilesController(profileService, action) {
var vm = this,
permittedActions = ["index", "new"];
var actions = {
index: function() {
vm.hideProfile = {
currentProfile: null,
showModal: false,
setCurrentProfile: function(profile_id) { this.currentProfile = profile_id },
toggleHide: function() {/*...*/}
}
profileService.all().then(function(data) {
vm.profiles = data.result;
})
},
new: {/*.. */}
}
if(permittedActions[action] > -1) actions[action]();
}
now my question is if I hit a link (in the header,lets say) multiple times in succession, each time I hit profiles/index.html should it re-initialize everything ?
if I want to takethe advantage of angular's dirty checking thing should I put the vm.hideProfile out of the index function ? and If I do that should I also not do the same with vm.profiles = [];?
how/what can I do to check if variables are getting re-initialized and angular's dirty checking is not in play, or is it just common sense!! or should I just have a separate controller for each action?
and in case of re-initialization, is there any better way so that I can keep the hideProiles inside the index because I really don't need my new and show actions to know about hideProfile, since its unnecessary?
A few things that hopefully can help:
Dirty checking is for objects bound to an active $scope (and $rootScope). https://docs.angularjs.org/guide/scope
To use $scope in your controller, (a) inject it. e.g. ProfilesController($scope, profileService, action), (b) put things on it, e.g. $scope.vm = vm;
looking at your code, the raw initialization should be super fast.
Don't think javascript is secure. If security is a real concern for your app, protect at the server.
Lastly on app arch, since angular is an MVC framework: define functions in your controller, and connect them to actions in the view via $scope. eg
$scope.index = function() { // do stuff
if (action !== "index") {return}; // disallowed by route
//continue
}
I have an AngularJS service which communicates with the server and returns
translations of different sections of the application:
angular
.module('utils')
.service('Translations', ['$q','$http',function($q, $http) {
translationsService = {
get: function(section) {
if (!promise) {
var q = $q.defer();
promise = $http
.get(
'/api/translations',
{
section: section
})
.success(function(data,status,headers,config) {
q.resolve(result.data);
})
.error(function(data,status,headers,config){
q.reject(status);
});
return q.promise;
}
}
};
return translationsService;
}]);
The name of the section is passed as the section parameter of the get function.
I'm using AngularJS ui-router module and following design pattern described here
So I have the following states config:
angular.module('app')
.config(['$stateProvider', function($stateProvider) {
$stateProvider
.state('users', {
url: '/users',
resolve: {
translations: ['Translations',
function(Translations) {
return Translations.get('users');
}
]
},
templateUrl: '/app/users/list.html',
controller: 'usersController',
controllerAs: 'vm'
})
.state('shifts', {
url: '/shifts',
resolve: {
translations: ['Translations',
function(Translations) {
return Translations.get('shifts');
}
]
},
templateUrl: '/app/shifts/list.html',
controller: 'shiftsController',
controllerAs: 'vm'
})
This works fine but as you may notice I have to explicitly specify translations in the resolve parameter. I think that's not good enough as this duplicates the logic.
Is there any way to resolve translations globally and avoid the code duplicates. I mean some kind of middleware.
I was thinking about listening for the $stateChangeStart, then get translations specific to the new state and bind them to controllers, but I have not found the way to do it.
Any advice will be appreciated greatly.
Important note:
In my case the resolved translations object must contain the translations data, not service/factory/whatever.
Kind regards.
Let me show you my approach. There is a working plunker
Let's have a translation.json like this:
{
"home" : "trans for home",
"parent" : "trans for parent",
"parent.child" : "trans for child"
}
Now, let's introduce the super parent state root
$stateProvider
.state('root', {
abstract: true,
template: '<div ui-view=""></div>',
resolve: ['Translations'
, function(Translations){return Translations.loadAll();}]
});
This super root state is not having any url (not effecting any child url). Now, we will silently inject that into every state:
$stateProvider
.state('home', {
parent: 'root',
url: "/home",
templateUrl: 'tpl.html',
})
.state('parent', {
parent: 'root',
url: "/parent",
templateUrl: 'tpl.html',
})
As we can see, we use setting parent - and do not effect/extend the original state name.
The root state is loading the translations at one shot via new method loadAll():
.service('Translations', ['$http'
,function($http) {
translationsService = {
data : {},
loadAll : function(){
return $http
.get("translations.json")
.then(function(response){
this.data = response.data;
return this.data;
})
},
get: function(section) {
return data[section];
}
};
return translationsService;
}])
We do not need $q at all. Our super root state just resolves that once... via $http and loadAll() method. All these are now loaded, and we can even place that service into $rootScope:
.run(['$rootScope', '$state', '$stateParams', 'Translations',
function ($rootScope, $state, $stateParams, Translations) {
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
$rootScope.Translations = Translations;
}])
And we can access it anyhwere like this:
<h5>Translation</h5>
<pre>{{Translations.get($state.current.name) | json}}</pre>
Wow... that is solution profiting almost from each feature coming with UI-Router... I'd say. All loaded once. All inherited because of $rootScope and view inheritance... all available in any child state...
Check that all here.
Though this is a very old question, I'd like to post solution which I'm using now. Hope it will help somebody in the future.
After using some different approaches I came up with a beautiful angularjs pattern by John Papa
He suggest using a special service routerHelperProvider and configure states as a regular JS object. I'm not going to copy-paste the entire provider here. See the link above for details. But I'm going to show how I solved my problem by the means of that service.
Here is the part of code of that provider which takes the JS object and transforms it to the states configuration:
function configureStates(states, otherwisePath) {
states.forEach(function(state) {
$stateProvider.state(state.state, state.config);
});
I transformed it as follows:
function configureStates(states, otherwisePath) {
states.forEach(function(state) {
var resolveAlways = {
translations: ['Translations', function(Translations) {
if (state.translationCategory) {
return Translations.get(state.translationCategory);
} else {
return {};
}
}],
};
state.config.resolve =
angular.extend(state.config.resolve || {}, resolveAlways || {});
$stateProvider.state(state.state, state.config);
});
});
And my route configuration object now looks as follows:
{
state: ‘users’,
translationsCategory: ‘users’,
config: {
controller: ‘usersController’
controllerAs: ‘vm’,
url: ‘/users’.
templateUrl: ‘users.html'
}
So what I did:
I implemented the resolveAlways object which takes the custom translationsCategory property, injects the Translations service and resolves the necessary data. Now no need to do it everytime.