Creating and using AngularJS resources.
Creating a New JavaScript Asset
To create a new custom JavaScript file, go to: CREATE NEW > JavaScript, set the Dependency Manager options (Enabled, Global, Use shim), and then select the predefined JavaScript file from the drop-down (Angular module, Angular service, Angular factory, Angular directive, Angular filter, or External lib).
This will set the corresponding Dependency Manager resource type, and the template of the file which you want to create.
Note
Angular service, Angular filter, Angular factory and Angular directive types correspond to the Angular resource in the Dependency Manager.
More information on the Dependency Manager options can be found here.
You can read more about creating the corresponding modules in the official AngularJS documentation.
When creating a new JavaScript of Angular module and External Lib type, it is recommended that you leave the Use shim checkbox unchecked to create an AMD (Asynchronous module definition) module.
This is due to the slight difference between developing an AMD or non-AMD module. The only distinction is that AMD modules have the defined wrapping, while non-AMD modules do not.
AMD modules are also easier to program. In an AMD module, all the dependencies and returned values can be defined inside the module, but data in a non-AMD module must be defined in the Dependency Manager.
For example, you can simply insert the code of a 3rd party module or library into the defined function callback and then set the module dependencies in the array of the first argument of the defined function. If you insert the code of a 3rd party library/module without define wrapping, you will have to set the module dependencies in the Dependency Manager. You can find this under App settings > Resources in your project: Shim > Deps.
Naming
Modules are connected by name, therefore the module and the JavaScript file names must match. Also, only one module can be created within one file.
Services
AngularJS services are substitutable objects that are wired together using a dependency injection. You can use services to organise and share code across your app. There are two popular kind of services:
- service – when injecting service by calling Apperyio.get('name') you will be provided with an instance of the function. The retrieved result will equal to new FunctionYouPassedToService().
- factory – when injecting factory, you will be provided with the value that is returned by invoking the function reference passed to module.factory.
Note
See more details at Stack Overflow discussion.
In Appery.io, you can create custom services by performing CREATE NEW > JavaScript and choosing Angular service in Type drop-down. You will get a template for creating a chosen AngularJS resource:
define( ['require'], function( require ){
function Func( Apperyio ){
/*
this.someValue = "";
this.someFunc = function (){...};
*/
}
return [{
name: 'myService',
type: 'service',
deps: [ 'Apperyio', Func ]
}];
});
For those, who familiar with AngularJS, this template could look wired. This is because Appery.io uses AMD mechanism and RequireJS to asynchronously load resources on demand.
Note
Read this blog post about AngularJS and RequireJS in Appery.io.
Creating Custom Service
To learn more on how to write custom AngularJS services in Appery.io, let us look at the following example:
This example will show how to create a service, which should sum certain numbers in an asynchronously way with following conditions:
- Service method should return promise object.
- If the result of the calculation is more than 100, promise should be rejected, otherwise promise should be resolved as success.
- In both cases, the calculation result should be sent into callback.
An example will be shown in two forms – as service and as factory.
Service
This example uses the $timeout function to implement the asynchronous approach. AngularJS $q service will provide a promise object. Let us add those dependencies and save them in the object:
define( ['require'], function( require ){
return [{
name: 'first_service',
type: 'service',
deps: [ 'Apperyio','$timeout', '$q', Func ]
}];
function Func( Apperyio, $timeout, $q ){
// save injected services for using in method
this.$timeout = $timeout;
this.$q = $q;
this.Apperyio = Apperyio;
};
});
While AngularJS service is used, all the dependencies should be stored inside the object for future use. This is because all object methods should be added to prototype of a service function.
Custom methods should be added to a service via the prototype property. Adding a sum method will looks like this:
Func.prototype.sum = function() {}
When AngularJS service is executed via the Dependency Manager, AngularJS always call the service-function (in our case Func) as new Func() to create a new instance of an object. That’s why prototype is used here.
Note
Note that JavaScript is an interpreted language, so creating a sum function inside the prototype property of a Func function is assignment operation, not the declaration. If the return operator will be before the assignment, sum function will be simply ignored. That is why you should keep the return operator at the very end of the service declaration.
Here is how the whole service looks with sum function:
define( ['require'], function( require ){
function Func( Apperyio, $timeout, $q ){
// save injected services for using in method
this.$timeout = $timeout;
this.$q = $q;
this.Apperyio = Apperyio;
};
Func.prototype.sum = function() {
}
return [{
name: 'first_service',
type: 'service',
deps: [ 'Apperyio','$timeout', '$q', Func ]
}];
});
Next, let us store all incoming arguments into a simple variable. Also, let us store the $timeout service into a variable as well. And lastly, let us write the logic for asynchronous calculations:
func.prototype.sum = function() {
var args = Array.prototype.concat.apply( [], arguments );
var $timeout = this.$timeout;
return this.$q( function( resolve, reject ) {
$timeout( function() {
}, 300 );
} );
}
Lets review in details the code above:
$q service called as function with a single argument – another function which will be called with two arguments:
- resolve function – in case of all calculations (or AJAX request) are successful. As an argument for this function, you can send any results.
- reject function – in case of any error. In this example, this function will be called, if the result of the calculations will be more then 100.
Also, $timeout service with 300 ms delay is used for emulating asynchronous calculations, which are usually done by AJAX requests.
Also, $timeout service with 300 ms delay is used for emulating asynchronous calculations, which are usually done by AJAX requests.
Note
$timeout service can be used for lazy calculations. This can be helpful when you need to complete some complex calculations and simultaneously update the UI. But, the page rendering and JavaScript code interpretation are done in the same thread. Therefore it makes sense to render the UI initially, show spinner (or something that indicates loading process), and then make the calculations. This such approach will prevent the UI to be frozen, because you can make sure that calculations are done once the UI is fully loaded and displayed.
And the last step – calculations:
var result = _.reduce(args, function (sum, arg) {
return sum + arg;
});
if (result > 100) {
reject(result);
} else {
resolve(result);
}
Note
Read more about the reduce method here.
Here is how the service finally looks:
define( [ 'require', 'lodash' ], function( require, _ ) {
function Func( Apperyio, $timeout, $q ) {
// save injected services for using in method
this.$timeout = $timeout;
this.$q = $q;
this.Apperyio = Apperyio;
}
Func.prototype.sum = function() {
var args = Array.prototype.concat.apply( [], arguments );
var $timeout = this.$timeout;
return this.$q( function( resolve, reject ) {
$timeout( function() {
var result = _.reduce( args, function( sum, arg ) {
return sum + arg;
} );
if ( result > 100 ) {
reject( result );
} else {
resolve( result );
}
}, 300 );
} );
};
return [ {
name: 'first_service',
type: 'service',
deps: [ 'Apperyio', '$timeout', '$q', Func ]
} ];
} );
Factory
Here is how the same example will look in case of implementation in factory form:
define( [ 'require', 'lodash' ], function( require, _ ) {
return [ {
name: 'first_factory',
type: 'factory',
deps: [ 'Apperyio', '$timeout', '$q', func ]
} ];
function func( Apperyio, $timeout, $q ) {
// factory always called by AngularJS as simple function,
// so, sum function can be simply returned
return function() {
var args = Array.prototype.concat.apply( [], arguments );
return $q( function( resolve, reject ) {
$timeout( function() {
var result = _.reduce( args, function( sum, arg ) {
return sum + arg;
} );
if ( result > 100 ) {
reject( result );
} else {
resolve( result );
}
}, 300 );
} );
};
}
} );
As you can see, in the case of factory, return operator doesn’t need to be shifted down, because there is no assignments in module body.
More than One Resource in One File
You can create more than one service or factory in one file by adding new items into an array returned by module:
define( [ 'require', 'lodash' ], function( require, _ ) {
//... both implementations can be placed here
return [ {
name: 'first_factory',
type: 'factory',
deps: [ 'Apperyio', '$timeout', '$q', func_factory ]
}, {
name: 'first_service',
/* type of angular resource [factory, filter, service] */
/* angular dependency injection array */
deps: [ 'Apperyio', '$timeout', '$q', func_service ]
}];
});
Executing Service in Scope Functions
To use any AngularJS resource on the page, use the Apperyio.get() method. Go to the page you need and switch to the SCOPE tab. You can create a new one or use already existing methods. Here is how you can use a custom service made in the creating custom service section in your scope methods (controller).
Service
var sumobject = Apperyio.get('first_service');
sumobject.sum(10, 20, 23, 12).then(function (calcresult) {
$scope.resultfromservice = calcresult;
$scope.$apply();
}).catch(function (errorresult) {
alert(error_result);
});
Factory
var sum = Apperyio.get('first_factory');
sum(10, 20, 23, 12).then(function (calcresult) {
$scope.resultfromfactory = calcresult;
$scope.$apply();
}).catch(function (errorresult) {
alert(errorresult);
});
Try to change numbers in sum method, so their sum will be more than 100. You’ll see, that in this case error is triggered.
Loading Resources on Demand
It’s often a good idea to load resources on demand instead of loading them all initially. Loading resources when needed will positively impact on the page loading time. Here is how you can load on demand AngularJS resources:
- Remove resource you want from the global dependencies by going to Project > App settings > Resources and removing the tick in Global column. This way, your resource won’t be loaded during app launching:
- Now, add your resource as a dependency for the page, where you need to use it. Go to Project > Routing and click Manage dependencies right to the needed page. Check your resource and click Save changes.
Now, your resource will be loaded only for the certain page, not for the whole app.
Directives
Directives in AngularJS are used to make custom HTML elements and simplify DOM manipulations. They can also modify the behaviour of new and existing DOM elements by adding custom functionality, such as a datepicker or an autocomplete widget. AngularJS comes with its own set of built-in directives, like ngBind, ngModel, and ngClass, as well as the ability to add your own.
.js files contain JavaScript code that is used to execute instructions in a Web page that uses client-side scripting. These functions may include the validation of form fields, rollover images, drop-down menu creation, etc.
Directives Implementation
Appery.io comes with a wide selection of AngularJS directives which you can make use of.
Below are the links to the directives specifying custom behaviour on events:
- ngBlur
- ngChange
- ngClick
- ngCopy
- ngCut
- ngDblclick
- ngFocus
- ngKeydown
- ngKeypress
- ngKeyup
- ngMousedown
- ngMouseenter
- ngMouseleave
- ngMousemove
- ngMouseover
- ngMouseup
- ngPaste
- ngSubmit
Note
The Autocomplete feature may be useful when entering/editing custom directives to define the app components.
To add a directive to your app in the Appery.io Visual Builder, open the DESIGN tab, select a component (for selecting components, you can also use the COMPONENTS TREE view), and pass the directive on the PROPERTIES tab.
More information on AngularJS directives can be found here and here.
Directive Code Structure
Directive codes should have the following structure:
define( [ 'require' ], function( require ){
return [{
type: 'directive',
name: '<directive name>',
deps: [<function name>]
}];
function <function name>(){
return <directive structure>
}
}
});
– when declaring an AngularJS directive, the naming convention is camelCase.
For example, we would define the name of the directive as navigateTo. However, when you go to use the directive in your HTML, it will be the dash-separated version. That is, our widget would be and not .
– is a JSON.
For example:
{
restrict: 'A',
link: function(scope, el, attrs){
el.bind('click tap', function(scope){
return function(e){
history.back();
scope.$apply();
};
}(scope));
}
Appery.io Predefined Directives
Once a user has created a new Appery.io project, two directives are added by default: backButton and navigateTo.
This can be found under Project > JavaScript.
Using a NavigateTo Directive
The navigateTo directive can be used to manage navigation from one page to another or across multiples pages within the app.
For example, to make your app navigate between the app pages (navigate to the Price page, and click on an item from the List page), you must:
- Rename Screen1 to List and, in the DESIGN view, define the List item with the default Appery.io directive navigate-to (you may use prompts) with its Attribute value = Screen2.
- Create another page named Price leaving Create navigation route for this page checked.
Additionally, you can pass some parameters while navigating between the screens. To do it: - For the List item, add another attribute: navigate-toquantity = 15.
- Now, go to Project > Routing and define the path for Screen2:
- Click TEST, then: Remove frame.
Note
Here you can find more information on route navigation.
Using BackButton Directive
The backButton directive can be used in apps with a hierarchical navigation pattern for managing the browser’s history. The Back button will behave in an appropriate manner for the context of your application.
A simple way to use this directive is as follows:
- Place a Button component on the Price page of your app (see above) and change its Text to Back to the list.
- Add back-button for Attribute name (you may use prompts) and leave the Attribute value field blank.
Creating a Custom Directive
Almost everything we use in AngularJS is a directive. Directives are what makes AngularJS so powerful and responsive.
Although AngularJS comes with powerful directives, some users want to create their own.
Building an App with a Custom Directive
Let’s create a new app that will display the alert Good bye! by clicking on a button placed on the first screen, that will then redirect you to the main screen.
- To add a new directive, go to CREATE NEW > JavaScript.
- Enter the name (function, for example), select Angular directive as Type and click Create JavaScript.
- Then, pass the following code into the code field (replacing the default code):
define( [ 'require' ], function( ){
return [{
type: 'directive',
name: 'newDirective',
deps: ["Apperyio", directive_newDirective]
}];
/**
* More information about directive development you can find in official
* documentation: https://docs.angularjs.org/guide/directive
* This is just a template for common use cases
*/
function directive_newDirective(Apperyio){
return {
restrict: 'A',
link: function (scope, el, attrs) {
el.bind('click tap', function(){
alert("Good bye");
scope.$apply( function(){
Apperyio.navigateTo( "MainScreen" );
} );
});
}
};
}
});
In the code, define the since redirection is required. Also, pass the alert for . The page which you want to be redirected to has already been passed:
- Then, drag & drop a Button component onto Screen1 and define the directive you have just created.
- Now, add another page to the app: MainSreen leaving Create navigation route for this page checked.
- Switch to the MainSreen and insert a Text component with some text.
- Save.
Testing the App
Now, when you click TEST, you’ll go to the first screen with a Click me! button.
After clicking it, an alert is displayed.
After you clicking OK, you will be redirected to MainScreen.
Signalling Errors
Another useful feature of having a code editor is that it can signal the user about incorrect code entries. For example, if a user enters an invalid function, the prompt directive name disappears from the directives list.
Note
To see the changes made, you must refresh the code editor by clicking outside of the code field.