Is it possible to declare a global function in a backbone / marionnette app, that calls a precise module's function?
This question may sound weird since I personally would suggest not to do this at all, because using modules is awesome and keep app structure clean. I need a global function because there is a module / function that I use everywhere, in all single view / template of my app. The function is lang.item(). For example:
lang.item(ID, p1, p2, p3, ... )
I use this everywhere, with a module I called "lang". Example :
define([ 'backbone', 'lang'], function(Backbone, lang){
return Backbone.View.extend({
...
template: _.template('<%= lang.item("welcome") %> <%=username%>'),
onError: function(){
this.ui.error.html(lang.item('wrong_login'));
}
...
});
});
I always have to include the "lang" module so I can use it. Since I call it and use it everywhere, I would love to use a simple global function like lang(ITEM_ID), anywhere, when I need it, without having to include the "lang" module everytime.
define(['Backbone'], function(Backbone){
return Backbone.View.extend({
...
template: _.template('<%= lang("welcome") %> <%=username%>'),
onError: function(){
this.ui.error.html(lang('wrong_login'));
}
...
});
});
Someone have a solution?
I suggest using this in your main javascript file, used by requireJS:
define('init', function (require) {
...
window.lang = require('lang');
...
});
// Initialize the application.
require(['init']);
Personally, I do not find this offensive to any coding standards. Let me know if it worked.
Related
I'm building a small two-language app with the use of angular-translate. I want to have a language switcher in every view (controller). I'm trying to figure out how to put the code responsible for language switching into every controller. The code looks like this:
var langSwitch = $Scope.setLang = function (langKey) {
$translate.use(langKey);
};
So far I've figured that I can create a factory that looks like this:
app.factory('langSwitch', function ($rootScope, $translate) {
var langSwitch = $rootScope.setLang = function (langKey) {
$translate.use(langKey);
};
return langSwitch;
});
and inject it into controllers in this maner:
app.controller('HomeCtrl', function (langSwitch) {
// normal controller code here
});
This works but 1) I'm using $rootScope and I have a feeling this is bad practice & 2) jsHint screams that "langSwitch" is not defined. Maybe there is a simpler way to make the function global without putting it into every controller?
I'm still pretty new to Angular so don't scream at me :) Thanks.
edit
My view:
<button ng-click="setLang('en_GB')">English</button>
<button ng-click="setLang('pl_PL')">Polish</button>
Although you got the idea, you overcomplicated things a bit. You could declare the service as follows:
app.service('langSwitch', function ($translate) {
this.setLang = function (langKey) {
$translate.use(langKey);
};
});
And then inject langSwitch in the controller responsible for lang switching, as you already did. No need to inject $rootScope in the service.
You don't need $rootScope indeed unless you need to process some global events in your application. All services and factories in angular are singletons by default. That means once it created, it will be passed as the same instance in every place it is declared as a dependency. So if you want to share data and functionality between different controllers - the services will suit fine. You can change your factory code to:
app.factory('langSwitch', function($translate) {
return {
setLang: function(langKey) {
$trasnlate.use(langKey);
};
};
});
I just went through a code where the programmer has created an array of controllers and then added the array to a module.
Following is the type of code :-
// the array of controllers
var controllers = {};
//adding a controller to array
controllers.ExampleController1 = function(){};
controllers.ExampleController2 = function(){};
//adding the controllers to module
var ABCmodule=angular.module('ABCmodule',[]);
ABCmodule.controller(controllers);
I realized that the controller array is made in global scope. Isnt that dangerous? Moreover is this good style of coding. I prefer only making one variable for module and adding all controllers inside that module. What is the best way to do this?
This particlar setup can be solved by:
for (var ctrl in controllers) {
if(controllers.hasOwnProperty(ctrl)) {
ABCmodule.controller(ctrl);
}
}
That being said, why not just add controllers directly to module which would be declared first? Also loop can be simplified using lodash's _.forOwn()
angular
.module('abc', [])
.controller('ctrl1', ctrl1)
.controller('ctrl2', ctrl2); // ...
function ctrl1() {
}
function ctrl2() {
}
Angular does this internally all the time, but you don't have to accept its source code style as paragon. It is ok while you keep the variables inside IIFE.
If you want to keep the style in ng vein, you can do like this:
ctrlModule.config(function ($controllerProvider, controllers) {
angular.forEach(controllers, function (constructor, name) {
$controllerProvider.register(name, constructor);
});
});
ctrlModule.constant('controllers', { ... });
What is the best practice when you have a Factory with like 4 related methods, each of those is really long (200+ lines of code) and you want to avoid having a huge file of 800+ lines of code?
One solution is to create 4 factories under the same module , each one exposing a single method and in its own file. Then inject all of them in the controller that requires them.
Is there a better solution? I'd like to create the Factory once, then add methods to it like I was doing module augmentation using the module pattern. Then I just need to inject the Factory once and have all its methods available.
I'd like to create the Factory once, then add methods to it like I was doing module augmentation using the module pattern. Then I just need to inject the Factory once and have all its methods available.
Yes, that will work:
// In your main module file.
angular.module('myModule', []);
// file1.js
angular.module('myModule').factory('BigService1', function(){
// Your actual implementation here.
console.log('I am BigService1');
});
// file2.js
angular.module('myModule').factory('BigService2', function(){
// Your actual implementation here.
console.log('I am BigService2');
});
// ... additional files
// Separate file/service to aggregate your big factory services.
angular.module('myModule').service('AggregateService', [
// Add dependencies
'BigService1','BigService2',
function(BigService1, BigService2){
// Return an object that exposes the factory interfaces.
return {
service1: BigService1,
service2: BigService2
};
}]);
You could also arrange your code the old vanilla js style and then access those libraries in your services like this:
var Mirko = { };
Mirko.FunService = {
getAllSomething : function (p1, p2) {
},
...
};
angular.module('myModule').factory('BigService', function(){
return {
methodOne : Mirko.getAllSomething,
...
};
});
You will end up with one object Mirko that you can access outside the scope of your angular app, but it will in no way differ from other externals api's (not written for angular) you would want to use in your app. The way you handle your own 'external' api can be done the oldschool fashion way, one file per 'class' e.g. 'FunService'.
It might not be the prettiest solution but it will be an easy abstraction.
Just saying...
Maybe segment your methods through other factories, which can be injected to your "main" factory :
// file 1
angular.module('foo').factory('segment1', function () {
return {
method: function () {
// ... super long method
}
};
});
// file 2
angular.module('foo').factory('segment2', function () {
return {
method: function () {
// ... super long method
}
};
});
// your main factory file
angular.module('foo').factory('myFactory', function (segment1, segment2) {
return {
method1: segment1.method,
method2: segment2.method
};
}
Generally, I'd do the following and there would be an ng-app in my HTML:
var myApp = angular.module("myApp", []);
myApp.controller("AttributeCtrl", function ($scope) {
$scope.master = {
name: "some name"
};
});
However, I need to manually bootstrap angular because I'm only using it in a part of my app that is loaded via jQuery's $.load(). If I do the following:
main.js - this is where the page I want to use angular on is being pulled in
$("#form").load(contextPath + url, params, function() {
angular.bootstrap($("#angularApp"));
});
And then the page being pulled in has it's own javascript:
function AttributeCtrl($scope) {
$scope.master = { name: "some name"};
}
This works, however, ideally, I'd like my controllers to be scoped at the module level. So I modified the above code like so
main.js
$("#form").load(contextPath + url, params, function() {
angular.bootstrap($("#angularApp", ["myApp"]));
});
and then...
var app = angular.module("myApp"); // retrieve a module
app.controller("AttributeCtrl", function($scope) {
$scope.master = { name: "some name"};
});
Retrieving the module this way doesn't seem to work, though. Am I doing something wrong?
You cannot create a controller after you've bootstrapped the app. See the documentation for angular.bootstrap.
You should call angular.bootstrap() after you've loaded or defined your modules. You cannot add controllers, services, directives, etc after an application bootstraps.
I don't know if this is just in the example code you have here but:
angular.bootstrap($("#angularApp", ["myApp"]));
should be
angular.bootstrap($("#angularApp"), ["myApp"]);
Your code for retrieving the module should work.
Updated
They updated the documentation and now it reads like this
Each item in the array should be the name of a predefined module or a
(DI annotated) function that will be invoked by the injector as a run
block. See: {#link angular.module modules}
It seems a bug.
The way you implemented to retrieve the module is correct. Just quote it from the doc to make it clear since it may not be well-known.
When passed two or more arguments, a new module is created. If passed
only one argument, an existing module (the name passed as the first
argument to module) is retrieved.
For the problem you mentioned, long story short...
The bootstrap function calls createInjector with the module list ['ng', ['ngLocale', function(){...}] , 'myApp'] (the last one is the module you passed in)
function bootstrap(element, modules) {
...
var injector = createInjector(modules);
Inside createInjector(), it calls loadModules for each module passed in
function createInjector(modulesToLoad) {
forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); });
And loadModules calls angularModule, which is initialized as angularModule = setupModuleLoader(window);, which creates the object window.angular.module
function loadModules(modulesToLoad){
....
var moduleFn = angularModule(module); // triggers the error
The the error occurs, since angularModule takes 2nd parameter as requires. Without it, it will throws an exception on this line (line 1148) throw Error('No module: ' + name);
Reported: https://github.com/angular/angular.js/issues/3692
Not sure if this counts as a bug or an implementation decision (albeit a seemingly poor one). Adding an empty array solves the undefined require problem that you were having and should solve your problem overall.
var app = angular.module("myApp", []); // create a module
app.controller("AttributeCtrl", function($scope) {
$scope.master = { name: "some name"};
});`
Also, in your fiddle you call {{name}} which won't render. You should be calling {{master.name}}
Edit
Thank you all for the downvotes .. Here's a working example. Good luck!
http://plnkr.co/edit/UowJpWYc1UDryLLlC3Be?p=preview
This seems like it should be easy, but I can't find documentation on it. I'm wondering how to make an Angular component (let's say a filter) reusable in different apps. I've made a simple filter that formats a phone number, and I'd like to be able to use it in any app. Currently it is declared like this:
var myModule = angular.module('myModule', ['ngSanitize']);
myModule.filter('formatFilter', function() {
return function(input) {
return input.replace(/(\d{3})(\d{3})(\d{4})/, "($1) $2-$3");
}
});
The question is how to make this filter reusable. Right now it's just attached to 'myModule', but how can I take it out of this file so I can reuse it elsewhere too?
You started OK by encapsulating your filter in a separate module. For other apps to use it, those apps will just need to include source code of your filter and declare dependency on a module:
angular.module('myApp',['myModule'])
You would probably like to rename your filter-holding module to something more meaningful, something like phoneFormatter or sth.
Just as an example :
This is a reusable filter declared as a module, so only needs to be injected on the app:
Filter:
angular.module("lov-filter", []).filter('StartOnPage', function () {
return function (input, start) {
start = +start;
return input.slice(start);
}
});
App:
var demoApp = angular.module('demoApp', ['lov-filter'])
.config(function () {
});
Used on this angular directive: https://github.com/rolandocc/lov-angular-directive