$ git clone https://github.com/fabiandev/angular-quiz-app.git
$ cd angular-quiz-app
$ npm install
$ npm start
Since the app is hosted on a free Heroku instance, it may need some time to boot up.
Fun fact: The app is named "hue", because the course at University, where this project was born, was called "Hypermedia User Experience Engineering".
This app uses:
- Angular as a front-end framework.
- Express with spotify-web-api-node for the server.
- Spotify API for the quiz data.
- css-animator and animate.css for animations.
- Materialize for styling.
- Material icons and Icons8 Flat Color Icons for icons.
- iScroll for a better mobile scrolling experience.
We use npm or yarn and jspm (currently jspm@beta
) to install dependencies.
For simplicity gulp and jspm can be installed globally, by using the -g
flag.
$ npm install -g gulp jspm@beta
Make sure that you have Node.js installed, npm comes with it. You can check with
node --version
. For faster npm dependency installs, use yarn.
To install development dependencies, used e.g. in gulp tasks use:
$ npm install --save-dev module-name
or
yarn add module-name --dev
To install application dependencies, used on the server side use:
$ npm install --save module-name
or
yarn add module-name
To install client side dependencies, use jspm:
$ jspm install modulename && npm run update-paths
The execution of update-paths
is required to have all jspm package also mapped
in compilerOptions.paths
of tsconfig.json
.
jspm also supports
install npm:modulename
andinstall github:user/repo
Typings are used to tell the TypeScript compiler about definitions. You can install them via npm just like this:
$ npm install @types/core-js
The production build should be used to compile the app for deployment. It will do it's best to keep the target files as small as possible.
$ gulp build
A development build performs similar tasks as a production build, but makes debugging a lot easier.
$ gulp dev-build
During development make use of the watch task, which does not need to compile the entire app on each change. The application will be transpiled on demand in the browser.
$ gulp watch
You may also execute
gulp watch-build
to perform those actions only once.
Before starting the server copy .env.example
in /server
and name it .env
, get
Spotify API keys and fill them in.
NEVER PASTE YOUR KEYS IN THE EXAMPLE FILE OR ANYWHERE ELSE!
To start the sever type:
$ npm start
or
yarn start
The server will be started with the
dist
directory as root, and a built version of the app will be used. Make sure to rungulp build
orgul dev-build
first.
To start a development server type:
$ npm start dev
or
yarn start dev
The server will be started on the very top level of the application code. All files (including dependencies) are transpiled on-demand in the browser. While developing, make sure
gulp watch
is running, to pick up index.html and less-files changes.
This app supports deployment on Heroku:
$ git push heroku master
Just make sure to set the correct Node and npm environment variables:
NODE_ENV=production
NPM_CONFIG_PRODUCTION=false
The npm production flag must be set to false that we can build the app on Heroku after pushing the repository.
And of course you have to add SPOTIFY_CLIENT_ID
and SPOTIFY_CLIENT_SECRET
as environment variables.
Optionally you may also add NEW_RELIC_LICENSE_KEY
to enable monitoring by New Relic.
If you do not provide a license key, New Relic simply won't be enabled.
Tip: If you deploy to Heroku, you can add the New Relic Add-on for free.
This section covers how to configure the build tasks, the server and the application itself.
You can set some configuration for TypeScript in tsconfig.json
and in
tslint.json
. All other configuration can be found in config.js
.
Please take a closer look at the config.js
file comment's on the configuration
properties for more detailed explanations.
Type: String
The folder, where the source files can be found, e.g. ./src
(no trailing slash!).
Type: String
The folder, where the built app will go to. Again, do not use a trailing slash.
dist is short for distribution.
Type: String|Array<String>
Define which files should or shouldn't be watched, when using gulp watch
.
You can use the globbing pattern here.
Type: Object
This configuration holds the command, that will be executed later via gulp when building the application.
You can type jspm
in the command line to see all available options.
jspm internally uses the SystemJS builder.
Type: Object
Configure the less gulp task, to create CSS files from LESS files.
Type: Object
Define a globbing pattern, which TypeScript files to lint for errors.
Type: String
Define the index file for the application.
Type: Object
Files to copy without further processing.
Type: Array<Object>
Files to copy into a desired location, but only preserve the path from the set base
.
You can set environment variables in server/.env
(not included in this repo).
Copy server/.env.example
and rename it to .env
.
Other options are set in server/config/app.js
for a production server, or
/server.config/app.dev.js
for a development server.
Note, that the index.html is not inside the src
, but on the very top level
of the application code.
The
index.html
is processed by gulp-preprocess.
For the dev server or a dev build, src/js/main.dev.ts
will be used. For a production build, src/js/main.prod.ts
is the entry point of the app.
It is possible to add questions and answers to this app, by performing a few steps discussed by examples below.
To add a custom answer yesno
, create a directory yesno
in src/js/components/quiz/answers
, containing the following files:
answer-yesno.component.ts
answer-yesno.html
answer-yesno.css
(optional)index.ts
// answer-yesno.component.ts
import { Component } from '@angular/core';
import { GenericAnswer } from 'app/components';
import template from './answer-yesno.html';
import mainStyle from './answer-yesno.css';
import commonStyle from '../common.css';
@Component({
selector: 'answer-yesno',
template: template,
styles: [
commonStyle,
mainStyle
]
})
export class AnswerYesNoComponent extends GenericAnswer {
protected init(): void {
}
}
<!-- answer-yesno.html -->
<div class="row answers">
<div class="col l6 m12 s12">
<input #checkYes id="answer{{question.id}}_yes" type="checkbox" (change)="answerChanged(checkYes.checked ? 'yes' : null)" [disabled]="!checkYes.checked && hasAnswer()">
<label htmlFor="answer{{question.id}}_yes" class="grey-text text-darken-3">Yes</label>
</div>
<div class="col l6 m12 s12">
<input #checkNo id="answer{{question.id}}_no" type="checkbox" (change)="answerChanged(checkNo.checked ? 'no' : null)" [disabled]="!checkNo.checked && hasAnswer()">
<label htmlFor="answer{{question.id}}_no" class="grey-text text-darken-3">No</label>
</div>
</div>
// index.ts
export * from './answer-yesno.component';
Finally add an export to src/js/components/quiz/answers/answers.ts
:
export * from './yesno/index';
To add a new question type simple
, define it in src/js/components/quiz/questions/types.ts
:
export enum QuestionType {
// ...
Simple,
// ...
}
Also create a directory simple
in src/js/components/quiz/questions
, containing the following files:
question-simple.component.ts
question-simple.html
question-simple.css
(optional)index.ts
// question-simple.component.ts
import { Component } from '@angular/core';
import { GenericQuestion, QuestionType } from 'app/components';
import template from './question-simple.html';
import mainStyle from './question-simple.css';
import commonStyle from '../common.css';
@Component({
selector: 'question-simple',
template: template,
styles: [
commonStyle,
mainStyle
]
})
export class QuestionSimpleComponent extends GenericQuestion {
public static type = QuestionType.Simple;
public init(): void {
this.setTitle('Do you like this quiz?');
this.setCorrectAnswer('yes');
}
}
<!-- question-simple.html -->
<div class="col s12 m8 offset-m2 l6 offset-l3">
<div class="card-panel grey lighten-5 z-depth-1">
<div class="row valign-wrapper">
<div class="col s2">
<i class="material-icons circle green white-text">sentiment_satisfied</i>
</div>
<div class="col s10 truncate">
{{ question.title }}
</div>
</div>
</div>
</div>
<!-- Using the previously created answer type -->
<answer-yesno [question]="question" (onAnswerChange)="answerChanged($event)">
<!-- It is possible to omit the following, but it gives you
the ability to add a custom icon, image, or anything else
to the status overview -->
<ng-template #statusTemplate>
<i class="material-icons circle green white-text">sentiment_satisfied</i>
</ng-template>
// index.ts
export * from './question-simple.component';
Finally add an export to src/js/components/quiz/questions/questions.ts
:
export * from './simple/index';
The application will automatically consider the added question and will use it randomly.
Try it! You can copy-paste the code above.