├── 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 | 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 | 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 | --------------------------------------------------------------------------------