├── LICENSE
├── README.md
├── angular-cli.json
├── e2e
├── app.e2e-spec.ts
├── app.po.ts
└── tsconfig.json
├── karma.conf.js
├── package.json
├── protractor.conf.js
├── src
├── app
│ ├── app.component.css
│ ├── app.component.html
│ ├── app.component.spec.ts
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── app.ts
│ ├── create_filters_object.ts
│ ├── filters
│ │ ├── filters.component.css
│ │ ├── filters.component.html
│ │ ├── filters.component.spec.ts
│ │ └── filters.component.ts
│ ├── format-rating.pipe.ts
│ ├── model.ts
│ ├── rate-button
│ │ ├── rate-button.component.html
│ │ └── rate-button.component.ts
│ ├── talk
│ │ ├── talk.component.css
│ │ ├── talk.component.html
│ │ └── talk.component.ts
│ ├── talks
│ │ ├── talks.component.css
│ │ ├── talks.component.html
│ │ ├── talks.component.spec.ts
│ │ └── talks.component.ts
│ └── watch-button
│ │ ├── watch-button.component.html
│ │ └── watch-button.component.ts
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── favicon.ico
├── index.html
├── main.ts
├── polyfills.ts
├── styles.css
├── test.ts
└── tsconfig.json
└── tslint.json
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Packt
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ## $5 Tech Unlocked 2021!
5 | [Buy and download this Book for only $5 on PacktPub.com](https://www.packtpub.com/product/essential-angular-4/9781788293761)
6 | -----
7 | *If you have read this book, please leave a review on [Amazon.com](https://www.amazon.com/gp/product/1788293762). Potential readers can then use your unbiased opinion to help them make purchase decisions. Thank you. The $5 campaign runs from __December 15th 2020__ to __January 13th 2021.__*
8 |
9 | # Essential Angular
10 | This is the code repository for [Essential Angular](https://www.packtpub.com/application-development/essential-angular?utm_source=github&utm_medium=repository&utm_campaign=9781788293761), published by [Packt](https://www.packtpub.com/?utm_source=github). It contains all the supporting project files necessary to work through the book from start to finish.
11 | ## About the Book
12 | Essential Angular is a concise, complete overview of the key aspects of Angular, written by two Angular core contributors. The book covers the framework’s mental model, its API, and the design principles behind it. This book is fully up to date with the latest release of Angular.
13 |
14 | Essential Angular gives you a strong foundation in the core Angular technology. It will help you put all the concepts into the right places so you will have a good understanding of why the framework is the way it is. Read this book after you have toyed around with the framework, but before you embark on writing your first serious Angular application. This book covers concepts such as the differences between Just-In-Time and Ahead-Of-Time compilation in Angular, alongside NgModules, components and directives. It also goes into detail on Dependency Injection and Change Detection: essential skill for Angular developers to master. The book finishes with a look at testing, and how to integrate different testing methodologies in your Angular code.
15 | ## Instructions and Navigation
16 | All of the code is organized into folders. Each folder starts with a number followed by the application name. For example, Chapter02.
17 |
18 |
19 |
20 | The code will look like the following:
21 | ```
22 | class TalkCmp {
23 | @Input() talk: Talk;
24 | @Output() rate: EventEmitter;
25 | //...
26 | }
27 | ```
28 |
29 |
30 |
31 | ## Related Products
32 | * [Angular Router](https://www.packtpub.com/application-development/angular-router?utm_source=github&utm_medium=repository&utm_campaign=9781787288904)
33 |
34 | * [Introducing AngularJS [Video]](https://www.packtpub.com/web-development/introducing-angularjs-video?utm_source=github&utm_medium=repository&utm_campaign=9781783554218)
35 |
36 | * [AngularJS Directives Cookbook](https://www.packtpub.com/web-development/angularjs-directives-cookbook?utm_source=github&utm_medium=repository&utm_campaign=9781784395896)
37 | ### Download a free PDF
38 |
39 | If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost. Simply click on the link to claim your free PDF.
40 |
https://packt.link/free-ebook/9781788293761
--------------------------------------------------------------------------------
/angular-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "project": {
3 | "version": "1.0.0-beta.24",
4 | "name": "essentials-angular-app"
5 | },
6 | "apps": [
7 | {
8 | "root": "src",
9 | "outDir": "dist",
10 | "assets": [
11 | "assets",
12 | "favicon.ico"
13 | ],
14 | "index": "index.html",
15 | "main": "main.ts",
16 | "test": "test.ts",
17 | "tsconfig": "tsconfig.json",
18 | "prefix": "app",
19 | "mobile": false,
20 | "styles": [
21 | "styles.css"
22 | ],
23 | "scripts": [],
24 | "environments": {
25 | "source": "environments/environment.ts",
26 | "dev": "environments/environment.ts",
27 | "prod": "environments/environment.prod.ts"
28 | }
29 | }
30 | ],
31 | "addons": [],
32 | "packages": [],
33 | "e2e": {
34 | "protractor": {
35 | "config": "./protractor.conf.js"
36 | }
37 | },
38 | "test": {
39 | "karma": {
40 | "config": "./karma.conf.js"
41 | }
42 | },
43 | "defaults": {
44 | "styleExt": "css",
45 | "prefixInterfaces": false,
46 | "inline": {
47 | "style": false,
48 | "template": false
49 | },
50 | "spec": {
51 | "class": false,
52 | "component": true,
53 | "directive": true,
54 | "module": false,
55 | "pipe": true,
56 | "service": true
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/e2e/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { TalksAppPage } from './app.po';
2 |
3 | describe('e2e tests', function() {
4 | let page: TalksAppPage;
5 |
6 | beforeEach(() => {
7 | page = new TalksAppPage();
8 | });
9 |
10 | it('should filter talks by title', () => {
11 | page.navigateTo();
12 |
13 | const title = page.getTitleInput();
14 | title.sendKeys("Are we there");
15 |
16 | expect(page.getTalks().count()).toEqual(1);
17 | expect(page.getTalkText(0)).toContain("Are we there yet?");
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/e2e/app.po.ts:
--------------------------------------------------------------------------------
1 | import {browser, element, by, ElementArrayFinder, ElementFinder} from 'protractor';
2 |
3 | export class TalksAppPage {
4 | navigateTo() {
5 | return browser.get('/');
6 | }
7 |
8 | getTitleInput() {
9 | return element(by.css('input[formcontrolname=title]'));
10 | }
11 |
12 | getTalks() {
13 | return element.all(by.css('talk-cmp'));
14 | }
15 |
16 | getTalkText(index: number) {
17 | return this.getTalks().get(index).geText();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/e2e/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "declaration": false,
5 | "emitDecoratorMetadata": true,
6 | "experimentalDecorators": true,
7 | "module": "commonjs",
8 | "moduleResolution": "node",
9 | "outDir": "../dist/out-tsc-e2e",
10 | "sourceMap": true,
11 | "target": "es5",
12 | "typeRoots": [
13 | "../node_modules/@types"
14 | ]
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/0.13/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', 'angular-cli'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-remap-istanbul'),
12 | require('angular-cli/plugins/karma')
13 | ],
14 | files: [
15 | { pattern: './src/test.ts', watched: false }
16 | ],
17 | preprocessors: {
18 | './src/test.ts': ['angular-cli']
19 | },
20 | mime: {
21 | 'text/x-typescript': ['ts','tsx']
22 | },
23 | remapIstanbulReporter: {
24 | reports: {
25 | html: 'coverage',
26 | lcovonly: './coverage/coverage.lcov'
27 | }
28 | },
29 | angularCli: {
30 | config: './angular-cli.json',
31 | environment: 'dev'
32 | },
33 | reporters: config.angularCli && config.angularCli.codeCoverage
34 | ? ['progress', 'karma-remap-istanbul']
35 | : ['progress'],
36 | port: 9876,
37 | colors: true,
38 | logLevel: config.LOG_INFO,
39 | autoWatch: true,
40 | browsers: ['Chrome'],
41 | singleRun: false
42 | });
43 | };
44 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "essentials-angular-app",
3 | "version": "0.0.0",
4 | "license": "MIT",
5 | "angular-cli": {},
6 | "scripts": {
7 | "ng": "ng",
8 | "start": "ng serve",
9 | "lint": "tslint \"src/**/*.ts\"",
10 | "test": "ng test",
11 | "pree2e": "webdriver-manager update --standalone false --gecko false",
12 | "e2e": "protractor"
13 | },
14 | "private": true,
15 | "dependencies": {
16 | "@angular/common": "^2.3.1",
17 | "@angular/compiler": "^2.3.1",
18 | "@angular/core": "^2.3.1",
19 | "@angular/forms": "^2.3.1",
20 | "@angular/http": "^2.3.1",
21 | "@angular/platform-browser": "^2.3.1",
22 | "@angular/platform-browser-dynamic": "^2.3.1",
23 | "@angular/router": "^3.3.1",
24 | "@angular/material": "2.0.0-beta.1",
25 | "core-js": "^2.4.1",
26 | "rxjs": "^5.0.1",
27 | "ts-helpers": "^1.1.1",
28 | "zone.js": "^0.7.2"
29 | },
30 | "devDependencies": {
31 | "@angular/compiler-cli": "^2.3.1",
32 | "@types/jasmine": "2.5.38",
33 | "@types/node": "^6.0.42",
34 | "angular-cli": "1.0.0-beta.24",
35 | "codelyzer": "~2.0.0-beta.1",
36 | "jasmine-core": "2.5.2",
37 | "jasmine-spec-reporter": "2.5.0",
38 | "karma": "1.2.0",
39 | "karma-chrome-launcher": "^2.0.0",
40 | "karma-cli": "^1.0.1",
41 | "karma-jasmine": "^1.0.2",
42 | "karma-remap-istanbul": "^0.2.1",
43 | "protractor": "~4.0.13",
44 | "ts-node": "1.2.1",
45 | "tslint": "^4.0.2",
46 | "typescript": "~2.0.3"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/lib/config.ts
3 |
4 | /*global jasmine */
5 | var SpecReporter = require('jasmine-spec-reporter');
6 |
7 | exports.config = {
8 | allScriptsTimeout: 11000,
9 | specs: [
10 | './e2e/**/*.e2e-spec.ts'
11 | ],
12 | capabilities: {
13 | 'browserName': 'chrome'
14 | },
15 | directConnect: true,
16 | baseUrl: 'http://localhost:4200/',
17 | framework: 'jasmine',
18 | jasmineNodeOpts: {
19 | showColors: true,
20 | defaultTimeoutInterval: 30000,
21 | print: function() {}
22 | },
23 | useAllAngular2AppRoots: true,
24 | beforeLaunch: function() {
25 | require('ts-node').register({
26 | project: 'e2e'
27 | });
28 | },
29 | onPrepare: function() {
30 | jasmine.getEnv().addReporter(new SpecReporter());
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/src/app/app.component.css:
--------------------------------------------------------------------------------
1 | :host {
2 | display: flex;
3 | flex-direction: column;
4 | }
5 |
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 | Tech Talks
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-unused-variable */
2 |
3 | import {TestBed, async, ComponentFixture, inject} from '@angular/core/testing';
4 | import { AppCmp } from './app.component';
5 | import { AppModule } from './app.module';
6 | import {App} from "./app";
7 |
8 | describe('AppCmp', () => {
9 | let component: AppCmp;
10 | let fixture: ComponentFixture;
11 | let el: Element;
12 |
13 | beforeEach(async(() => {
14 | TestBed.configureTestingModule({
15 | imports: [AppModule]
16 | });
17 | TestBed.compileComponents();
18 | }));
19 |
20 | beforeEach(() => {
21 | fixture = TestBed.createComponent(AppCmp);
22 | component = fixture.componentInstance;
23 | fixture.detectChanges();
24 | el = fixture.debugElement.nativeElement;
25 | });
26 |
27 | it('should filter talks by title', async(inject([App], (app: App) => {
28 | app.model.talks = [
29 | {
30 | "id": 1,
31 | "title": "Are we there yet?",
32 | "speaker": "Rich Hickey",
33 | "yourRating": null,
34 | "rating": 9.0
35 | },
36 | {
37 | "id": 2,
38 | "title": "The Value of Values",
39 | "speaker": "Rich Hickey",
40 | "yourRating": null,
41 | "rating": 8.0
42 | },
43 | ];
44 | fixture.detectChanges();
45 |
46 |
47 | expect(el.innerHTML).toContain("Are we there yet?");
48 | expect(el.innerHTML).toContain("The Value of Values");
49 |
50 | component.handleFiltersChange({
51 | title: 'we',
52 | speaker: null,
53 | minRating: 0
54 | });
55 | fixture.detectChanges();
56 |
57 | expect(el.innerHTML).toContain("Are we there yet?");
58 | expect(el.innerHTML).not.toContain("The Value of Values");
59 | })));
60 | });
61 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import {App} from "./app";
3 | import {Filters} from "./model";
4 |
5 | @Component({
6 | selector: 'app-cmp',
7 | templateUrl: './app.component.html',
8 | styleUrls: ['./app.component.css']
9 | })
10 | export class AppCmp {
11 | constructor(public app: App) {}
12 |
13 | handleFiltersChange(filters: Filters): void {
14 | this.app.model.filters = filters;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { BrowserModule } from '@angular/platform-browser';
2 | import { NgModule } from '@angular/core';
3 | import { ReactiveFormsModule } from '@angular/forms';
4 | import { HttpModule } from '@angular/http';
5 | import { MaterialModule} from '@angular/material';
6 |
7 | import { AppCmp } from './app.component';
8 | import { TalksCmp } from './talks/talks.component';
9 | import { TalkCmp } from './talk/talk.component';
10 | import { WatchButtonCmp } from './watch-button/watch-button.component';
11 | import { RateButtonCmp } from './rate-button/rate-button.component';
12 | import { FormatRatingPipe } from './format-rating.pipe';
13 | import {App} from "./app";
14 | import {createFiltersObject} from "./create_filters_object";
15 | import { FiltersCmp } from './filters/filters.component';
16 |
17 | @NgModule({
18 | declarations: [
19 | AppCmp,
20 | TalksCmp,
21 | TalkCmp,
22 | WatchButtonCmp,
23 | RateButtonCmp,
24 | FormatRatingPipe,
25 | FiltersCmp
26 | ],
27 | imports: [
28 | BrowserModule,
29 | ReactiveFormsModule,
30 | HttpModule,
31 | MaterialModule.forRoot()
32 | ],
33 | providers: [
34 | App,
35 | {provide: 'createFiltersObject', useValue: createFiltersObject}
36 | ],
37 | bootstrap: [AppCmp]
38 | })
39 | export class AppModule { }
40 |
--------------------------------------------------------------------------------
/src/app/app.ts:
--------------------------------------------------------------------------------
1 | import {Talk, Model, Filters} from "./model";
2 |
3 | export class App {
4 | model: Model = {
5 | filters: {speaker: null, title: null, minRating: 0},
6 | talks: [
7 | {
8 | "id": 898,
9 | "title": "Are we there yet?",
10 | "speaker": "Rich Hickey",
11 | "yourRating": null,
12 | "rating": 9.1
13 | },
14 | {
15 | "id": 777,
16 | "title": "The Value of Values",
17 | "speaker": "Rich Hickey",
18 | "yourRating": null,
19 | "rating": 8.5
20 | },
21 | {
22 | "id": 466,
23 | "title": "Simple Made Easy",
24 | "speaker": "Rich Hickey",
25 | "yourRating": null,
26 | "rating": 8.2
27 | },
28 | {
29 | "id": 322,
30 | "title": "Growing a Language",
31 | "speaker": "Guy Steele",
32 | "yourRating": null,
33 | "rating": 8.9
34 | }
35 | ]
36 | };
37 |
38 | get talks(): Talk[] {
39 | const filters = this.model.filters;
40 | return this.model.talks.filter(t => {
41 | const titlePass = filters.title ? t.title.indexOf(filters.title) > -1 : true;
42 | const speakerPass = filters.speaker ? t.speaker.indexOf(filters.speaker) > -1 : true;
43 | const ratingPass = t.rating >= filters.minRating;
44 | return titlePass && speakerPass && ratingPass;
45 | });
46 | }
47 |
48 | rateTalk(talk: Talk, rating: number): void {
49 | talk.yourRating = rating;
50 | }
51 |
52 | watch(talk: Talk): void {
53 | console.log("Watching", talk);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/app/create_filters_object.ts:
--------------------------------------------------------------------------------
1 | import {Filters} from "./model";
2 |
3 | export function createFiltersObject({title, speaker, highRating}: {title: string, speaker: string, highRating: false}): Filters {
4 | const minRating = highRating ? 9 : 0;
5 | return {speaker, title, minRating};
6 | }
7 |
--------------------------------------------------------------------------------
/src/app/filters/filters.component.css:
--------------------------------------------------------------------------------
1 | :host {
2 | padding: 10px;
3 | display: flex;
4 | }
5 |
6 | div {
7 | display: flex;
8 | flex-direction: column;
9 | flex-grow: 1;
10 | }
11 |
12 | md-input-container {
13 | flex-grow: 1;
14 | }
15 |
16 | md-checkbox {
17 | flex-grow: 1;
18 | }
19 |
--------------------------------------------------------------------------------
/src/app/filters/filters.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | High Rating
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/app/filters/filters.component.spec.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-unused-variable */
2 | import {fakeAsync, tick} from '@angular/core/testing';
3 |
4 | import { FiltersCmp } from './filters.component';
5 |
6 | describe('FiltersCmp', () => {
7 | describe("when filters change", () => {
8 | it('should fire a change event after 200 millis', fakeAsync(() => {
9 | const component = new FiltersCmp((v) => v);
10 |
11 | const events = [];
12 | component.change.subscribe(v => events.push(v));
13 |
14 | component.filters.controls['title'].setValue('N');
15 | setTimeout(() => { component.filters.controls['title'].setValue('Ne'); }, 150);
16 | setTimeout(() => { component.filters.controls['title'].setValue('New'); }, 200);
17 |
18 | expect(events).toEqual([]);
19 |
20 | tick(1000);
21 |
22 | // only one item because of debouncing
23 | expect(events).toEqual([
24 | {title: 'New', speaker: null, highRating: false}
25 | ]);
26 |
27 | component.filters.controls['title'].setValue('New!');
28 |
29 | tick(1000);
30 |
31 | expect(events).toEqual([
32 | {title: 'New', speaker: null, highRating: false},
33 | {title: 'New!', speaker: null, highRating: false}
34 | ]);
35 | }));
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/src/app/filters/filters.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, EventEmitter, Output, Inject} from "@angular/core";
2 | import {FormGroup, FormControl} from "@angular/forms";
3 | import 'rxjs/add/operator/debounceTime';
4 |
5 | @Component({
6 | selector: 'filters-cmp',
7 | templateUrl: './filters.component.html',
8 | styleUrls: ['./filters.component.css']
9 | })
10 | export class FiltersCmp {
11 | @Output() change = new EventEmitter();
12 |
13 | filters = new FormGroup({
14 | speaker: new FormControl(),
15 | title: new FormControl(),
16 | highRating: new FormControl(false),
17 | });
18 |
19 | constructor(@Inject('createFiltersObject') createFilters: Function) {
20 | this.filters.valueChanges.debounceTime(200).subscribe((value) => {
21 | this.change.next(createFilters(value));
22 | });
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/app/format-rating.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 |
3 | @Pipe({
4 | name: 'formatRating'
5 | })
6 | export class FormatRatingPipe implements PipeTransform {
7 | transform(value: any, args?: any): any {
8 | return value;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/model.ts:
--------------------------------------------------------------------------------
1 | export interface Talk {
2 | id: number;
3 | title: string;
4 | speaker: string;
5 | yourRating: number;
6 | rating: number;
7 | }
8 |
9 | export interface Filters {
10 | speaker: string;
11 | title: string;
12 | minRating: number;
13 | }
14 |
15 | export interface Model {
16 | talks: Talk[];
17 | filters: Filters;
18 | }
19 |
--------------------------------------------------------------------------------
/src/app/rate-button/rate-button.component.html:
--------------------------------------------------------------------------------
1 | Rate
2 |
--------------------------------------------------------------------------------
/src/app/rate-button/rate-button.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, Input, EventEmitter, Output} from '@angular/core';
2 | import {Talk} from "../model";
3 |
4 | @Component({
5 | selector: 'rate-button',
6 | templateUrl: './rate-button.component.html',
7 | styleUrls: ['./rate-button.component.css']
8 | })
9 | export class RateButtonCmp {
10 | @Input() talk: Talk;
11 | @Output() rate = new EventEmitter();
12 |
13 | promptRating(): void {
14 | const value = prompt("Enter rating");
15 | if (value) {
16 | this.rate.next(+value);
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/talk/talk.component.css:
--------------------------------------------------------------------------------
1 | :host {
2 | margin: 10px;
3 | }
4 |
5 | md-card {
6 | display: flex;
7 | }
8 |
9 | #rating-column {
10 | margin-right: 20px;
11 | }
12 |
13 | #rating {
14 | display: block;
15 | font-size: 25px;
16 | }
17 |
18 | #title {
19 | display: block;
20 | font-size: 20px;
21 | }
22 |
23 | #speaker {
24 | display: block;
25 | font-size: 15px;
26 | margin-bottom: 20px;
27 | }
28 |
--------------------------------------------------------------------------------
/src/app/talk/talk.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Rating
4 | {{talk.rating | formatRating}}
5 |
6 |
7 |
8 | {{talk.title}}
9 | {{talk.speaker}}
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/app/talk/talk.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, Input} from '@angular/core';
2 | import {Talk} from "../model";
3 | import {App} from "../app";
4 |
5 | @Component({
6 | selector: 'talk-cmp',
7 | templateUrl: './talk.component.html',
8 | styleUrls: ['./talk.component.css']
9 | })
10 | export class TalkCmp {
11 | @Input() talk: Talk;
12 |
13 | constructor(private app: App) {}
14 |
15 | handleRate(newRating: number): void {
16 | this.app.rateTalk(this.talk, newRating);
17 | }
18 |
19 | handleWatch(): void {
20 | this.app.watch(this.talk);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/app/talks/talks.component.css:
--------------------------------------------------------------------------------
1 | :host {
2 | display: flex;
3 | flex-direction: column;
4 | }
5 |
--------------------------------------------------------------------------------
/src/app/talks/talks.component.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/app/talks/talks.component.spec.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-unused-variable */
2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
3 | import { By } from '@angular/platform-browser';
4 | import {DebugElement, NO_ERRORS_SCHEMA} from '@angular/core';
5 |
6 | import { TalksCmp } from './talks.component';
7 |
8 | describe('TalksCmp', () => {
9 | let component: TalksCmp;
10 | let fixture: ComponentFixture;
11 |
12 | beforeEach(async(() => {
13 | TestBed.configureTestingModule({
14 | declarations: [TalksCmp],
15 | schemas: [NO_ERRORS_SCHEMA]
16 | })
17 | .compileComponents();
18 | }));
19 |
20 | beforeEach(() => {
21 | fixture = TestBed.createComponent(TalksCmp);
22 | component = fixture.componentInstance;
23 | fixture.detectChanges();
24 | });
25 |
26 | it('should render a list of talks', () => {
27 | component.talks = [
28 | { title: 'Are we there yet?' },
29 | { title: 'The Value of Values' }
30 | ];
31 | fixture.detectChanges();
32 |
33 | const s = fixture.debugElement.nativeElement;
34 | const ts = s.querySelectorAll("talk-cmp");
35 |
36 | expect(ts.length).toEqual(2);
37 | expect(ts[0].talk.title).toEqual('Are we there yet?');
38 | expect(ts[1].talk.title).toEqual('The Value of Values');
39 | });
40 | });
41 |
--------------------------------------------------------------------------------
/src/app/talks/talks.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, Input} from '@angular/core';
2 | import {Talk} from '../model';
3 |
4 |
5 | @Component({
6 | selector: 'talks-cmp',
7 | templateUrl: './talks.component.html',
8 | styleUrls: ['./talks.component.css']
9 | })
10 | export class TalksCmp {
11 | @Input() talks: Talk[];
12 | }
13 |
--------------------------------------------------------------------------------
/src/app/watch-button/watch-button.component.html:
--------------------------------------------------------------------------------
1 | Watch
2 |
--------------------------------------------------------------------------------
/src/app/watch-button/watch-button.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, Input, EventEmitter, Output} from '@angular/core';
2 | import {Talk} from "../model";
3 |
4 | @Component({
5 | selector: 'watch-button',
6 | templateUrl: './watch-button.component.html',
7 | styleUrls: ['./watch-button.component.css']
8 | })
9 | export class WatchButtonCmp {
10 | @Input() talk: Talk;
11 | @Output() watch = new EventEmitter();
12 |
13 | handleWatch(): void {
14 | this.watch.next(null);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // The file contents for the current environment will overwrite these during build.
2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do
3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead.
4 | // The list of which env maps to which file can be found in `angular-cli.json`.
5 |
6 | export const environment = {
7 | production: false
8 | };
9 |
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Essential-Angular/ceba14eda96810f3514797ae8592514b24c8fdcb/src/favicon.ico
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | EssentialsAngularApp
6 |
7 |
8 |
9 |
10 |
11 |
12 | Loading...
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import './polyfills.ts';
2 |
3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
4 | import { enableProdMode } from '@angular/core';
5 | import { environment } from './environments/environment';
6 | import { AppModule } from './app/app.module';
7 |
8 | if (environment.production) {
9 | enableProdMode();
10 | }
11 |
12 | platformBrowserDynamic().bootstrapModule(AppModule);
13 |
--------------------------------------------------------------------------------
/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | // This file includes polyfills needed by Angular 2 and is loaded before
2 | // the app. You can add your own extra polyfills to this file.
3 | import 'core-js/es6/symbol';
4 | import 'core-js/es6/object';
5 | import 'core-js/es6/function';
6 | import 'core-js/es6/parse-int';
7 | import 'core-js/es6/parse-float';
8 | import 'core-js/es6/number';
9 | import 'core-js/es6/math';
10 | import 'core-js/es6/string';
11 | import 'core-js/es6/date';
12 | import 'core-js/es6/array';
13 | import 'core-js/es6/regexp';
14 | import 'core-js/es6/map';
15 | import 'core-js/es6/set';
16 | import 'core-js/es6/reflect';
17 |
18 | import 'core-js/es7/reflect';
19 | import 'zone.js/dist/zone';
20 |
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/src/test.ts:
--------------------------------------------------------------------------------
1 | import './polyfills.ts';
2 |
3 | import 'zone.js/dist/long-stack-trace-zone';
4 | import 'zone.js/dist/proxy.js';
5 | import 'zone.js/dist/sync-test';
6 | import 'zone.js/dist/jasmine-patch';
7 | import 'zone.js/dist/async-test';
8 | import 'zone.js/dist/fake-async-test';
9 | import { getTestBed } from '@angular/core/testing';
10 | import {
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting
13 | } from '@angular/platform-browser-dynamic/testing';
14 |
15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
16 | declare var __karma__: any;
17 | declare var require: any;
18 |
19 | // Prevent Karma from running prematurely.
20 | __karma__.loaded = function () {};
21 |
22 | // First, initialize the Angular testing environment.
23 | getTestBed().initTestEnvironment(
24 | BrowserDynamicTestingModule,
25 | platformBrowserDynamicTesting()
26 | );
27 | // Then we find all the tests.
28 | let context = require.context('./', true, /\.spec\.ts$/);
29 | // And load the modules.
30 | context.keys().map(context);
31 | // Finally, start Karma to run the tests.
32 | __karma__.start();
33 |
--------------------------------------------------------------------------------
/src/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "",
4 | "declaration": false,
5 | "emitDecoratorMetadata": true,
6 | "experimentalDecorators": true,
7 | "lib": ["es6", "dom"],
8 | "mapRoot": "./",
9 | "module": "es6",
10 | "moduleResolution": "node",
11 | "outDir": "../dist/out-tsc",
12 | "sourceMap": true,
13 | "target": "es5",
14 | "typeRoots": [
15 | "../node_modules/@types"
16 | ]
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer"
4 | ],
5 | "rules": {
6 | "class-name": true,
7 | "comment-format": [
8 | true,
9 | "check-space"
10 | ],
11 | "curly": true,
12 | "eofline": true,
13 | "forin": true,
14 | "indent": [
15 | true,
16 | "spaces"
17 | ],
18 | "label-position": true,
19 | "max-line-length": [
20 | true,
21 | 140
22 | ],
23 | "member-access": false,
24 | "member-ordering": [
25 | true,
26 | "static-before-instance",
27 | "variables-before-functions"
28 | ],
29 | "no-arg": true,
30 | "no-bitwise": true,
31 | "no-console": [
32 | true,
33 | "debug",
34 | "info",
35 | "time",
36 | "timeEnd",
37 | "trace"
38 | ],
39 | "no-construct": true,
40 | "no-debugger": true,
41 | "no-duplicate-variable": true,
42 | "no-empty": false,
43 | "no-eval": true,
44 | "no-inferrable-types": true,
45 | "no-shadowed-variable": true,
46 | "no-string-literal": false,
47 | "no-switch-case-fall-through": true,
48 | "no-trailing-whitespace": true,
49 | "no-unused-expression": true,
50 | "no-use-before-declare": true,
51 | "no-var-keyword": true,
52 | "object-literal-sort-keys": false,
53 | "one-line": [
54 | true,
55 | "check-open-brace",
56 | "check-catch",
57 | "check-else",
58 | "check-whitespace"
59 | ],
60 | "radix": true,
61 | "semicolon": [
62 | "always"
63 | ],
64 | "triple-equals": [
65 | true,
66 | "allow-null-check"
67 | ],
68 | "typedef-whitespace": [
69 | true,
70 | {
71 | "call-signature": "nospace",
72 | "index-signature": "nospace",
73 | "parameter": "nospace",
74 | "property-declaration": "nospace",
75 | "variable-declaration": "nospace"
76 | }
77 | ],
78 | "variable-name": false,
79 | "whitespace": [
80 | true,
81 | "check-branch",
82 | "check-decl",
83 | "check-operator",
84 | "check-separator",
85 | "check-type"
86 | ],
87 |
88 | "use-input-property-decorator": true,
89 | "use-output-property-decorator": true,
90 | "use-host-property-decorator": true,
91 | "no-input-rename": true,
92 | "no-output-rename": true,
93 | "use-life-cycle-interface": true,
94 | "use-pipe-transform-interface": true,
95 | "component-class-suffix": false,
96 | "directive-class-suffix": false,
97 | "no-access-missing-member": true,
98 | "templates-use-public": true,
99 | "invoke-injectable": true
100 | }
101 | }
102 |
--------------------------------------------------------------------------------