This assignment will introduce you to the front-end technologies used in the Opal app. You will go through an overview of the dependency managers, the set-up of a typical web-app and a coding example in the form of a "hello world" application.
By the end of this module, you should be a bit more familiar with the Opal application set up, the common AngularJS flow, OnsenUI, and ultimately how to set up a project using these technologies.
To help you follow this assignment, you should first familiarize yourself with the following topics. You can use the resources listed below or any others of your choice.
- HTML introduction: W3Schools. Please read all of this guide. It's important for you to understand the basic HTML tags, as Angular builds on top of them to create its own tags which have enhanced functionality.
- JS introduction: W3Schools. This is a great introduction to how JavaScript interacts with the DOM (Document Object Model) natively. Every year the ECMAScript organization gets together and decides on how the JavaScript standard for the web is going to evolve. This will include neat new features, or simply fixes to the previous API. Their main job is to provide an API that JavaScript uses inside a browser to interact with the DOM tree, and other important functions such as requests to a server via get or post requests. Angular builds on top of this basic JavaScript API in two ways: it creates more complex functionality and it wraps this functionality to offer it to the user under the Angular paradigm.
- Git tutorial by Logan Montgomery: Git tutorial. Please go through this tutorial which covers some of the basics of Git.
This may be a lot of new material for you to process. To help, here are some additional resources which you may refer to as needed.
- AngularJS Documentation
- OnsenUI Documentation
- Style Guide for AngularJS
Please use this style guide when writing your AngularJS code. It makes the code clean, consistent, and easy to use for the people who will follow you. - Webpack Documentation
The instructions below will guide you through the process of creating a simple "hello world" application from scratch. After following this mini-tutorial, you should have an idea of what the config files do in an Angular application, how you'd go about creating them and how to create a basic web app, i.e. Hello World the Opal front-end way :)
-
Make sure you're comfortable using the command line terminal in your chosen operating system. You should also have access to an IDE of your choice, for example, WebStorm, which is free for students. In addition, if you're using a Mac without root access, you may need to precede all npm commands with
sudo(e.g.sudo npm install ...). -
Install the latest version of NodeJS. Verify that Node and its package manager, npm, were correctly installed by running
node -vandnpm -vin a terminal. We will be using npm as our front-end dependency manager. -
Create a new directory for this assignment and navigate to it in a terminal. Also open the folder in an IDE.
-
Run the following command:
npm initThis will initialize your project by creating a config file called
package.json. It will ask you common questions relating to your project's set up, such as asking you to provide a description, version number and name for your project. You can press enter without typing an answer to leave the default (shown in parentheses) for any given line. Do this for all lines except author (write your name) and description (writeThe first assignment of Opal bootcamp.).
After completing this setup, apackage.jsonfile will be created in your root project folder. Open it and see what was added. Here, you can change anything you answered duringnpm init. In addition to giving some background information on your project,package.jsonwill be used by npm to track your project's dependencies. As we install dependencies, you will see them automatically added to this file. -
Install webpack, and webpack's dev server and command line, as development dependencies for your project (warnings are fine, as long as there are no errors):
npm install webpack@4.42.1 webpack-dev-server@3.11.2 webpack-cli@3.3.11 --save-devWebpack is a static module bundler for JavaScript applications. We will be using webpack-dev-server to build and serve our application in a browser.
The--save-devflag instructs npm to add the installed dependencies topackage.jsonunder "devDependencies". These dependencies are needed during development, but not when building a production version of the app.
The@symbol is used to specify a specific version to install. When creating a new project from scratch, you generally won't need to specify version numbers (for example,npm install webpack --save-devwill install the latest version). However, this "hello world" project uses specific version of all dependencies to ensure that it still runs correctly years from now (since sometimes the latest version of a dependency can introduce new requirements that aren't covered in this guide).
After running this command, openpackage.json. You will see that these three new dependencies and their version numbers have been added. The command will also have created anode_modulesfolder, which will contain the installed dependencies, and a package-lock.json file, which is a trace of everything that was installed. Bothnode_modulesandpackage-lock.jsonwill contain many more packages than the three you installed. This is because npm installs all of those packages' dependencies as well. -
Next, it's time to install our two major frameworks as front-end dependencies:
npm install angular@1.8.2 --save npm install onsenui@1.3.17 --saveHere, the
--saveflag instructs npm to add the installed dependencies to package.json as actual project dependencies (under "dependencies"). Note than when installing a new dependency using npm, you should always use the --save-dev or --save flag. If you don't, anyone who tries to install your project usingpackage.jsonwill be missing some of the dependencies they need.
In the case of Angular and OnsenUI, newer versions have been released that completely change their usage and syntax. Since we want to use the same older versions as Opal, we must specify which older versions to install.
OnsenUI and AngularJS are the two main JavaScript dependencies we use in the app.- AngularJS provides a framework for the logic of a web-app that builds on top of the MVC design pattern. Here is a small introduction: https://docs.angularjs.org/guide/concepts.
- OnsenUI is a CSS/JavaScript library that provides out-of-the-box components that mimic a mobile user interface. This allows our web-app to look and feel like a mobile app. Look through the start page on OnsenUI https://onsen.io/v1/guide.html, Please note that we will be working with version 1 of this framework. When you're going through the documentation, make sure you're consulting the right version.
After running this command, open package.json. You'll see that it was updated with a new dependencies section that lists both Angular and OnsenUI.
-
Next, create a folder called
srcin the root folder of your project. In it, create anindex.htmlfile which will be the starting point of our web application. Add the following code to this file:<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <title>Opal Hello World</title> </head> <body> <h3>This is index.html</h3> </body> </html> -
Then, inside
src, create a folder calledjs, and in it, a fileapp.js(its full path will besrc/js/app.js). Also create a folder insrccalledviews, and create a file in it calledhello-world.html. We will add to these later. -
Now, we'll set up webpack-dev-server to serve the application. In the root of your project folder (alongside
package.json), create a file calledwebpack.config.js. This file will contain all the configurations we need to use webpack. Add the following content:const CopyPlugin = require('copy-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const path = require('path'); let entry = [ "./src/js/app.js" ]; module.exports = { entry: entry, devtool: 'eval-cheap-source-map', mode: 'development', devServer: { contentBase: './www', compress: true, hot: false, watchContentBase: true, liveReload: true, port: 9001 }, output: { path: path.resolve(__dirname, 'www'), filename: '[name].[hash].js', }, module: { rules: [ { test: /\.html$/, loader: 'raw-loader', }, { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader' } }, { test: /\.css$/, use: ['style-loader', 'css-loader'] }, { test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/, use: [ { loader: 'file-loader', options: { name: '[name].[ext]' }, } ] }, { // Allows Onsen to compile with Webpack test: /onsenui.js/, loader: 'imports-loader?this=>window!exports-loader?window.Modernizr' }, { // Allows Onsen to compile with Webpack test: /onsenui.js/, loader: 'imports-loader?define=>false,module.exports=>false' }, ] }, plugins: [ new CopyPlugin({ patterns: [ { from: './src/views', to: './views' }, ], }), new HtmlWebpackPlugin({ template: './src/index.html', }), ] };Here's an explanation of what each part of this config file is used for:
entry: used to specify the entry point js files for our application. We'll add more to this later.devtool: specifies which method to use to generate source maps, which allow us to debug minified code.mode: affects webpack's optimizations. For now, we're only concerned with usingdevelopmentmode.devServer: these are the configurations for webpack-dev-server, which we'll use to launch the app. Of particular interest here arecontentBase(which should matchoutput.path),watchContentBaseandliveReload, which allow the browser to reload changes to the source files without having to relaunch webpack-dev-server, and finallyport, which can be changed to a different value if the one chosen is already in use.output: the output folder for the application build. Webpack-dev-server only builds in memory, so you won't seewwwbe created, but webpack can also be used to build an app bundle to this folder directly.module.rules: this section contains rules on how to load each type of file while serving the app.plugins: we're using two plugins: CopyPlugin to make the views (which will be added later) accessible in the served app, and HtmlWebpackPlugin to serve our html files.
-
Before we can serve the app using webpack-dev-server, we'll need to install all the dependencies referenced in the
webpack.config.jsfile:npm install copy-webpack-plugin@6.4.0 html-webpack-plugin@3.2.0 --save-dev npm install css-loader@5.2.6 exports-loader@0.7.0 file-loader@5.1.0 imports-loader@0.8.0 raw-loader@4.0.0 style-loader@1.1.3 --save-dev npm install @babel/core@7.9.0 babel-loader@8.1.0 --save-devThe first line installs the required plugins; the second, the loaders used in
modules.rules; and the third, babel, which is used to interpret .js files. -
In addition, to run webpack, we'll set up a short npm script that we can use as a shortcut. Add the
startscript to package.json:"scripts": { "start": "webpack-dev-server --open --watch --progress --colors", "test": "echo \"Error: no test specified\" && exit 1" }, -
Next, try out your webpack setup by running
npm run start(this will launch the script you've just added). A browser window should open (this tutorial will use Chrome as an example), and you should seeThis is index.html. To test for errors, check the console by inspecting the page and choosingconsoleon the top bar of the browser. Also disable the cache by going into thenetworktab of the debugging tools and selecting thedisable cachebox. If you get an error aboutfavicon.ico, it's fine. -
At this point, you've successfully set up a simple html page to run locally in your browser. However, this setup hasn't made use of Angular or OnsenUI yet. Now it's time to start building the app using Angular. An Angular app is bootstrapped by the module config function. To make use of this, we need to add the following. First, add an angular directive (ng-app) to
index.html's body tag to tell Angular where to start nesting the application.<body ng-app="HelloWorld" ng-cloak> ... </body>Here, we have told Angular to start our HelloWorld application from the body tag of our html tree. We have also told Angular to hide placeholders while they load their values using ng-cloak.
Theindex.htmlpage is the only page that will be loaded in the application. Angular will take care of dynamically swapping dependencies in and out within that page. It will also control state via Angular directives which start withng.
Second, instantiate the module, by adding the following toapp.js:import angular from "angular"; import "onsenui/js/onsenui"; import "onsenui/css/onsen-css-components-blue-basic-theme.css"; import "onsenui/css/onsenui.css"; const app = angular .module('HelloWorld', ['onsen']); // Defines the HelloWorld module and its dependenciesHere, we are importing four components. Two Javascript components (Angular and OnsenUI), as well as two stylesheets from OnsenUI. Angular.module will create an Angular module with the specified name (using the same name used in the ng-app directive) and will declare the set of Angular dependencies for that module (
onsenwill be its only dependency). -
Note that we have already linked app.js to
webpack.config.jsto specify that this file is the entry point to our application:let entry = [ "./src/js/app.js" ]; module.exports = { entry: entry, -
We are now ready to add logic to the app. We will create an onsen navigator, an onsen page, and finally add some logic to display "hello world".
In theindex.htmlfile, add an onsen navigator between the tags, and remove the "This is index.html" header:<body ng-app="HelloWorld" ng-cloak> <ons-navigator var="navi" page="./views/hello-world.html"></ons-navigator> </body>This navigator instantiates the global variable
navi, which will allow you to add and remove pages. The url in the page attribute points to the root page for the navigator. The navigator creates a stack from which you can push and pop new pages. See page navigation for details. -
Open the file called
hello-world.htmlin theviewsfolder. This will be the initial view displayed by the navigator. To this view, add the following code:<ons-page ng-controller="HelloWorldController as vm"> <ons-toolbar> <div class="center">Navigation Bar</div> </ons-toolbar> <div style="text-align: center"> <!--TODO: Add a binding from the controller to print hello world on the page.--> </div> </ons-page>Notice the TODO task which will require you to add a binding to a variable from the HelloWorldController. This html page creates and ons-page component, which is the type of component that can be pushed onto an onsen navigator. The ng-controller tag indicates the controller use for this view.
-
Create a new folder called
controllersinsidejs. Create a new file calledhelloWorldController.jsin this folder (its full path will besrc/js/controllers/helloWorldController.js). Add the following contents to this file:(function(){ // Wraps the controller in an Immediately Invoked Function Expression (as per the Johnpapa style guide) to limit variable scope 'use strict'; angular .module('HelloWorld') // Fetches the angular module so that the controller can access it .controller("HelloWorldController", HelloWorldController); // Declares the controller component in the angular module and connects it to the function below HelloWorldController.$inject = []; // Injects the angular dependencies for this controller (there are none) /* @ngInject */ function HelloWorldController() { // Function representing this controller const vm = this; // vm is the equivalent of $scope // TODO: Create and attach a hello variable to the vm object. } })();Notice the
(function(){})();. This will encapsulate the code as to not let variables escape from its scope. Declaring variables in a JavaScript file without function encapsulation risks causing collisions between variables in different files in the project. -
This controller is referenced by the hello-world view, but it needs to be linked to the rest of the application via webpack in order to be correctly loaded on launch. To do this, create a new file
src/js/app.controllers.js:import "./controllers/helloWorldController.js";In
webpack.config.js, add the following line to specify that this file should be read on entry alongsideapp.js:let entry = [ "./src/js/app.js", "./src/js/app.controllers.js" ]; -
If you've left webpack running, it will have reloaded for you every time you saved new changes. However, it will not reload changes to
webpack.config.js. Since you've made changes to this file, you'll need to restart webpack. Do this by cancelling the running process and re-runningnpm run start.
Check that the app still runs correctly and there are no errors in the console. Now, you should see only the navigation bar, since this is the only visual component in the hello-world view. -
Finally, it's time to display "Hello World". In
helloWorldController.js, replace the TODO line with the following:vm.hello = "Hello World!"; // Declare a controller variable than can be used in its viewThen, to use the controller variable 'hello' in the view, in
hello-world.html, replace the TODO line with the following:<h1>{{vm.hello}}</h1>In the view, vm refers to the controller "
as vm", andhellois the variable defined in this controller. The curly brackets are used to express the contents of the variable instead of printingvm.helloin plain text to the screen.
Since you've only made changes to project files (not towebpack.config.js), webpack will have updated the running instance automatically. Look at your browser and check that "Hello World!" is now visible.
Success!!! You have now gone through the basic steps to get an AngularJS app running with Opal's front-end technology stack!
If you have any issues with the above instructions, please contact the bootcamp runner.