├── Cover.PNG
├── LICENSE
├── README.md
├── ch01
├── .editorconfig
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── tasks.json
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── public
│ └── favicon.ico
├── src
│ ├── app
│ │ ├── app.component.css
│ │ ├── app.component.html
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.config.ts
│ │ └── app.routes.ts
│ ├── index.html
│ ├── main.ts
│ └── styles.css
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
├── ch02
├── .gitignore
├── app.ts
├── customer.ts
├── functions.ts
├── interfaces.ts
├── tsconfig.json
├── user.ts
└── variables.ts
├── ch03
├── .editorconfig
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── tasks.json
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── public
│ └── favicon.ico
├── src
│ ├── app
│ │ ├── app.component.css
│ │ ├── app.component.html
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.config.ts
│ │ ├── app.routes.ts
│ │ ├── product-detail
│ │ │ ├── product-detail.component.css
│ │ │ ├── product-detail.component.html
│ │ │ ├── product-detail.component.spec.ts
│ │ │ └── product-detail.component.ts
│ │ ├── product-list
│ │ │ ├── product-list.component.css
│ │ │ ├── product-list.component.html
│ │ │ ├── product-list.component.spec.ts
│ │ │ └── product-list.component.ts
│ │ └── product.ts
│ ├── index.html
│ ├── main.ts
│ └── styles.css
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
├── ch04
├── .editorconfig
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── tasks.json
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── public
│ └── favicon.ico
├── src
│ ├── app
│ │ ├── app.component.css
│ │ ├── app.component.html
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.config.ts
│ │ ├── app.routes.ts
│ │ ├── copyright.directive.spec.ts
│ │ ├── copyright.directive.ts
│ │ ├── numeric.directive.spec.ts
│ │ ├── numeric.directive.ts
│ │ ├── product-detail
│ │ │ ├── product-detail.component.css
│ │ │ ├── product-detail.component.html
│ │ │ ├── product-detail.component.spec.ts
│ │ │ └── product-detail.component.ts
│ │ ├── product-list
│ │ │ ├── product-list.component.css
│ │ │ ├── product-list.component.html
│ │ │ ├── product-list.component.spec.ts
│ │ │ └── product-list.component.ts
│ │ ├── product.ts
│ │ ├── sort.pipe.spec.ts
│ │ └── sort.pipe.ts
│ ├── index.html
│ ├── main.ts
│ └── styles.css
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
├── ch05
├── .editorconfig
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── tasks.json
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── public
│ └── favicon.ico
├── src
│ ├── app
│ │ ├── app.component.css
│ │ ├── app.component.html
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.config.ts
│ │ ├── app.routes.ts
│ │ ├── app.settings.ts
│ │ ├── copyright.directive.spec.ts
│ │ ├── copyright.directive.ts
│ │ ├── favorites.service.spec.ts
│ │ ├── favorites.service.ts
│ │ ├── favorites.ts
│ │ ├── favorites
│ │ │ ├── favorites.component.css
│ │ │ ├── favorites.component.html
│ │ │ ├── favorites.component.spec.ts
│ │ │ └── favorites.component.ts
│ │ ├── numeric.directive.spec.ts
│ │ ├── numeric.directive.ts
│ │ ├── product-detail
│ │ │ ├── product-detail.component.css
│ │ │ ├── product-detail.component.html
│ │ │ ├── product-detail.component.spec.ts
│ │ │ └── product-detail.component.ts
│ │ ├── product-list
│ │ │ ├── product-list.component.css
│ │ │ ├── product-list.component.html
│ │ │ ├── product-list.component.spec.ts
│ │ │ └── product-list.component.ts
│ │ ├── product-view
│ │ │ ├── product-view.component.css
│ │ │ ├── product-view.component.html
│ │ │ ├── product-view.component.spec.ts
│ │ │ ├── product-view.component.ts
│ │ │ ├── product-view.service.spec.ts
│ │ │ └── product-view.service.ts
│ │ ├── product.ts
│ │ ├── products.service.spec.ts
│ │ ├── products.service.ts
│ │ ├── sort.pipe.spec.ts
│ │ └── sort.pipe.ts
│ ├── index.html
│ ├── main.ts
│ └── styles.css
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
├── ch06
├── .editorconfig
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── tasks.json
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── public
│ └── favicon.ico
├── src
│ ├── app
│ │ ├── app.component.css
│ │ ├── app.component.html
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.config.ts
│ │ ├── app.routes.ts
│ │ ├── app.settings.ts
│ │ ├── copyright.directive.spec.ts
│ │ ├── copyright.directive.ts
│ │ ├── key-logger
│ │ │ ├── key-logger.component.css
│ │ │ ├── key-logger.component.html
│ │ │ ├── key-logger.component.spec.ts
│ │ │ └── key-logger.component.ts
│ │ ├── product-detail
│ │ │ ├── product-detail.component.css
│ │ │ ├── product-detail.component.html
│ │ │ ├── product-detail.component.spec.ts
│ │ │ └── product-detail.component.ts
│ │ ├── product-list
│ │ │ ├── product-list.component.css
│ │ │ ├── product-list.component.html
│ │ │ ├── product-list.component.spec.ts
│ │ │ └── product-list.component.ts
│ │ ├── product.ts
│ │ ├── products.service.spec.ts
│ │ ├── products.service.ts
│ │ ├── sort.pipe.spec.ts
│ │ └── sort.pipe.ts
│ ├── index.html
│ ├── main.ts
│ └── styles.css
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
├── ch07
├── .editorconfig
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── tasks.json
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── public
│ └── favicon.ico
├── src
│ ├── app
│ │ ├── app.component.css
│ │ ├── app.component.html
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.config.ts
│ │ ├── app.routes.ts
│ │ ├── app.settings.ts
│ │ ├── copyright.directive.spec.ts
│ │ ├── copyright.directive.ts
│ │ ├── product-detail
│ │ │ ├── product-detail.component.css
│ │ │ ├── product-detail.component.html
│ │ │ ├── product-detail.component.spec.ts
│ │ │ └── product-detail.component.ts
│ │ ├── product-list
│ │ │ ├── product-list.component.css
│ │ │ ├── product-list.component.html
│ │ │ ├── product-list.component.spec.ts
│ │ │ └── product-list.component.ts
│ │ ├── product.ts
│ │ ├── products.service.spec.ts
│ │ ├── products.service.ts
│ │ ├── sort.pipe.spec.ts
│ │ └── sort.pipe.ts
│ ├── index.html
│ ├── main.ts
│ └── styles.css
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
├── ch08
├── .editorconfig
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── tasks.json
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── public
│ └── favicon.ico
├── src
│ ├── app
│ │ ├── app.component.css
│ │ ├── app.component.html
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.config.ts
│ │ ├── app.routes.ts
│ │ ├── app.settings.ts
│ │ ├── auth.interceptor.spec.ts
│ │ ├── auth.interceptor.ts
│ │ ├── auth.service.spec.ts
│ │ ├── auth.service.ts
│ │ ├── auth
│ │ │ ├── auth.component.css
│ │ │ ├── auth.component.html
│ │ │ ├── auth.component.spec.ts
│ │ │ └── auth.component.ts
│ │ ├── copyright.directive.spec.ts
│ │ ├── copyright.directive.ts
│ │ ├── product-create
│ │ │ ├── product-create.component.css
│ │ │ ├── product-create.component.html
│ │ │ ├── product-create.component.spec.ts
│ │ │ └── product-create.component.ts
│ │ ├── product-detail
│ │ │ ├── product-detail.component.css
│ │ │ ├── product-detail.component.html
│ │ │ ├── product-detail.component.spec.ts
│ │ │ └── product-detail.component.ts
│ │ ├── product-list
│ │ │ ├── product-list.component.css
│ │ │ ├── product-list.component.html
│ │ │ ├── product-list.component.spec.ts
│ │ │ └── product-list.component.ts
│ │ ├── product.ts
│ │ ├── products.service.spec.ts
│ │ ├── products.service.ts
│ │ ├── sort.pipe.spec.ts
│ │ └── sort.pipe.ts
│ ├── index.html
│ ├── main.ts
│ └── styles.css
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
├── ch09
├── .editorconfig
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── tasks.json
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── public
│ └── favicon.ico
├── src
│ ├── app
│ │ ├── app.component.css
│ │ ├── app.component.html
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.config.ts
│ │ ├── app.routes.ts
│ │ ├── app.settings.ts
│ │ ├── auth.guard.spec.ts
│ │ ├── auth.guard.ts
│ │ ├── auth.service.spec.ts
│ │ ├── auth.service.ts
│ │ ├── auth
│ │ │ ├── auth.component.css
│ │ │ ├── auth.component.html
│ │ │ ├── auth.component.spec.ts
│ │ │ └── auth.component.ts
│ │ ├── cart
│ │ │ ├── cart.component.css
│ │ │ ├── cart.component.html
│ │ │ ├── cart.component.spec.ts
│ │ │ └── cart.component.ts
│ │ ├── checkout.guard.spec.ts
│ │ ├── checkout.guard.ts
│ │ ├── copyright.directive.spec.ts
│ │ ├── copyright.directive.ts
│ │ ├── product-create
│ │ │ ├── product-create.component.css
│ │ │ ├── product-create.component.html
│ │ │ ├── product-create.component.spec.ts
│ │ │ └── product-create.component.ts
│ │ ├── product-detail
│ │ │ ├── product-detail.component.css
│ │ │ ├── product-detail.component.html
│ │ │ ├── product-detail.component.spec.ts
│ │ │ └── product-detail.component.ts
│ │ ├── product-list
│ │ │ ├── product-list.component.css
│ │ │ ├── product-list.component.html
│ │ │ ├── product-list.component.spec.ts
│ │ │ └── product-list.component.ts
│ │ ├── product.ts
│ │ ├── products.resolver.spec.ts
│ │ ├── products.resolver.ts
│ │ ├── products.service.spec.ts
│ │ ├── products.service.ts
│ │ ├── sort.pipe.spec.ts
│ │ ├── sort.pipe.ts
│ │ ├── user.routes.ts
│ │ └── user
│ │ │ ├── user.component.css
│ │ │ ├── user.component.html
│ │ │ ├── user.component.spec.ts
│ │ │ └── user.component.ts
│ ├── index.html
│ ├── main.ts
│ └── styles.css
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
├── ch10
├── .editorconfig
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── tasks.json
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── public
│ └── favicon.ico
├── src
│ ├── app
│ │ ├── app.component.css
│ │ ├── app.component.html
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.config.ts
│ │ ├── app.routes.ts
│ │ ├── app.settings.ts
│ │ ├── auth.guard.spec.ts
│ │ ├── auth.guard.ts
│ │ ├── auth.service.spec.ts
│ │ ├── auth.service.ts
│ │ ├── auth
│ │ │ ├── auth.component.css
│ │ │ ├── auth.component.html
│ │ │ ├── auth.component.spec.ts
│ │ │ └── auth.component.ts
│ │ ├── cart.service.spec.ts
│ │ ├── cart.service.ts
│ │ ├── cart.ts
│ │ ├── cart
│ │ │ ├── cart.component.css
│ │ │ ├── cart.component.html
│ │ │ ├── cart.component.spec.ts
│ │ │ └── cart.component.ts
│ │ ├── checkout.guard.spec.ts
│ │ ├── checkout.guard.ts
│ │ ├── copyright.directive.spec.ts
│ │ ├── copyright.directive.ts
│ │ ├── price-maximum.directive.spec.ts
│ │ ├── price-maximum.directive.ts
│ │ ├── price-maximum.validator.ts
│ │ ├── product-create
│ │ │ ├── product-create.component.css
│ │ │ ├── product-create.component.html
│ │ │ ├── product-create.component.spec.ts
│ │ │ └── product-create.component.ts
│ │ ├── product-detail
│ │ │ ├── product-detail.component.css
│ │ │ ├── product-detail.component.html
│ │ │ ├── product-detail.component.spec.ts
│ │ │ └── product-detail.component.ts
│ │ ├── product-list
│ │ │ ├── product-list.component.css
│ │ │ ├── product-list.component.html
│ │ │ ├── product-list.component.spec.ts
│ │ │ └── product-list.component.ts
│ │ ├── product.ts
│ │ ├── products.resolver.spec.ts
│ │ ├── products.resolver.ts
│ │ ├── products.service.spec.ts
│ │ ├── products.service.ts
│ │ ├── sort.pipe.spec.ts
│ │ ├── sort.pipe.ts
│ │ ├── user.routes.ts
│ │ └── user
│ │ │ ├── user.component.css
│ │ │ ├── user.component.html
│ │ │ ├── user.component.spec.ts
│ │ │ └── user.component.ts
│ ├── index.html
│ ├── main.ts
│ └── styles.css
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
├── ch11
├── .editorconfig
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── tasks.json
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── public
│ └── favicon.ico
├── src
│ ├── app
│ │ ├── app-error-handler.ts
│ │ ├── app.component.css
│ │ ├── app.component.html
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.config.ts
│ │ ├── app.routes.ts
│ │ ├── app.settings.ts
│ │ ├── auth.guard.spec.ts
│ │ ├── auth.guard.ts
│ │ ├── auth.service.spec.ts
│ │ ├── auth.service.ts
│ │ ├── auth
│ │ │ ├── auth.component.css
│ │ │ ├── auth.component.html
│ │ │ ├── auth.component.spec.ts
│ │ │ └── auth.component.ts
│ │ ├── cart.service.spec.ts
│ │ ├── cart.service.ts
│ │ ├── cart.ts
│ │ ├── cart
│ │ │ ├── cart.component.css
│ │ │ ├── cart.component.html
│ │ │ ├── cart.component.spec.ts
│ │ │ └── cart.component.ts
│ │ ├── checkout.guard.spec.ts
│ │ ├── checkout.guard.ts
│ │ ├── copyright.directive.spec.ts
│ │ ├── copyright.directive.ts
│ │ ├── price-maximum.directive.spec.ts
│ │ ├── price-maximum.directive.ts
│ │ ├── price-maximum.validator.ts
│ │ ├── product-create
│ │ │ ├── product-create.component.css
│ │ │ ├── product-create.component.html
│ │ │ ├── product-create.component.spec.ts
│ │ │ └── product-create.component.ts
│ │ ├── product-detail
│ │ │ ├── product-detail.component.css
│ │ │ ├── product-detail.component.html
│ │ │ ├── product-detail.component.spec.ts
│ │ │ └── product-detail.component.ts
│ │ ├── product-list
│ │ │ ├── product-list.component.css
│ │ │ ├── product-list.component.html
│ │ │ ├── product-list.component.spec.ts
│ │ │ └── product-list.component.ts
│ │ ├── product.ts
│ │ ├── products.resolver.spec.ts
│ │ ├── products.resolver.ts
│ │ ├── products.service.spec.ts
│ │ ├── products.service.ts
│ │ ├── sort.pipe.spec.ts
│ │ ├── sort.pipe.ts
│ │ ├── user.routes.ts
│ │ └── user
│ │ │ ├── user.component.css
│ │ │ ├── user.component.html
│ │ │ ├── user.component.spec.ts
│ │ │ └── user.component.ts
│ ├── index.html
│ ├── main.ts
│ └── styles.css
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
├── ch12
├── .editorconfig
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── tasks.json
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── public
│ └── favicon.ico
├── src
│ ├── app
│ │ ├── app-error-handler.ts
│ │ ├── app.component.css
│ │ ├── app.component.html
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.config.ts
│ │ ├── app.routes.ts
│ │ ├── app.settings.ts
│ │ ├── auth.guard.spec.ts
│ │ ├── auth.guard.ts
│ │ ├── auth.service.spec.ts
│ │ ├── auth.service.ts
│ │ ├── auth
│ │ │ ├── auth.component.css
│ │ │ ├── auth.component.html
│ │ │ ├── auth.component.spec.ts
│ │ │ └── auth.component.ts
│ │ ├── cart.service.spec.ts
│ │ ├── cart.service.ts
│ │ ├── cart.ts
│ │ ├── cart
│ │ │ ├── cart.component.css
│ │ │ ├── cart.component.html
│ │ │ ├── cart.component.spec.ts
│ │ │ └── cart.component.ts
│ │ ├── checkout.guard.spec.ts
│ │ ├── checkout.guard.ts
│ │ ├── checkout
│ │ │ ├── checkout.component.css
│ │ │ ├── checkout.component.html
│ │ │ ├── checkout.component.spec.ts
│ │ │ └── checkout.component.ts
│ │ ├── copyright.directive.spec.ts
│ │ ├── copyright.directive.ts
│ │ ├── price-maximum.directive.spec.ts
│ │ ├── price-maximum.directive.ts
│ │ ├── price-maximum.validator.ts
│ │ ├── product-create
│ │ │ ├── product-create.component.css
│ │ │ ├── product-create.component.html
│ │ │ ├── product-create.component.spec.ts
│ │ │ └── product-create.component.ts
│ │ ├── product-detail
│ │ │ ├── product-detail.component.css
│ │ │ ├── product-detail.component.html
│ │ │ ├── product-detail.component.spec.ts
│ │ │ └── product-detail.component.ts
│ │ ├── product-list
│ │ │ ├── product-list.component.css
│ │ │ ├── product-list.component.html
│ │ │ ├── product-list.component.spec.ts
│ │ │ └── product-list.component.ts
│ │ ├── product.ts
│ │ ├── products.resolver.spec.ts
│ │ ├── products.resolver.ts
│ │ ├── products.service.spec.ts
│ │ ├── products.service.ts
│ │ ├── sort.pipe.spec.ts
│ │ ├── sort.pipe.ts
│ │ ├── user.routes.ts
│ │ └── user
│ │ │ ├── user.component.css
│ │ │ ├── user.component.html
│ │ │ ├── user.component.spec.ts
│ │ │ └── user.component.ts
│ ├── index.html
│ ├── main.ts
│ └── styles.css
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
├── ch13
├── .editorconfig
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── tasks.json
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── public
│ └── favicon.ico
├── src
│ ├── app
│ │ ├── app.component.css
│ │ ├── app.component.html
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.config.ts
│ │ ├── app.routes.ts
│ │ ├── async.service.spec.ts
│ │ ├── async.service.ts
│ │ ├── async
│ │ │ ├── async.component.spec.ts
│ │ │ └── async.component.ts
│ │ ├── auth.guard.spec.ts
│ │ ├── auth.guard.ts
│ │ ├── auth.service.ts
│ │ ├── bindings
│ │ │ ├── bindings.component.spec.ts
│ │ │ └── bindings.component.ts
│ │ ├── copyright.directive.spec.ts
│ │ ├── copyright.directive.ts
│ │ ├── deps.service.spec.ts
│ │ ├── deps.service.ts
│ │ ├── items.resolver.spec.ts
│ │ ├── items.resolver.ts
│ │ ├── list.pipe.spec.ts
│ │ ├── list.pipe.ts
│ │ ├── routed
│ │ │ ├── routed.component.spec.ts
│ │ │ └── routed.component.ts
│ │ ├── search
│ │ │ ├── search.component.spec.ts
│ │ │ └── search.component.ts
│ │ ├── spy
│ │ │ ├── spy.component.spec.ts
│ │ │ └── spy.component.ts
│ │ ├── stub.service.spec.ts
│ │ ├── stub.service.ts
│ │ └── stub
│ │ │ ├── stub.component.spec.ts
│ │ │ └── stub.component.ts
│ ├── index.html
│ ├── main.ts
│ └── styles.css
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
├── ch14
├── .editorconfig
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── tasks.json
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── public
│ └── favicon.ico
├── src
│ ├── app
│ │ ├── app.component.css
│ │ ├── app.component.html
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.config.ts
│ │ └── app.routes.ts
│ ├── environments
│ │ ├── environment.development.ts
│ │ └── environment.ts
│ ├── index.html
│ ├── main.ts
│ └── styles.css
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
└── ch15
├── .editorconfig
├── .gitignore
├── .vscode
├── extensions.json
├── launch.json
└── tasks.json
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── public
├── favicon.ico
└── placeholder.png
├── src
├── app
│ ├── app-error-handler.ts
│ ├── app.component.css
│ ├── app.component.html
│ ├── app.component.spec.ts
│ ├── app.component.ts
│ ├── app.config.server.ts
│ ├── app.config.ts
│ ├── app.routes.ts
│ ├── app.settings.ts
│ ├── auth.guard.spec.ts
│ ├── auth.guard.ts
│ ├── auth.service.spec.ts
│ ├── auth.service.ts
│ ├── auth
│ │ ├── auth.component.css
│ │ ├── auth.component.html
│ │ ├── auth.component.spec.ts
│ │ └── auth.component.ts
│ ├── cart.service.spec.ts
│ ├── cart.service.ts
│ ├── cart.ts
│ ├── cart
│ │ ├── cart.component.css
│ │ ├── cart.component.html
│ │ ├── cart.component.spec.ts
│ │ └── cart.component.ts
│ ├── checkout.guard.spec.ts
│ ├── checkout.guard.ts
│ ├── checkout
│ │ ├── checkout.component.css
│ │ ├── checkout.component.html
│ │ ├── checkout.component.spec.ts
│ │ └── checkout.component.ts
│ ├── copyright.directive.spec.ts
│ ├── copyright.directive.ts
│ ├── featured
│ │ ├── featured.component.css
│ │ ├── featured.component.html
│ │ ├── featured.component.spec.ts
│ │ └── featured.component.ts
│ ├── price-maximum.directive.spec.ts
│ ├── price-maximum.directive.ts
│ ├── price-maximum.validator.ts
│ ├── product-create
│ │ ├── product-create.component.css
│ │ ├── product-create.component.html
│ │ ├── product-create.component.spec.ts
│ │ └── product-create.component.ts
│ ├── product-detail
│ │ ├── product-detail.component.css
│ │ ├── product-detail.component.html
│ │ ├── product-detail.component.spec.ts
│ │ └── product-detail.component.ts
│ ├── product-list
│ │ ├── product-list.component.css
│ │ ├── product-list.component.html
│ │ ├── product-list.component.spec.ts
│ │ └── product-list.component.ts
│ ├── product.ts
│ ├── products.resolver.spec.ts
│ ├── products.resolver.ts
│ ├── products.service.spec.ts
│ ├── products.service.ts
│ ├── sort.pipe.spec.ts
│ ├── sort.pipe.ts
│ ├── user.routes.ts
│ └── user
│ │ ├── user.component.css
│ │ ├── user.component.html
│ │ ├── user.component.spec.ts
│ │ └── user.component.ts
├── index.html
├── main.server.ts
├── main.ts
├── server.ts
└── styles.css
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
/Cover.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/Cover.PNG
--------------------------------------------------------------------------------
/ch01/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 | ij_typescript_use_double_quotes = false
14 |
15 | [*.md]
16 | max_line_length = off
17 | trim_trailing_whitespace = false
18 |
--------------------------------------------------------------------------------
/ch01/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/ch01/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/ch01/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch01/public/favicon.ico
--------------------------------------------------------------------------------
/ch01/src/app/app.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch01/src/app/app.component.css
--------------------------------------------------------------------------------
/ch01/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { RouterOutlet } from '@angular/router';
3 |
4 | @Component({
5 | selector: 'app-root',
6 | imports: [RouterOutlet],
7 | templateUrl: './app.component.html',
8 | styleUrl: './app.component.css'
9 | })
10 | export class AppComponent {
11 | title = 'World';
12 | }
13 |
--------------------------------------------------------------------------------
/ch01/src/app/app.config.ts:
--------------------------------------------------------------------------------
1 | import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
2 | import { provideRouter } from '@angular/router';
3 |
4 | import { routes } from './app.routes';
5 |
6 | export const appConfig: ApplicationConfig = {
7 | providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)]
8 | };
9 |
--------------------------------------------------------------------------------
/ch01/src/app/app.routes.ts:
--------------------------------------------------------------------------------
1 | import { Routes } from '@angular/router';
2 |
3 | export const routes: Routes = [];
4 |
--------------------------------------------------------------------------------
/ch01/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MyApp
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/ch01/src/main.ts:
--------------------------------------------------------------------------------
1 | import { bootstrapApplication } from '@angular/platform-browser';
2 | import { appConfig } from './app/app.config';
3 | import { AppComponent } from './app/app.component';
4 |
5 | bootstrapApplication(AppComponent, appConfig)
6 | .catch((err) => console.error(err));
7 |
--------------------------------------------------------------------------------
/ch01/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/ch01/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/app",
7 | "types": []
8 | },
9 | "files": [
10 | "src/main.ts"
11 | ],
12 | "include": [
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/ch01/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/spec",
7 | "types": [
8 | "jasmine"
9 | ]
10 | },
11 | "include": [
12 | "src/**/*.spec.ts",
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/ch02/.gitignore:
--------------------------------------------------------------------------------
1 | *.js
--------------------------------------------------------------------------------
/ch02/app.ts:
--------------------------------------------------------------------------------
1 | const title = 'Hello TypeScript!';
--------------------------------------------------------------------------------
/ch02/customer.ts:
--------------------------------------------------------------------------------
1 | import { User } from './user';
2 |
3 | class Customer extends User {
4 | taxNumber: number;
5 |
6 | constructor(firstName: string, lastName: string) {
7 | super(firstName, lastName);
8 | }
9 | }
--------------------------------------------------------------------------------
/ch02/functions.ts:
--------------------------------------------------------------------------------
1 | function getProduct(): string {
2 | return 'Keyboard';
3 | }
4 |
5 | function getFullname(firstName: string, lastName: string): string {
6 | return `${this.firstName} ${this.lastName}`;
7 | }
8 |
9 | function printFullname(firstName: string, lastName: string): void {
10 | console.log(`${this.firstName} ${this.lastName}`);
11 | }
12 |
13 | function addtoCart(productId: number, quantity?: number) {
14 | const product = {
15 | id: productId,
16 | qty: quantity ?? 1
17 | };
18 | }
--------------------------------------------------------------------------------
/ch02/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022"
4 | }
5 | }
--------------------------------------------------------------------------------
/ch02/user.ts:
--------------------------------------------------------------------------------
1 | export class User {
2 | firstName: string = '';
3 | lastName: string = '';
4 | private isActive: boolean = false;
5 |
6 | constructor(firstName: string, lastName: string, isActive: boolean = true) {
7 | this.firstName = firstName;
8 | this.lastName = lastName;
9 | this.isActive = isActive;
10 | }
11 |
12 | getFullname(): string {
13 | return `${this.firstName} ${this.lastName}`;
14 | }
15 |
16 | get active(): boolean {
17 | return this.isActive;
18 | }
19 | }
--------------------------------------------------------------------------------
/ch02/variables.ts:
--------------------------------------------------------------------------------
1 | // Primitive types
2 | const product: string = 'Keyboard';
3 | const isActive: boolean = true;
4 | const price: number = 100;
5 | const categories: string[] = ['Computing', 'Multimedia'];
6 |
7 | // Any
8 | let order: any;
9 | function setOrderNo() {
10 | order = '0001';
11 | }
12 |
13 | // Custom types
14 | type Categories = 'computing' | 'multimedia';
15 | const category: Categories = 'computing';
16 |
--------------------------------------------------------------------------------
/ch03/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 | ij_typescript_use_double_quotes = false
14 |
15 | [*.md]
16 | max_line_length = off
17 | trim_trailing_whitespace = false
18 |
--------------------------------------------------------------------------------
/ch03/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/ch03/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/ch03/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch03/public/favicon.ico
--------------------------------------------------------------------------------
/ch03/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/ch03/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { RouterOutlet } from '@angular/router';
3 | import { ProductListComponent } from './product-list/product-list.component';
4 |
5 | @Component({
6 | selector: 'app-root',
7 | imports: [RouterOutlet, ProductListComponent],
8 | templateUrl: './app.component.html',
9 | styleUrl: './app.component.css'
10 | })
11 | export class AppComponent {
12 | title = 'World';
13 | }
14 |
--------------------------------------------------------------------------------
/ch03/src/app/app.config.ts:
--------------------------------------------------------------------------------
1 | import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
2 | import { provideRouter } from '@angular/router';
3 |
4 | import { routes } from './app.routes';
5 |
6 | export const appConfig: ApplicationConfig = {
7 | providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)]
8 | };
9 |
--------------------------------------------------------------------------------
/ch03/src/app/app.routes.ts:
--------------------------------------------------------------------------------
1 | import { Routes } from '@angular/router';
2 |
3 | export const routes: Routes = [];
4 |
--------------------------------------------------------------------------------
/ch03/src/app/product-detail/product-detail.component.css:
--------------------------------------------------------------------------------
1 | button {
2 | display: flex;
3 | align-items: center;
4 | --button-accent: var(--bright-blue);
5 | background: color-mix(in srgb, var(--button-accent) 65%, transparent);
6 | color: white;
7 | padding-inline: 0.75rem;
8 | padding-block: 0.375rem;
9 | border-radius: 0.5rem;
10 | border: 0;
11 | transition: background 0.3s ease;
12 | font-family: var(--inter-font);
13 | font-size: 0.875rem;
14 | font-style: normal;
15 | font-weight: 500;
16 | line-height: 1.4rem;
17 | letter-spacing: -0.00875rem;
18 | cursor: pointer;
19 | }
20 |
21 | button:hover {
22 | background: color-mix(in srgb, var(--button-accent) 50%, transparent);
23 | }
24 |
--------------------------------------------------------------------------------
/ch03/src/app/product-detail/product-detail.component.html:
--------------------------------------------------------------------------------
1 | @if (product()) {
2 | You selected:
3 | {{product()!.title}}
4 |
5 |
6 | }
7 |
--------------------------------------------------------------------------------
/ch03/src/app/product-detail/product-detail.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, input, output } from '@angular/core';
2 | import { Product } from '../product';
3 |
4 | @Component({
5 | selector: 'app-product-detail',
6 | imports: [],
7 | templateUrl: './product-detail.component.html',
8 | styleUrl: './product-detail.component.css'
9 | })
10 | export class ProductDetailComponent {
11 | product = input();
12 | added = output();
13 |
14 | addToCart() {
15 | this.added.emit();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/ch03/src/app/product-list/product-list.component.html:
--------------------------------------------------------------------------------
1 | @if (products.length > 0) {
2 | Products ({{products.length}})
3 | }
4 |
5 |
6 | @for (product of products; track product.id) {
7 | -
8 | @switch (product.title) {
9 | @case ('Keyboard') { ⌨️ }
10 | @case ('Microphone') { 🎙️ }
11 | @default { 🏷️ }
12 | }
13 | {{product.title}}
14 |
15 | } @empty {
16 | No products found!
17 | }
18 |
19 |
20 |
24 |
--------------------------------------------------------------------------------
/ch03/src/app/product.ts:
--------------------------------------------------------------------------------
1 | export interface Product {
2 | id: number;
3 | title: string;
4 | }
5 |
--------------------------------------------------------------------------------
/ch03/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MyApp
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/ch03/src/main.ts:
--------------------------------------------------------------------------------
1 | import { bootstrapApplication } from '@angular/platform-browser';
2 | import { appConfig } from './app/app.config';
3 | import { AppComponent } from './app/app.component';
4 |
5 | bootstrapApplication(AppComponent, appConfig)
6 | .catch((err) => console.error(err));
7 |
--------------------------------------------------------------------------------
/ch03/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/ch03/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/app",
7 | "types": []
8 | },
9 | "files": [
10 | "src/main.ts"
11 | ],
12 | "include": [
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/ch03/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/spec",
7 | "types": [
8 | "jasmine"
9 | ]
10 | },
11 | "include": [
12 | "src/**/*.spec.ts",
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/ch04/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 | ij_typescript_use_double_quotes = false
14 |
15 | [*.md]
16 | max_line_length = off
17 | trim_trailing_whitespace = false
18 |
--------------------------------------------------------------------------------
/ch04/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/ch04/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/ch04/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch04/public/favicon.ico
--------------------------------------------------------------------------------
/ch04/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ch04/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { RouterOutlet } from '@angular/router';
3 | import { ProductListComponent } from './product-list/product-list.component';
4 | import { CopyrightDirective } from './copyright.directive';
5 |
6 | @Component({
7 | selector: 'app-root',
8 | imports: [
9 | RouterOutlet,
10 | ProductListComponent,
11 | CopyrightDirective
12 | ],
13 | templateUrl: './app.component.html',
14 | styleUrl: './app.component.css'
15 | })
16 | export class AppComponent {
17 | title = 'World';
18 | }
19 |
--------------------------------------------------------------------------------
/ch04/src/app/app.config.ts:
--------------------------------------------------------------------------------
1 | import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
2 | import { provideRouter } from '@angular/router';
3 |
4 | import { routes } from './app.routes';
5 |
6 | export const appConfig: ApplicationConfig = {
7 | providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)]
8 | };
9 |
--------------------------------------------------------------------------------
/ch04/src/app/app.routes.ts:
--------------------------------------------------------------------------------
1 | import { Routes } from '@angular/router';
2 |
3 | export const routes: Routes = [];
4 |
--------------------------------------------------------------------------------
/ch04/src/app/copyright.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { CopyrightDirective } from './copyright.directive';
2 |
3 | describe('CopyrightDirective', () => {
4 | it('should create an instance', () => {
5 | const directive = new CopyrightDirective();
6 | expect(directive).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/ch04/src/app/copyright.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, ElementRef } from '@angular/core';
2 |
3 | @Directive({
4 | selector: '[appCopyright]'
5 | })
6 | export class CopyrightDirective {
7 |
8 | constructor(el: ElementRef) {
9 | const currentYear = new Date().getFullYear();
10 | const targetEl: HTMLElement = el.nativeElement;
11 | targetEl.classList.add('copyright');
12 | targetEl.textContent = `Copyright ©${currentYear} All Rights Reserved`;
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/ch04/src/app/numeric.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { NumericDirective } from './numeric.directive';
2 |
3 | describe('NumericDirective', () => {
4 | it('should create an instance', () => {
5 | const directive = new NumericDirective();
6 | expect(directive).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/ch04/src/app/numeric.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, HostBinding, HostListener } from '@angular/core';
2 |
3 | @Directive({
4 | selector: '[appNumeric]'
5 | })
6 | export class NumericDirective {
7 | @HostBinding('class') currentClass = '';
8 | @HostListener('keypress', ['$event']) onKeyPress(event: KeyboardEvent) {
9 | const charCode = event.key.charCodeAt(0);
10 | if (charCode > 31 && (charCode < 48 || charCode > 57)) {
11 | this.currentClass = 'invalid';
12 | event.preventDefault();
13 | } else {
14 | this.currentClass = 'valid';
15 | }
16 | }
17 | constructor() { }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/ch04/src/app/product-detail/product-detail.component.html:
--------------------------------------------------------------------------------
1 | @if (product()) {
2 | You selected:
3 | {{product()!.title}}
4 |
5 | {{product()!.price | currency:'EUR'}}
6 |
7 | @for (cat of product()!.categories | keyvalue; track cat.key) {
8 |
{{cat.value | lowercase}}
9 | }
10 |
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/ch04/src/app/product-detail/product-detail.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { Component, input, output } from '@angular/core';
3 | import { Product } from '../product';
4 |
5 | @Component({
6 | selector: 'app-product-detail',
7 | imports: [CommonModule],
8 | templateUrl: './product-detail.component.html',
9 | styleUrl: './product-detail.component.css'
10 | })
11 | export class ProductDetailComponent {
12 | product = input();
13 | added = output();
14 |
15 | addToCart() {
16 | this.added.emit();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ch04/src/app/product-list/product-list.component.html:
--------------------------------------------------------------------------------
1 | @if (products.length > 0) {
2 | Products ({{products.length}})
3 | }
4 |
5 |
6 | @for (product of products | sort; track product.id) {
7 | -
8 | @switch (product.title) {
9 | @case ('Keyboard') { ⌨️ }
10 | @case ('Microphone') { 🎙️ }
11 | @default { 🏷️ }
12 | }
13 | {{product.title}}
14 |
15 | } @empty {
16 | No products found!
17 | }
18 |
19 |
20 |
24 |
--------------------------------------------------------------------------------
/ch04/src/app/product.ts:
--------------------------------------------------------------------------------
1 | export interface Product {
2 | id: number;
3 | title: string;
4 | price: number;
5 | categories: Record;
6 | }
7 |
--------------------------------------------------------------------------------
/ch04/src/app/sort.pipe.spec.ts:
--------------------------------------------------------------------------------
1 | import { SortPipe } from './sort.pipe';
2 |
3 | describe('SortPipe', () => {
4 | it('create an instance', () => {
5 | const pipe = new SortPipe();
6 | expect(pipe).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/ch04/src/app/sort.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 | import { Product } from './product';
3 |
4 | @Pipe({
5 | name: 'sort'
6 | })
7 | export class SortPipe implements PipeTransform {
8 |
9 | transform(value: Product[], args: keyof Product = 'title'): Product[] {
10 | if (value) {
11 | return value.sort((a: Product, b: Product) => {
12 | if (a[args] < b[args]) {
13 | return -1;
14 | } else if (b[args] < a[args]) {
15 | return 1;
16 | }
17 | return 0;
18 | });
19 | }
20 | return [];
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/ch04/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MyApp
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/ch04/src/main.ts:
--------------------------------------------------------------------------------
1 | import { bootstrapApplication } from '@angular/platform-browser';
2 | import { appConfig } from './app/app.config';
3 | import { AppComponent } from './app/app.component';
4 |
5 | bootstrapApplication(AppComponent, appConfig)
6 | .catch((err) => console.error(err));
7 |
--------------------------------------------------------------------------------
/ch04/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 | .copyright {
3 | font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
4 | Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
5 | "Segoe UI Symbol";
6 | width: 100%;
7 | min-height: 100%;
8 | display: flex;
9 | justify-content: center;
10 | align-items: center;
11 | padding: 1rem;
12 | box-sizing: inherit;
13 | position: relative;
14 | }
15 | input.valid {
16 | border: solid green;
17 | }
18 | input.invalid {
19 | border: solid red;
20 | }
21 |
--------------------------------------------------------------------------------
/ch04/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/app",
7 | "types": []
8 | },
9 | "files": [
10 | "src/main.ts"
11 | ],
12 | "include": [
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/ch04/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/spec",
7 | "types": [
8 | "jasmine"
9 | ]
10 | },
11 | "include": [
12 | "src/**/*.spec.ts",
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/ch05/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 | ij_typescript_use_double_quotes = false
14 |
15 | [*.md]
16 | max_line_length = off
17 | trim_trailing_whitespace = false
18 |
--------------------------------------------------------------------------------
/ch05/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/ch05/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/ch05/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch05/public/favicon.ico
--------------------------------------------------------------------------------
/ch05/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ch05/src/app/app.config.ts:
--------------------------------------------------------------------------------
1 | import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
2 | import { provideRouter } from '@angular/router';
3 |
4 | import { routes } from './app.routes';
5 |
6 | export const appConfig: ApplicationConfig = {
7 | providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)]
8 | };
9 |
--------------------------------------------------------------------------------
/ch05/src/app/app.routes.ts:
--------------------------------------------------------------------------------
1 | import { Routes } from '@angular/router';
2 |
3 | export const routes: Routes = [];
4 |
--------------------------------------------------------------------------------
/ch05/src/app/app.settings.ts:
--------------------------------------------------------------------------------
1 | import { InjectionToken } from '@angular/core';
2 |
3 | export interface AppSettings {
4 | title: string;
5 | version: string;
6 | }
7 |
8 | export const appSettings: AppSettings = {
9 | title: 'My e-shop',
10 | version: '1.0'
11 | };
12 |
13 | export const APP_SETTINGS = new InjectionToken('app.settings');
14 |
--------------------------------------------------------------------------------
/ch05/src/app/copyright.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { CopyrightDirective } from './copyright.directive';
2 |
3 | describe('CopyrightDirective', () => {
4 | it('should create an instance', () => {
5 | const directive = new CopyrightDirective();
6 | expect(directive).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/ch05/src/app/copyright.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, ElementRef } from '@angular/core';
2 |
3 | @Directive({
4 | selector: '[appCopyright]'
5 | })
6 | export class CopyrightDirective {
7 |
8 | constructor(el: ElementRef) {
9 | const currentYear = new Date().getFullYear();
10 | const targetEl: HTMLElement = el.nativeElement;
11 | targetEl.classList.add('copyright');
12 | targetEl.textContent = `Copyright ©${currentYear} All Rights Reserved`;
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/ch05/src/app/favorites.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { FavoritesService } from './favorites.service';
4 |
5 | describe('FavoritesService', () => {
6 | let service: FavoritesService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(FavoritesService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/ch05/src/app/favorites.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Product } from './product';
3 | import { ProductsService } from './products.service';
4 |
5 | @Injectable({
6 | providedIn: 'root'
7 | })
8 | export class FavoritesService extends ProductsService {
9 |
10 | constructor() {
11 | super();
12 | }
13 |
14 | override getProducts(): Product[] {
15 | return super.getProducts().slice(1, 3);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/ch05/src/app/favorites.ts:
--------------------------------------------------------------------------------
1 | import { FavoritesService } from './favorites.service';
2 | import { ProductsService } from './products.service';
3 |
4 | export function favoritesFactory(isFavorite: boolean) {
5 | return () => {
6 | if (isFavorite) {
7 | return new FavoritesService();
8 | }
9 | return new ProductsService();
10 | };
11 | }
12 |
--------------------------------------------------------------------------------
/ch05/src/app/favorites/favorites.component.css:
--------------------------------------------------------------------------------
1 | .pill-group {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: start;
5 | flex-wrap: wrap;
6 | gap: 1.25rem;
7 | }
8 |
9 | .pill {
10 | display: flex;
11 | align-items: center;
12 | --pill-accent: var(--hot-red);
13 | background: color-mix(in srgb, var(--hot-red) 5%, transparent);
14 | color: var(--pill-accent);
15 | padding-inline: 0.75rem;
16 | padding-block: 0.375rem;
17 | border-radius: 2.75rem;
18 | border: 0;
19 | transition: background 0.3s ease;
20 | font-family: var(--inter-font);
21 | font-size: 0.875rem;
22 | font-style: normal;
23 | font-weight: 500;
24 | line-height: 1.4rem;
25 | letter-spacing: -0.00875rem;
26 | text-decoration: none;
27 | }
28 |
--------------------------------------------------------------------------------
/ch05/src/app/favorites/favorites.component.html:
--------------------------------------------------------------------------------
1 |
2 | @for (product of products | slice:1:3; track product.id) {
3 | -
4 | ⭐ {{product.title}}
5 |
6 | }
7 |
8 |
--------------------------------------------------------------------------------
/ch05/src/app/favorites/favorites.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { FavoritesComponent } from './favorites.component';
4 |
5 | describe('FavoritesComponent', () => {
6 | let component: FavoritesComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | imports: [FavoritesComponent]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(FavoritesComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/ch05/src/app/favorites/favorites.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { Component, OnInit } from '@angular/core';
3 | import { Product } from '../product';
4 | import { ProductsService } from '../products.service';
5 |
6 | @Component({
7 | selector: 'app-favorites',
8 | imports: [CommonModule],
9 | templateUrl: './favorites.component.html',
10 | styleUrl: './favorites.component.css'
11 | })
12 | export class FavoritesComponent implements OnInit {
13 | products: Product[] = [];
14 |
15 | constructor(private productService: ProductsService) {}
16 |
17 | ngOnInit(): void {
18 | this.products = this.productService.getProducts();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ch05/src/app/numeric.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { NumericDirective } from './numeric.directive';
2 |
3 | describe('NumericDirective', () => {
4 | it('should create an instance', () => {
5 | const directive = new NumericDirective();
6 | expect(directive).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/ch05/src/app/numeric.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, HostBinding, HostListener } from '@angular/core';
2 |
3 | @Directive({
4 | selector: '[appNumeric]'
5 | })
6 | export class NumericDirective {
7 | @HostBinding('class') currentClass = '';
8 | @HostListener('keypress', ['$event']) onKeyPress(event: KeyboardEvent) {
9 | const charCode = event.key.charCodeAt(0);
10 | if (charCode > 31 && (charCode < 48 || charCode > 57)) {
11 | this.currentClass = 'invalid';
12 | event.preventDefault();
13 | } else {
14 | this.currentClass = 'valid';
15 | }
16 | }
17 | constructor() { }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/ch05/src/app/product-detail/product-detail.component.html:
--------------------------------------------------------------------------------
1 | @if (product()) {
2 | You selected:
3 | {{product()!.title}}
4 |
5 | {{product()!.price | currency:'EUR'}}
6 |
7 | @for (cat of product()!.categories | keyvalue; track cat.key) {
8 |
{{cat.value | lowercase}}
9 | }
10 |
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/ch05/src/app/product-detail/product-detail.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { Component, input, output } from '@angular/core';
3 | import { Product } from '../product';
4 |
5 | @Component({
6 | selector: 'app-product-detail',
7 | imports: [CommonModule],
8 | templateUrl: './product-detail.component.html',
9 | styleUrl: './product-detail.component.css'
10 | })
11 | export class ProductDetailComponent {
12 | product = input();
13 | added = output();
14 |
15 | addToCart() {
16 | this.added.emit();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ch05/src/app/product-list/product-list.component.html:
--------------------------------------------------------------------------------
1 | @if (products.length > 0) {
2 | Products ({{products.length}})
3 | }
4 |
5 |
6 | @for (product of products | sort; track product.id) {
7 | -
8 | @switch (product.title) {
9 | @case ('Keyboard') { ⌨️ }
10 | @case ('Microphone') { 🎙️ }
11 | @default { 🏷️ }
12 | }
13 | {{product.title}}
14 |
15 | } @empty {
16 | No products found!
17 | }
18 |
19 |
20 |
24 |
--------------------------------------------------------------------------------
/ch05/src/app/product-view/product-view.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch05/src/app/product-view/product-view.component.css
--------------------------------------------------------------------------------
/ch05/src/app/product-view/product-view.component.html:
--------------------------------------------------------------------------------
1 | @switch (product?.title) {
2 | @case ('Keyboard') { ⌨️ }
3 | @case ('Microphone') { 🎙️ }
4 | @default { 🏷️ }
5 | }
6 | {{product?.title}}
7 |
--------------------------------------------------------------------------------
/ch05/src/app/product-view/product-view.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, input, OnInit } from '@angular/core';
2 | import { ProductViewService } from './product-view.service';
3 | import { Product } from '../product';
4 |
5 | @Component({
6 | selector: 'app-product-view',
7 | imports: [],
8 | templateUrl: './product-view.component.html',
9 | styleUrl: './product-view.component.css',
10 | providers: [ProductViewService]
11 | })
12 | export class ProductViewComponent implements OnInit {
13 | id = input();
14 | product: Product | undefined;
15 |
16 | constructor(private productViewService: ProductViewService) {}
17 |
18 | ngOnInit(): void {
19 | this.product = this.productViewService.getProduct(this.id()!);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ch05/src/app/product-view/product-view.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { ProductViewService } from './product-view.service';
4 |
5 | describe('ProductViewService', () => {
6 | let service: ProductViewService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(ProductViewService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/ch05/src/app/product-view/product-view.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { ProductsService } from '../products.service';
3 | import { Product } from '../product';
4 |
5 | @Injectable()
6 | export class ProductViewService {
7 | private product: Product | undefined;
8 |
9 | constructor(private productService: ProductsService) { }
10 |
11 | getProduct(id: number): Product | undefined {
12 | const products = this.productService.getProducts();
13 | if (!this.product) {
14 | this.product = products.find(product => product.id === id)
15 | }
16 | return this.product;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ch05/src/app/product.ts:
--------------------------------------------------------------------------------
1 | export interface Product {
2 | id: number;
3 | title: string;
4 | price: number;
5 | categories: Record;
6 | }
7 |
--------------------------------------------------------------------------------
/ch05/src/app/products.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { ProductsService } from './products.service';
4 |
5 | describe('ProductsService', () => {
6 | let service: ProductsService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(ProductsService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/ch05/src/app/sort.pipe.spec.ts:
--------------------------------------------------------------------------------
1 | import { SortPipe } from './sort.pipe';
2 |
3 | describe('SortPipe', () => {
4 | it('create an instance', () => {
5 | const pipe = new SortPipe();
6 | expect(pipe).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/ch05/src/app/sort.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 | import { Product } from './product';
3 |
4 | @Pipe({
5 | name: 'sort'
6 | })
7 | export class SortPipe implements PipeTransform {
8 |
9 | transform(value: Product[], args: keyof Product = 'title'): Product[] {
10 | if (value) {
11 | return value.sort((a: Product, b: Product) => {
12 | if (a[args] < b[args]) {
13 | return -1;
14 | } else if (b[args] < a[args]) {
15 | return 1;
16 | }
17 | return 0;
18 | });
19 | }
20 | return [];
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/ch05/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MyApp
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/ch05/src/main.ts:
--------------------------------------------------------------------------------
1 | import { bootstrapApplication } from '@angular/platform-browser';
2 | import { appConfig } from './app/app.config';
3 | import { AppComponent } from './app/app.component';
4 |
5 | bootstrapApplication(AppComponent, appConfig)
6 | .catch((err) => console.error(err));
7 |
--------------------------------------------------------------------------------
/ch05/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 | .copyright {
3 | font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
4 | Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
5 | "Segoe UI Symbol";
6 | width: 100%;
7 | min-height: 100%;
8 | display: flex;
9 | justify-content: center;
10 | align-items: center;
11 | padding: 1rem;
12 | box-sizing: inherit;
13 | position: relative;
14 | }
15 | input.valid {
16 | border: solid green;
17 | }
18 | input.invalid {
19 | border: solid red;
20 | }
21 |
--------------------------------------------------------------------------------
/ch05/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/app",
7 | "types": []
8 | },
9 | "files": [
10 | "src/main.ts"
11 | ],
12 | "include": [
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/ch05/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/spec",
7 | "types": [
8 | "jasmine"
9 | ]
10 | },
11 | "include": [
12 | "src/**/*.spec.ts",
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/ch06/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 | ij_typescript_use_double_quotes = false
14 |
15 | [*.md]
16 | max_line_length = off
17 | trim_trailing_whitespace = false
18 |
--------------------------------------------------------------------------------
/ch06/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/ch06/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/ch06/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch06/public/favicon.ico
--------------------------------------------------------------------------------
/ch06/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ch06/src/app/app.config.ts:
--------------------------------------------------------------------------------
1 | import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
2 | import { provideRouter } from '@angular/router';
3 |
4 | import { routes } from './app.routes';
5 |
6 | export const appConfig: ApplicationConfig = {
7 | providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)]
8 | };
9 |
--------------------------------------------------------------------------------
/ch06/src/app/app.routes.ts:
--------------------------------------------------------------------------------
1 | import { Routes } from '@angular/router';
2 |
3 | export const routes: Routes = [];
4 |
--------------------------------------------------------------------------------
/ch06/src/app/app.settings.ts:
--------------------------------------------------------------------------------
1 | import { InjectionToken } from '@angular/core';
2 |
3 | export interface AppSettings {
4 | title: string;
5 | version: string;
6 | }
7 |
8 | export const appSettings: AppSettings = {
9 | title: 'My e-shop',
10 | version: '1.0'
11 | };
12 |
13 | export const APP_SETTINGS = new InjectionToken('app.settings');
14 |
--------------------------------------------------------------------------------
/ch06/src/app/copyright.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { CopyrightDirective } from './copyright.directive';
2 |
3 | describe('CopyrightDirective', () => {
4 | it('should create an instance', () => {
5 | const directive = new CopyrightDirective();
6 | expect(directive).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/ch06/src/app/copyright.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, ElementRef } from '@angular/core';
2 |
3 | @Directive({
4 | selector: '[appCopyright]'
5 | })
6 | export class CopyrightDirective {
7 |
8 | constructor(el: ElementRef) {
9 | const currentYear = new Date().getFullYear();
10 | const targetEl: HTMLElement = el.nativeElement;
11 | targetEl.classList.add('copyright');
12 | targetEl.textContent = `Copyright ©${currentYear} All Rights Reserved`;
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/ch06/src/app/key-logger/key-logger.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch06/src/app/key-logger/key-logger.component.css
--------------------------------------------------------------------------------
/ch06/src/app/key-logger/key-logger.component.html:
--------------------------------------------------------------------------------
1 |
2 | You pressed: {{keys}}
3 |
--------------------------------------------------------------------------------
/ch06/src/app/key-logger/key-logger.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { KeyLoggerComponent } from './key-logger.component';
4 |
5 | describe('KeyLoggerComponent', () => {
6 | let component: KeyLoggerComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | imports: [KeyLoggerComponent]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(KeyLoggerComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/ch06/src/app/product-detail/product-detail.component.html:
--------------------------------------------------------------------------------
1 | @if (product()) {
2 | You selected:
3 | {{product()!.title}}
4 |
5 | {{product()!.price | currency:'EUR'}}
6 |
7 | @for (cat of product()!.categories | keyvalue; track cat.key) {
8 |
{{cat.value | lowercase}}
9 | }
10 |
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/ch06/src/app/product-detail/product-detail.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { Component, input, output } from '@angular/core';
3 | import { Product } from '../product';
4 |
5 | @Component({
6 | selector: 'app-product-detail',
7 | imports: [CommonModule],
8 | templateUrl: './product-detail.component.html',
9 | styleUrl: './product-detail.component.css'
10 | })
11 | export class ProductDetailComponent {
12 | product = input();
13 | added = output();
14 |
15 | addToCart() {
16 | this.added.emit();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ch06/src/app/product-list/product-list.component.html:
--------------------------------------------------------------------------------
1 | @let products = (products$ | async)!;
2 |
3 | @if (products.length > 0) {
4 | Products ({{products.length}})
5 | }
6 |
7 |
8 | @for (product of products | sort; track product.id) {
9 | -
10 | @switch (product.title) {
11 | @case ('Keyboard') { ⌨️ }
12 | @case ('Microphone') { 🎙️ }
13 | @default { 🏷️ }
14 | }
15 | {{product.title}}
16 |
17 | } @empty {
18 | No products found!
19 | }
20 |
21 |
22 |
26 |
--------------------------------------------------------------------------------
/ch06/src/app/product.ts:
--------------------------------------------------------------------------------
1 | export interface Product {
2 | id: number;
3 | title: string;
4 | price: number;
5 | categories: Record;
6 | }
7 |
--------------------------------------------------------------------------------
/ch06/src/app/products.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { ProductsService } from './products.service';
4 |
5 | describe('ProductsService', () => {
6 | let service: ProductsService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(ProductsService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/ch06/src/app/sort.pipe.spec.ts:
--------------------------------------------------------------------------------
1 | import { SortPipe } from './sort.pipe';
2 |
3 | describe('SortPipe', () => {
4 | it('create an instance', () => {
5 | const pipe = new SortPipe();
6 | expect(pipe).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/ch06/src/app/sort.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 | import { Product } from './product';
3 |
4 | @Pipe({
5 | name: 'sort'
6 | })
7 | export class SortPipe implements PipeTransform {
8 |
9 | transform(value: Product[], args: keyof Product = 'title'): Product[] {
10 | if (value) {
11 | return value.sort((a: Product, b: Product) => {
12 | if (a[args] < b[args]) {
13 | return -1;
14 | } else if (b[args] < a[args]) {
15 | return 1;
16 | }
17 | return 0;
18 | });
19 | }
20 | return [];
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/ch06/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MyApp
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/ch06/src/main.ts:
--------------------------------------------------------------------------------
1 | import { bootstrapApplication } from '@angular/platform-browser';
2 | import { appConfig } from './app/app.config';
3 | import { AppComponent } from './app/app.component';
4 |
5 | bootstrapApplication(AppComponent, appConfig)
6 | .catch((err) => console.error(err));
7 |
--------------------------------------------------------------------------------
/ch06/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 | .copyright {
3 | font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
4 | Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
5 | "Segoe UI Symbol";
6 | width: 100%;
7 | min-height: 100%;
8 | display: flex;
9 | justify-content: center;
10 | align-items: center;
11 | padding: 1rem;
12 | box-sizing: inherit;
13 | position: relative;
14 | }
15 | input.valid {
16 | border: solid green;
17 | }
18 | input.invalid {
19 | border: solid red;
20 | }
21 |
--------------------------------------------------------------------------------
/ch06/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/app",
7 | "types": []
8 | },
9 | "files": [
10 | "src/main.ts"
11 | ],
12 | "include": [
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/ch06/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/spec",
7 | "types": [
8 | "jasmine"
9 | ]
10 | },
11 | "include": [
12 | "src/**/*.spec.ts",
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/ch07/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 | ij_typescript_use_double_quotes = false
14 |
15 | [*.md]
16 | max_line_length = off
17 | trim_trailing_whitespace = false
18 |
--------------------------------------------------------------------------------
/ch07/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/ch07/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/ch07/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch07/public/favicon.ico
--------------------------------------------------------------------------------
/ch07/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ch07/src/app/app.config.ts:
--------------------------------------------------------------------------------
1 | import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
2 | import { provideRouter } from '@angular/router';
3 |
4 | import { routes } from './app.routes';
5 |
6 | export const appConfig: ApplicationConfig = {
7 | providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)]
8 | };
9 |
--------------------------------------------------------------------------------
/ch07/src/app/app.routes.ts:
--------------------------------------------------------------------------------
1 | import { Routes } from '@angular/router';
2 |
3 | export const routes: Routes = [];
4 |
--------------------------------------------------------------------------------
/ch07/src/app/app.settings.ts:
--------------------------------------------------------------------------------
1 | import { InjectionToken } from '@angular/core';
2 |
3 | export interface AppSettings {
4 | title: string;
5 | version: string;
6 | }
7 |
8 | export const appSettings: AppSettings = {
9 | title: 'My e-shop',
10 | version: '1.0'
11 | };
12 |
13 | export const APP_SETTINGS = new InjectionToken('app.settings');
14 |
--------------------------------------------------------------------------------
/ch07/src/app/copyright.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { CopyrightDirective } from './copyright.directive';
2 |
3 | describe('CopyrightDirective', () => {
4 | it('should create an instance', () => {
5 | const directive = new CopyrightDirective();
6 | expect(directive).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/ch07/src/app/copyright.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, ElementRef } from '@angular/core';
2 |
3 | @Directive({
4 | selector: '[appCopyright]'
5 | })
6 | export class CopyrightDirective {
7 |
8 | constructor(el: ElementRef) {
9 | const currentYear = new Date().getFullYear();
10 | const targetEl: HTMLElement = el.nativeElement;
11 | targetEl.classList.add('copyright');
12 | targetEl.textContent = `Copyright ©${currentYear} All Rights Reserved`;
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/ch07/src/app/product-detail/product-detail.component.html:
--------------------------------------------------------------------------------
1 | @if (product()) {
2 | You selected:
3 | {{product()!.title}}
4 |
5 | {{product()!.price | currency:'EUR'}}
6 |
7 | @for (cat of product()!.categories | keyvalue; track cat.key) {
8 |
{{cat.value | lowercase}}
9 | }
10 |
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/ch07/src/app/product-detail/product-detail.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { Component, input, output } from '@angular/core';
3 | import { Product } from '../product';
4 |
5 | @Component({
6 | selector: 'app-product-detail',
7 | imports: [CommonModule],
8 | templateUrl: './product-detail.component.html',
9 | styleUrl: './product-detail.component.css'
10 | })
11 | export class ProductDetailComponent {
12 | product = input();
13 | added = output();
14 |
15 | addToCart() {
16 | this.added.emit();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ch07/src/app/product-list/product-list.component.html:
--------------------------------------------------------------------------------
1 | @if (products().length > 0) {
2 | Products ({{products().length}})
3 | }
4 |
5 |
6 | @for (product of products() | sort; track product.id) {
7 | -
8 | @switch (product.title) {
9 | @case ('Keyboard') { ⌨️ }
10 | @case ('Microphone') { 🎙️ }
11 | @default { 🏷️ }
12 | }
13 | {{product.title}}
14 |
15 | } @empty {
16 | No products found!
17 | }
18 |
19 |
20 |
24 |
--------------------------------------------------------------------------------
/ch07/src/app/product.ts:
--------------------------------------------------------------------------------
1 | export interface Product {
2 | id: number;
3 | title: string;
4 | price: number;
5 | categories: Record;
6 | }
7 |
--------------------------------------------------------------------------------
/ch07/src/app/products.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { ProductsService } from './products.service';
4 |
5 | describe('ProductsService', () => {
6 | let service: ProductsService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(ProductsService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/ch07/src/app/sort.pipe.spec.ts:
--------------------------------------------------------------------------------
1 | import { SortPipe } from './sort.pipe';
2 |
3 | describe('SortPipe', () => {
4 | it('create an instance', () => {
5 | const pipe = new SortPipe();
6 | expect(pipe).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/ch07/src/app/sort.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 | import { Product } from './product';
3 |
4 | @Pipe({
5 | name: 'sort'
6 | })
7 | export class SortPipe implements PipeTransform {
8 |
9 | transform(value: Product[], args: keyof Product = 'title'): Product[] {
10 | if (value) {
11 | return value.sort((a: Product, b: Product) => {
12 | if (a[args] < b[args]) {
13 | return -1;
14 | } else if (b[args] < a[args]) {
15 | return 1;
16 | }
17 | return 0;
18 | });
19 | }
20 | return [];
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/ch07/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MyApp
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/ch07/src/main.ts:
--------------------------------------------------------------------------------
1 | import { bootstrapApplication } from '@angular/platform-browser';
2 | import { appConfig } from './app/app.config';
3 | import { AppComponent } from './app/app.component';
4 |
5 | bootstrapApplication(AppComponent, appConfig)
6 | .catch((err) => console.error(err));
7 |
--------------------------------------------------------------------------------
/ch07/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 | .copyright {
3 | font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
4 | Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
5 | "Segoe UI Symbol";
6 | width: 100%;
7 | min-height: 100%;
8 | display: flex;
9 | justify-content: center;
10 | align-items: center;
11 | padding: 1rem;
12 | box-sizing: inherit;
13 | position: relative;
14 | }
15 | input.valid {
16 | border: solid green;
17 | }
18 | input.invalid {
19 | border: solid red;
20 | }
21 |
--------------------------------------------------------------------------------
/ch07/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/app",
7 | "types": []
8 | },
9 | "files": [
10 | "src/main.ts"
11 | ],
12 | "include": [
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/ch07/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/spec",
7 | "types": [
8 | "jasmine"
9 | ]
10 | },
11 | "include": [
12 | "src/**/*.spec.ts",
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/ch08/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 | ij_typescript_use_double_quotes = false
14 |
15 | [*.md]
16 | max_line_length = off
17 | trim_trailing_whitespace = false
18 |
--------------------------------------------------------------------------------
/ch08/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/ch08/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/ch08/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch08/public/favicon.ico
--------------------------------------------------------------------------------
/ch08/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 | {{ settings.title }}
3 |
4 |
5 |
6 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/ch08/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, inject } from '@angular/core';
2 | import { RouterOutlet } from '@angular/router';
3 | import { ProductListComponent } from './product-list/product-list.component';
4 | import { CopyrightDirective } from './copyright.directive';
5 | import { APP_SETTINGS } from './app.settings';
6 | import { AuthComponent } from './auth/auth.component';
7 |
8 | @Component({
9 | selector: 'app-root',
10 | imports: [
11 | RouterOutlet,
12 | ProductListComponent,
13 | CopyrightDirective,
14 | AuthComponent
15 | ],
16 | templateUrl: './app.component.html',
17 | styleUrl: './app.component.css'
18 | })
19 | export class AppComponent {
20 | settings = inject(APP_SETTINGS);
21 | }
22 |
--------------------------------------------------------------------------------
/ch08/src/app/app.config.ts:
--------------------------------------------------------------------------------
1 | import { provideHttpClient, withInterceptors } from '@angular/common/http';
2 | import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
3 | import { provideRouter } from '@angular/router';
4 |
5 | import { routes } from './app.routes';
6 | import { APP_SETTINGS, appSettings } from './app.settings';
7 | import { authInterceptor } from './auth.interceptor';
8 |
9 | export const appConfig: ApplicationConfig = {
10 | providers: [
11 | provideZoneChangeDetection({ eventCoalescing: true }),
12 | provideRouter(routes),
13 | provideHttpClient(withInterceptors([authInterceptor])),
14 | { provide: APP_SETTINGS, useValue: appSettings }
15 | ]
16 | };
17 |
--------------------------------------------------------------------------------
/ch08/src/app/app.routes.ts:
--------------------------------------------------------------------------------
1 | import { Routes } from '@angular/router';
2 |
3 | export const routes: Routes = [];
4 |
--------------------------------------------------------------------------------
/ch08/src/app/app.settings.ts:
--------------------------------------------------------------------------------
1 | import { InjectionToken } from '@angular/core';
2 |
3 | export interface AppSettings {
4 | title: string;
5 | version: string;
6 | apiUrl: string;
7 | }
8 |
9 | export const appSettings: AppSettings = {
10 | title: 'My e-shop',
11 | version: '1.0',
12 | apiUrl: 'https://fakestoreapi.com'
13 | };
14 |
15 | export const APP_SETTINGS = new InjectionToken('app.settings');
16 |
--------------------------------------------------------------------------------
/ch08/src/app/auth.interceptor.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { HttpInterceptorFn } from '@angular/common/http';
3 |
4 | import { authInterceptor } from './auth.interceptor';
5 |
6 | describe('authInterceptor', () => {
7 | const interceptor: HttpInterceptorFn = (req, next) =>
8 | TestBed.runInInjectionContext(() => authInterceptor(req, next));
9 |
10 | beforeEach(() => {
11 | TestBed.configureTestingModule({});
12 | });
13 |
14 | it('should be created', () => {
15 | expect(interceptor).toBeTruthy();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/ch08/src/app/auth.interceptor.ts:
--------------------------------------------------------------------------------
1 | import { HttpInterceptorFn } from '@angular/common/http';
2 |
3 | export const authInterceptor: HttpInterceptorFn = (req, next) => {
4 | const authReq = req.clone({
5 | setHeaders: { Authorization: 'myToken' }
6 | });
7 | return next(authReq);
8 | };
9 |
--------------------------------------------------------------------------------
/ch08/src/app/auth.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { AuthService } from './auth.service';
4 |
5 | describe('AuthService', () => {
6 | let service: AuthService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(AuthService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/ch08/src/app/auth/auth.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch08/src/app/auth/auth.component.css
--------------------------------------------------------------------------------
/ch08/src/app/auth/auth.component.html:
--------------------------------------------------------------------------------
1 | @if (!authService.isLoggedIn()) {
2 |
3 | } @else {
4 |
5 | }
6 |
--------------------------------------------------------------------------------
/ch08/src/app/auth/auth.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AuthComponent } from './auth.component';
4 |
5 | describe('AuthComponent', () => {
6 | let component: AuthComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | imports: [AuthComponent]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(AuthComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/ch08/src/app/auth/auth.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { AuthService } from '../auth.service';
3 |
4 | @Component({
5 | selector: 'app-auth',
6 | imports: [],
7 | templateUrl: './auth.component.html',
8 | styleUrl: './auth.component.css'
9 | })
10 | export class AuthComponent {
11 | constructor(public authService: AuthService) {}
12 |
13 | login() {
14 | this.authService.login('david_r', '3478*#54').subscribe();
15 | }
16 |
17 | logout() {
18 | this.authService.logout();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ch08/src/app/copyright.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { CopyrightDirective } from './copyright.directive';
2 |
3 | describe('CopyrightDirective', () => {
4 | it('should create an instance', () => {
5 | const directive = new CopyrightDirective();
6 | expect(directive).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/ch08/src/app/copyright.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, ElementRef } from '@angular/core';
2 |
3 | @Directive({
4 | selector: '[appCopyright]'
5 | })
6 | export class CopyrightDirective {
7 |
8 | constructor(el: ElementRef) {
9 | const currentYear = new Date().getFullYear();
10 | const targetEl: HTMLElement = el.nativeElement;
11 | targetEl.classList.add('copyright');
12 | targetEl.textContent = `Copyright ©${currentYear} All Rights Reserved`;
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/ch08/src/app/product-create/product-create.component.css:
--------------------------------------------------------------------------------
1 | input {
2 | width: 200px;
3 | }
4 |
5 | select {
6 | border-radius: 4px;
7 | padding: 8px;
8 | margin-bottom: 16px;
9 | border: 1px solid #BDBDBD;
10 | width: 220px;
11 | }
12 |
13 | label {
14 | margin-bottom: 4px;
15 | display: block;
16 | }
17 |
--------------------------------------------------------------------------------
/ch08/src/app/product-create/product-create.component.html:
--------------------------------------------------------------------------------
1 | Add new product
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/ch08/src/app/product-create/product-create.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { ProductsService } from '../products.service';
3 |
4 | @Component({
5 | selector: 'app-product-create',
6 | imports: [],
7 | templateUrl: './product-create.component.html',
8 | styleUrl: './product-create.component.css'
9 | })
10 | export class ProductCreateComponent {
11 | constructor(private productsService: ProductsService) {}
12 |
13 | createProduct(title: string, price: string, category: string) {
14 | this.productsService.addProduct({
15 | title,
16 | price: Number(price),
17 | category
18 | }).subscribe();
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/ch08/src/app/product.ts:
--------------------------------------------------------------------------------
1 | export interface Product {
2 | id: number;
3 | title: string;
4 | price: number;
5 | category: string;
6 | }
7 |
--------------------------------------------------------------------------------
/ch08/src/app/products.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { ProductsService } from './products.service';
4 |
5 | describe('ProductsService', () => {
6 | let service: ProductsService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(ProductsService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/ch08/src/app/sort.pipe.spec.ts:
--------------------------------------------------------------------------------
1 | import { SortPipe } from './sort.pipe';
2 |
3 | describe('SortPipe', () => {
4 | it('create an instance', () => {
5 | const pipe = new SortPipe();
6 | expect(pipe).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/ch08/src/app/sort.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 | import { Product } from './product';
3 |
4 | @Pipe({
5 | name: 'sort'
6 | })
7 | export class SortPipe implements PipeTransform {
8 |
9 | transform(value: Product[], args: keyof Product = 'title'): Product[] {
10 | if (value) {
11 | return value.sort((a: Product, b: Product) => {
12 | if (a[args] < b[args]) {
13 | return -1;
14 | } else if (b[args] < a[args]) {
15 | return 1;
16 | }
17 | return 0;
18 | });
19 | }
20 | return [];
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/ch08/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MyApp
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/ch08/src/main.ts:
--------------------------------------------------------------------------------
1 | import { bootstrapApplication } from '@angular/platform-browser';
2 | import { appConfig } from './app/app.config';
3 | import { AppComponent } from './app/app.component';
4 |
5 | bootstrapApplication(AppComponent, appConfig)
6 | .catch((err) => console.error(err));
7 |
--------------------------------------------------------------------------------
/ch08/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/app",
7 | "types": []
8 | },
9 | "files": [
10 | "src/main.ts"
11 | ],
12 | "include": [
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/ch08/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/spec",
7 | "types": [
8 | "jasmine"
9 | ]
10 | },
11 | "include": [
12 | "src/**/*.spec.ts",
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/ch09/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 | ij_typescript_use_double_quotes = false
14 |
15 | [*.md]
16 | max_line_length = off
17 | trim_trailing_whitespace = false
18 |
--------------------------------------------------------------------------------
/ch09/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/ch09/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/ch09/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch09/public/favicon.ico
--------------------------------------------------------------------------------
/ch09/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 | {{ settings.title }}
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/ch09/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, inject } from '@angular/core';
2 | import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
3 | import { CopyrightDirective } from './copyright.directive';
4 | import { APP_SETTINGS } from './app.settings';
5 | import { AuthComponent } from './auth/auth.component';
6 |
7 | @Component({
8 | selector: 'app-root',
9 | imports: [
10 | RouterOutlet,
11 | RouterLink,
12 | RouterLinkActive,
13 | CopyrightDirective,
14 | AuthComponent
15 | ],
16 | templateUrl: './app.component.html',
17 | styleUrl: './app.component.css'
18 | })
19 | export class AppComponent {
20 | settings = inject(APP_SETTINGS);
21 | }
22 |
--------------------------------------------------------------------------------
/ch09/src/app/app.config.ts:
--------------------------------------------------------------------------------
1 | import { provideHttpClient } from '@angular/common/http';
2 | import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
3 | import { provideRouter } from '@angular/router';
4 |
5 | import { routes } from './app.routes';
6 | import { APP_SETTINGS, appSettings } from './app.settings';
7 |
8 | export const appConfig: ApplicationConfig = {
9 | providers: [
10 | provideZoneChangeDetection({ eventCoalescing: true }),
11 | provideRouter(routes),
12 | provideHttpClient(),
13 | { provide: APP_SETTINGS, useValue: appSettings }
14 | ]
15 | };
16 |
--------------------------------------------------------------------------------
/ch09/src/app/app.settings.ts:
--------------------------------------------------------------------------------
1 | import { InjectionToken } from '@angular/core';
2 |
3 | export interface AppSettings {
4 | title: string;
5 | version: string;
6 | apiUrl: string;
7 | }
8 |
9 | export const appSettings: AppSettings = {
10 | title: 'My e-shop',
11 | version: '1.0',
12 | apiUrl: 'https://fakestoreapi.com'
13 | };
14 |
15 | export const APP_SETTINGS = new InjectionToken('app.settings');
16 |
--------------------------------------------------------------------------------
/ch09/src/app/auth.guard.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { CanActivateFn } from '@angular/router';
3 |
4 | import { authGuard } from './auth.guard';
5 |
6 | describe('authGuard', () => {
7 | const executeGuard: CanActivateFn = (...guardParameters) =>
8 | TestBed.runInInjectionContext(() => authGuard(...guardParameters));
9 |
10 | beforeEach(() => {
11 | TestBed.configureTestingModule({});
12 | });
13 |
14 | it('should be created', () => {
15 | expect(executeGuard).toBeTruthy();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/ch09/src/app/auth.guard.ts:
--------------------------------------------------------------------------------
1 | import { inject } from '@angular/core';
2 | import { CanActivateFn, CanMatchFn, Router } from '@angular/router';
3 | import { AuthService } from './auth.service';
4 |
5 | export const authGuard: CanActivateFn | CanMatchFn = () => {
6 | const authService = inject(AuthService);
7 | const router = inject(Router);
8 |
9 | if (authService.isLoggedIn()) {
10 | return true;
11 | }
12 | return router.parseUrl('/');
13 | };
14 |
--------------------------------------------------------------------------------
/ch09/src/app/auth.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { AuthService } from './auth.service';
4 |
5 | describe('AuthService', () => {
6 | let service: AuthService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(AuthService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/ch09/src/app/auth/auth.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch09/src/app/auth/auth.component.css
--------------------------------------------------------------------------------
/ch09/src/app/auth/auth.component.html:
--------------------------------------------------------------------------------
1 | @if (!authService.isLoggedIn()) {
2 |
3 | } @else {
4 |
5 | }
6 |
--------------------------------------------------------------------------------
/ch09/src/app/auth/auth.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AuthComponent } from './auth.component';
4 |
5 | describe('AuthComponent', () => {
6 | let component: AuthComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | imports: [AuthComponent]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(AuthComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/ch09/src/app/auth/auth.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { AuthService } from '../auth.service';
3 |
4 | @Component({
5 | selector: 'app-auth',
6 | imports: [],
7 | templateUrl: './auth.component.html',
8 | styleUrl: './auth.component.css'
9 | })
10 | export class AuthComponent {
11 | constructor(public authService: AuthService) {}
12 |
13 | login() {
14 | this.authService.login('david_r', '3478*#54').subscribe();
15 | }
16 |
17 | logout() {
18 | this.authService.logout();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ch09/src/app/cart/cart.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch09/src/app/cart/cart.component.css
--------------------------------------------------------------------------------
/ch09/src/app/cart/cart.component.html:
--------------------------------------------------------------------------------
1 | cart works!
2 |
--------------------------------------------------------------------------------
/ch09/src/app/cart/cart.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { CartComponent } from './cart.component';
4 |
5 | describe('CartComponent', () => {
6 | let component: CartComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | imports: [CartComponent]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(CartComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/ch09/src/app/cart/cart.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-cart',
5 | imports: [],
6 | templateUrl: './cart.component.html',
7 | styleUrl: './cart.component.css'
8 | })
9 | export class CartComponent {
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/ch09/src/app/checkout.guard.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { CanDeactivateFn } from '@angular/router';
3 |
4 | import { checkoutGuard } from './checkout.guard';
5 |
6 | describe('checkoutGuard', () => {
7 | const executeGuard: CanDeactivateFn = (...guardParameters) =>
8 | TestBed.runInInjectionContext(() => checkoutGuard(...guardParameters));
9 |
10 | beforeEach(() => {
11 | TestBed.configureTestingModule({});
12 | });
13 |
14 | it('should be created', () => {
15 | expect(executeGuard).toBeTruthy();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/ch09/src/app/checkout.guard.ts:
--------------------------------------------------------------------------------
1 | import { CanDeactivateFn } from '@angular/router';
2 | import { CartComponent } from './cart/cart.component';
3 |
4 | export const checkoutGuard: CanDeactivateFn = () => {
5 | const confirmation = confirm(
6 | 'You have pending items in your cart. Do you want to continue?'
7 | );
8 | return confirmation;
9 | };
10 |
--------------------------------------------------------------------------------
/ch09/src/app/copyright.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { CopyrightDirective } from './copyright.directive';
2 |
3 | describe('CopyrightDirective', () => {
4 | it('should create an instance', () => {
5 | const directive = new CopyrightDirective();
6 | expect(directive).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/ch09/src/app/copyright.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, ElementRef } from '@angular/core';
2 |
3 | @Directive({
4 | selector: '[appCopyright]'
5 | })
6 | export class CopyrightDirective {
7 |
8 | constructor(el: ElementRef) {
9 | const currentYear = new Date().getFullYear();
10 | const targetEl: HTMLElement = el.nativeElement;
11 | targetEl.classList.add('copyright');
12 | targetEl.textContent = `Copyright ©${currentYear} All Rights Reserved`;
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/ch09/src/app/product-create/product-create.component.css:
--------------------------------------------------------------------------------
1 | :host {
2 | width: 400px;
3 | }
4 |
5 | input {
6 | width: 200px;
7 | }
8 |
9 | select {
10 | border-radius: 4px;
11 | padding: 8px;
12 | margin-bottom: 16px;
13 | border: 1px solid #BDBDBD;
14 | width: 220px;
15 | }
16 |
17 | label {
18 | margin-bottom: 4px;
19 | display: block;
20 | }
21 |
--------------------------------------------------------------------------------
/ch09/src/app/product-create/product-create.component.html:
--------------------------------------------------------------------------------
1 | Add new product
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/ch09/src/app/product.ts:
--------------------------------------------------------------------------------
1 | export interface Product {
2 | id: number;
3 | title: string;
4 | price: number;
5 | category: string;
6 | }
7 |
--------------------------------------------------------------------------------
/ch09/src/app/products.resolver.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { ResolveFn } from '@angular/router';
3 |
4 | import { productsResolver } from './products.resolver';
5 |
6 | describe('productsResolver', () => {
7 | const executeResolver: ResolveFn = (...resolverParameters) =>
8 | TestBed.runInInjectionContext(() => productsResolver(...resolverParameters));
9 |
10 | beforeEach(() => {
11 | TestBed.configureTestingModule({});
12 | });
13 |
14 | it('should be created', () => {
15 | expect(executeResolver).toBeTruthy();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/ch09/src/app/products.resolver.ts:
--------------------------------------------------------------------------------
1 | import { inject } from '@angular/core';
2 | import { ResolveFn } from '@angular/router';
3 | import { Product } from './product';
4 | import { ProductsService } from './products.service';
5 |
6 | export const productsResolver: ResolveFn = (route, state) => {
7 | const productService = inject(ProductsService);
8 | const limit = Number(route.queryParamMap.get('limit'));
9 | return productService.getProducts(limit);
10 | };
11 |
--------------------------------------------------------------------------------
/ch09/src/app/products.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { ProductsService } from './products.service';
4 |
5 | describe('ProductsService', () => {
6 | let service: ProductsService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(ProductsService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/ch09/src/app/sort.pipe.spec.ts:
--------------------------------------------------------------------------------
1 | import { SortPipe } from './sort.pipe';
2 |
3 | describe('SortPipe', () => {
4 | it('create an instance', () => {
5 | const pipe = new SortPipe();
6 | expect(pipe).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/ch09/src/app/sort.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 | import { Product } from './product';
3 |
4 | @Pipe({
5 | name: 'sort'
6 | })
7 | export class SortPipe implements PipeTransform {
8 |
9 | transform(value: Product[], args: keyof Product = 'title'): Product[] {
10 | if (value) {
11 | return value.sort((a: Product, b: Product) => {
12 | if (a[args] < b[args]) {
13 | return -1;
14 | } else if (b[args] < a[args]) {
15 | return 1;
16 | }
17 | return 0;
18 | });
19 | }
20 | return [];
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/ch09/src/app/user.routes.ts:
--------------------------------------------------------------------------------
1 | import { UserComponent } from './user/user.component';
2 |
3 | export default [
4 | { path: '', component: UserComponent }
5 | ];
6 |
--------------------------------------------------------------------------------
/ch09/src/app/user/user.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch09/src/app/user/user.component.css
--------------------------------------------------------------------------------
/ch09/src/app/user/user.component.html:
--------------------------------------------------------------------------------
1 | user works!
2 |
--------------------------------------------------------------------------------
/ch09/src/app/user/user.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { UserComponent } from './user.component';
4 |
5 | describe('UserComponent', () => {
6 | let component: UserComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | imports: [UserComponent]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(UserComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/ch09/src/app/user/user.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-user',
5 | imports: [],
6 | templateUrl: './user.component.html',
7 | styleUrl: './user.component.css'
8 | })
9 | export class UserComponent {
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/ch09/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MyApp
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/ch09/src/main.ts:
--------------------------------------------------------------------------------
1 | import { bootstrapApplication } from '@angular/platform-browser';
2 | import { appConfig } from './app/app.config';
3 | import { AppComponent } from './app/app.component';
4 |
5 | bootstrapApplication(AppComponent, appConfig)
6 | .catch((err) => console.error(err));
7 |
--------------------------------------------------------------------------------
/ch09/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/app",
7 | "types": []
8 | },
9 | "files": [
10 | "src/main.ts"
11 | ],
12 | "include": [
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/ch09/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/spec",
7 | "types": [
8 | "jasmine"
9 | ]
10 | },
11 | "include": [
12 | "src/**/*.spec.ts",
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/ch10/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 | ij_typescript_use_double_quotes = false
14 |
15 | [*.md]
16 | max_line_length = off
17 | trim_trailing_whitespace = false
18 |
--------------------------------------------------------------------------------
/ch10/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/ch10/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/ch10/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch10/public/favicon.ico
--------------------------------------------------------------------------------
/ch10/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 | {{ settings.title }}
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/ch10/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, inject } from '@angular/core';
2 | import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
3 | import { CopyrightDirective } from './copyright.directive';
4 | import { APP_SETTINGS } from './app.settings';
5 | import { AuthComponent } from './auth/auth.component';
6 |
7 | @Component({
8 | selector: 'app-root',
9 | imports: [
10 | RouterOutlet,
11 | RouterLink,
12 | RouterLinkActive,
13 | CopyrightDirective,
14 | AuthComponent
15 | ],
16 | templateUrl: './app.component.html',
17 | styleUrl: './app.component.css'
18 | })
19 | export class AppComponent {
20 | settings = inject(APP_SETTINGS);
21 | }
22 |
--------------------------------------------------------------------------------
/ch10/src/app/app.config.ts:
--------------------------------------------------------------------------------
1 | import { provideHttpClient } from '@angular/common/http';
2 | import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
3 | import { provideRouter } from '@angular/router';
4 |
5 | import { routes } from './app.routes';
6 | import { APP_SETTINGS, appSettings } from './app.settings';
7 |
8 | export const appConfig: ApplicationConfig = {
9 | providers: [
10 | provideZoneChangeDetection({ eventCoalescing: true }),
11 | provideRouter(routes),
12 | provideHttpClient(),
13 | { provide: APP_SETTINGS, useValue: appSettings }
14 | ]
15 | };
16 |
--------------------------------------------------------------------------------
/ch10/src/app/app.settings.ts:
--------------------------------------------------------------------------------
1 | import { InjectionToken } from '@angular/core';
2 |
3 | export interface AppSettings {
4 | title: string;
5 | version: string;
6 | apiUrl: string;
7 | }
8 |
9 | export const appSettings: AppSettings = {
10 | title: 'My e-shop',
11 | version: '1.0',
12 | apiUrl: 'https://fakestoreapi.com'
13 | };
14 |
15 | export const APP_SETTINGS = new InjectionToken('app.settings');
16 |
--------------------------------------------------------------------------------
/ch10/src/app/auth.guard.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { CanActivateFn } from '@angular/router';
3 |
4 | import { authGuard } from './auth.guard';
5 |
6 | describe('authGuard', () => {
7 | const executeGuard: CanActivateFn = (...guardParameters) =>
8 | TestBed.runInInjectionContext(() => authGuard(...guardParameters));
9 |
10 | beforeEach(() => {
11 | TestBed.configureTestingModule({});
12 | });
13 |
14 | it('should be created', () => {
15 | expect(executeGuard).toBeTruthy();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/ch10/src/app/auth.guard.ts:
--------------------------------------------------------------------------------
1 | import { inject } from '@angular/core';
2 | import { CanActivateFn, CanMatchFn, Router } from '@angular/router';
3 | import { AuthService } from './auth.service';
4 |
5 | export const authGuard: CanActivateFn | CanMatchFn = () => {
6 | const authService = inject(AuthService);
7 | const router = inject(Router);
8 |
9 | if (authService.isLoggedIn()) {
10 | return true;
11 | }
12 | return router.parseUrl('/');
13 | };
14 |
--------------------------------------------------------------------------------
/ch10/src/app/auth.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { AuthService } from './auth.service';
4 |
5 | describe('AuthService', () => {
6 | let service: AuthService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(AuthService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/ch10/src/app/auth/auth.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch10/src/app/auth/auth.component.css
--------------------------------------------------------------------------------
/ch10/src/app/auth/auth.component.html:
--------------------------------------------------------------------------------
1 | @if (!authService.isLoggedIn()) {
2 |
3 | } @else {
4 |
5 | }
6 |
--------------------------------------------------------------------------------
/ch10/src/app/auth/auth.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AuthComponent } from './auth.component';
4 |
5 | describe('AuthComponent', () => {
6 | let component: AuthComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | imports: [AuthComponent]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(AuthComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/ch10/src/app/auth/auth.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { AuthService } from '../auth.service';
3 |
4 | @Component({
5 | selector: 'app-auth',
6 | imports: [],
7 | templateUrl: './auth.component.html',
8 | styleUrl: './auth.component.css'
9 | })
10 | export class AuthComponent {
11 | constructor(public authService: AuthService) {}
12 |
13 | login() {
14 | this.authService.login('david_r', '3478*#54').subscribe();
15 | }
16 |
17 | logout() {
18 | this.authService.logout();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ch10/src/app/cart.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { CartService } from './cart.service';
4 |
5 | describe('CartService', () => {
6 | let service: CartService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(CartService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/ch10/src/app/cart.ts:
--------------------------------------------------------------------------------
1 | export interface Cart {
2 | id: number;
3 | products: { productId :number }[];
4 | }
5 |
--------------------------------------------------------------------------------
/ch10/src/app/cart/cart.component.css:
--------------------------------------------------------------------------------
1 | :host {
2 | width: 500px;
3 | }
4 |
5 | input {
6 | width: 50px;
7 | }
8 |
--------------------------------------------------------------------------------
/ch10/src/app/cart/cart.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | @for(product of cartForm.controls.products.controls; track $index) {
4 |
5 |
6 | }
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ch10/src/app/cart/cart.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { CartComponent } from './cart.component';
4 |
5 | describe('CartComponent', () => {
6 | let component: CartComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | imports: [CartComponent]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(CartComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/ch10/src/app/checkout.guard.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { CanDeactivateFn } from '@angular/router';
3 |
4 | import { checkoutGuard } from './checkout.guard';
5 |
6 | describe('checkoutGuard', () => {
7 | const executeGuard: CanDeactivateFn = (...guardParameters) =>
8 | TestBed.runInInjectionContext(() => checkoutGuard(...guardParameters));
9 |
10 | beforeEach(() => {
11 | TestBed.configureTestingModule({});
12 | });
13 |
14 | it('should be created', () => {
15 | expect(executeGuard).toBeTruthy();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/ch10/src/app/checkout.guard.ts:
--------------------------------------------------------------------------------
1 | import { CanDeactivateFn } from '@angular/router';
2 | import { CartComponent } from './cart/cart.component';
3 | import { inject } from '@angular/core';
4 | import { CartService } from './cart.service';
5 |
6 | export const checkoutGuard: CanDeactivateFn = () => {
7 | const cartService = inject(CartService);
8 | if (cartService.cart) {
9 | const confirmation = confirm(
10 | 'You have pending items in your cart. Do you want to continue?'
11 | );
12 | return confirmation;
13 | }
14 | return true;
15 | };
16 |
--------------------------------------------------------------------------------
/ch10/src/app/copyright.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { CopyrightDirective } from './copyright.directive';
2 |
3 | describe('CopyrightDirective', () => {
4 | it('should create an instance', () => {
5 | const directive = new CopyrightDirective();
6 | expect(directive).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/ch10/src/app/copyright.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, ElementRef } from '@angular/core';
2 |
3 | @Directive({
4 | selector: '[appCopyright]'
5 | })
6 | export class CopyrightDirective {
7 |
8 | constructor(el: ElementRef) {
9 | const currentYear = new Date().getFullYear();
10 | const targetEl: HTMLElement = el.nativeElement;
11 | targetEl.classList.add('copyright');
12 | targetEl.textContent = `Copyright ©${currentYear} All Rights Reserved`;
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/ch10/src/app/price-maximum.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { PriceMaximumDirective } from './price-maximum.directive';
2 |
3 | describe('PriceMaximumDirective', () => {
4 | it('should create an instance', () => {
5 | const directive = new PriceMaximumDirective();
6 | expect(directive).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/ch10/src/app/price-maximum.validator.ts:
--------------------------------------------------------------------------------
1 | import { ValidatorFn, AbstractControl, ValidationErrors } from '@angular/forms';
2 |
3 | export function priceMaximumValidator(price: number): ValidatorFn {
4 | return (control: AbstractControl): ValidationErrors | null => {
5 | const isMax = control.value <= price;
6 | return isMax ? null : { priceMaximum: true };
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/ch10/src/app/product-create/product-create.component.css:
--------------------------------------------------------------------------------
1 | :host {
2 | width: 400px;
3 | }
4 |
5 | input {
6 | width: 200px;
7 | }
8 |
9 | select {
10 | border-radius: 4px;
11 | padding: 8px;
12 | margin-bottom: 16px;
13 | border: 1px solid #BDBDBD;
14 | width: 220px;
15 | }
16 |
--------------------------------------------------------------------------------
/ch10/src/app/product.ts:
--------------------------------------------------------------------------------
1 | export interface Product {
2 | id: number;
3 | title: string;
4 | price: number;
5 | category: string;
6 | }
7 |
--------------------------------------------------------------------------------
/ch10/src/app/products.resolver.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { ResolveFn } from '@angular/router';
3 |
4 | import { productsResolver } from './products.resolver';
5 |
6 | describe('productsResolver', () => {
7 | const executeResolver: ResolveFn = (...resolverParameters) =>
8 | TestBed.runInInjectionContext(() => productsResolver(...resolverParameters));
9 |
10 | beforeEach(() => {
11 | TestBed.configureTestingModule({});
12 | });
13 |
14 | it('should be created', () => {
15 | expect(executeResolver).toBeTruthy();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/ch10/src/app/products.resolver.ts:
--------------------------------------------------------------------------------
1 | import { inject } from '@angular/core';
2 | import { ResolveFn } from '@angular/router';
3 | import { Product } from './product';
4 | import { ProductsService } from './products.service';
5 |
6 | export const productsResolver: ResolveFn = (route, state) => {
7 | const productService = inject(ProductsService);
8 | const limit = Number(route.queryParamMap.get('limit'));
9 | return productService.getProducts(limit);
10 | };
11 |
--------------------------------------------------------------------------------
/ch10/src/app/products.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { ProductsService } from './products.service';
4 |
5 | describe('ProductsService', () => {
6 | let service: ProductsService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(ProductsService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/ch10/src/app/sort.pipe.spec.ts:
--------------------------------------------------------------------------------
1 | import { SortPipe } from './sort.pipe';
2 |
3 | describe('SortPipe', () => {
4 | it('create an instance', () => {
5 | const pipe = new SortPipe();
6 | expect(pipe).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/ch10/src/app/sort.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 | import { Product } from './product';
3 |
4 | @Pipe({
5 | name: 'sort'
6 | })
7 | export class SortPipe implements PipeTransform {
8 |
9 | transform(value: Product[], args: keyof Product = 'title'): Product[] {
10 | if (value) {
11 | return value.sort((a: Product, b: Product) => {
12 | if (a[args] < b[args]) {
13 | return -1;
14 | } else if (b[args] < a[args]) {
15 | return 1;
16 | }
17 | return 0;
18 | });
19 | }
20 | return [];
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/ch10/src/app/user.routes.ts:
--------------------------------------------------------------------------------
1 | import { UserComponent } from './user/user.component';
2 |
3 | export default [
4 | { path: '', component: UserComponent }
5 | ];
6 |
--------------------------------------------------------------------------------
/ch10/src/app/user/user.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch10/src/app/user/user.component.css
--------------------------------------------------------------------------------
/ch10/src/app/user/user.component.html:
--------------------------------------------------------------------------------
1 | user works!
2 |
--------------------------------------------------------------------------------
/ch10/src/app/user/user.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { UserComponent } from './user.component';
4 |
5 | describe('UserComponent', () => {
6 | let component: UserComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | imports: [UserComponent]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(UserComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/ch10/src/app/user/user.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-user',
5 | imports: [],
6 | templateUrl: './user.component.html',
7 | styleUrl: './user.component.css'
8 | })
9 | export class UserComponent {
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/ch10/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MyApp
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/ch10/src/main.ts:
--------------------------------------------------------------------------------
1 | import { bootstrapApplication } from '@angular/platform-browser';
2 | import { appConfig } from './app/app.config';
3 | import { AppComponent } from './app/app.component';
4 |
5 | bootstrapApplication(AppComponent, appConfig)
6 | .catch((err) => console.error(err));
7 |
--------------------------------------------------------------------------------
/ch10/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/app",
7 | "types": []
8 | },
9 | "files": [
10 | "src/main.ts"
11 | ],
12 | "include": [
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/ch10/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/spec",
7 | "types": [
8 | "jasmine"
9 | ]
10 | },
11 | "include": [
12 | "src/**/*.spec.ts",
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/ch11/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 | ij_typescript_use_double_quotes = false
14 |
15 | [*.md]
16 | max_line_length = off
17 | trim_trailing_whitespace = false
18 |
--------------------------------------------------------------------------------
/ch11/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/ch11/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/ch11/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch11/public/favicon.ico
--------------------------------------------------------------------------------
/ch11/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 | {{ settings.title }}
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/ch11/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, inject } from '@angular/core';
2 | import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
3 | import { CopyrightDirective } from './copyright.directive';
4 | import { APP_SETTINGS } from './app.settings';
5 | import { AuthComponent } from './auth/auth.component';
6 |
7 | @Component({
8 | selector: 'app-root',
9 | imports: [
10 | RouterOutlet,
11 | RouterLink,
12 | RouterLinkActive,
13 | CopyrightDirective,
14 | AuthComponent
15 | ],
16 | templateUrl: './app.component.html',
17 | styleUrl: './app.component.css'
18 | })
19 | export class AppComponent {
20 | settings = inject(APP_SETTINGS);
21 | }
22 |
--------------------------------------------------------------------------------
/ch11/src/app/app.config.ts:
--------------------------------------------------------------------------------
1 | import { provideHttpClient } from '@angular/common/http';
2 | import { ApplicationConfig, ErrorHandler, provideZoneChangeDetection } from '@angular/core';
3 | import { provideRouter } from '@angular/router';
4 |
5 | import { routes } from './app.routes';
6 | import { APP_SETTINGS, appSettings } from './app.settings';
7 | import { AppErrorHandler } from './app-error-handler';
8 |
9 | export const appConfig: ApplicationConfig = {
10 | providers: [
11 | provideZoneChangeDetection({ eventCoalescing: true }),
12 | provideRouter(routes),
13 | provideHttpClient(),
14 | { provide: APP_SETTINGS, useValue: appSettings },
15 | { provide: ErrorHandler, useClass: AppErrorHandler }
16 | ]
17 | };
18 |
--------------------------------------------------------------------------------
/ch11/src/app/app.settings.ts:
--------------------------------------------------------------------------------
1 | import { InjectionToken } from '@angular/core';
2 |
3 | export interface AppSettings {
4 | title: string;
5 | version: string;
6 | apiUrl: string;
7 | }
8 |
9 | export const appSettings: AppSettings = {
10 | title: 'My e-shop',
11 | version: '1.0',
12 | apiUrl: 'https://fakestoreapi.com'
13 | };
14 |
15 | export const APP_SETTINGS = new InjectionToken('app.settings');
16 |
--------------------------------------------------------------------------------
/ch11/src/app/auth.guard.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { CanActivateFn } from '@angular/router';
3 |
4 | import { authGuard } from './auth.guard';
5 |
6 | describe('authGuard', () => {
7 | const executeGuard: CanActivateFn = (...guardParameters) =>
8 | TestBed.runInInjectionContext(() => authGuard(...guardParameters));
9 |
10 | beforeEach(() => {
11 | TestBed.configureTestingModule({});
12 | });
13 |
14 | it('should be created', () => {
15 | expect(executeGuard).toBeTruthy();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/ch11/src/app/auth.guard.ts:
--------------------------------------------------------------------------------
1 | import { inject } from '@angular/core';
2 | import { CanActivateFn, CanMatchFn, Router } from '@angular/router';
3 | import { AuthService } from './auth.service';
4 |
5 | export const authGuard: CanActivateFn | CanMatchFn = () => {
6 | const authService = inject(AuthService);
7 | const router = inject(Router);
8 |
9 | if (authService.isLoggedIn()) {
10 | return true;
11 | }
12 | return router.parseUrl('/');
13 | };
14 |
--------------------------------------------------------------------------------
/ch11/src/app/auth.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { AuthService } from './auth.service';
4 |
5 | describe('AuthService', () => {
6 | let service: AuthService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(AuthService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/ch11/src/app/auth/auth.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch11/src/app/auth/auth.component.css
--------------------------------------------------------------------------------
/ch11/src/app/auth/auth.component.html:
--------------------------------------------------------------------------------
1 | @if (!authService.isLoggedIn()) {
2 |
3 | } @else {
4 |
5 | }
6 |
--------------------------------------------------------------------------------
/ch11/src/app/auth/auth.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AuthComponent } from './auth.component';
4 |
5 | describe('AuthComponent', () => {
6 | let component: AuthComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | imports: [AuthComponent]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(AuthComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/ch11/src/app/auth/auth.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { AuthService } from '../auth.service';
3 |
4 | @Component({
5 | selector: 'app-auth',
6 | imports: [],
7 | templateUrl: './auth.component.html',
8 | styleUrl: './auth.component.css'
9 | })
10 | export class AuthComponent {
11 | constructor(public authService: AuthService) {}
12 |
13 | login() {
14 | this.authService.login('david_r', '3478*#54').subscribe();
15 | }
16 |
17 | logout() {
18 | this.authService.logout();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ch11/src/app/cart.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { CartService } from './cart.service';
4 |
5 | describe('CartService', () => {
6 | let service: CartService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(CartService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/ch11/src/app/cart.ts:
--------------------------------------------------------------------------------
1 | export interface Cart {
2 | id: number;
3 | products: { productId :number }[];
4 | }
5 |
--------------------------------------------------------------------------------
/ch11/src/app/cart/cart.component.css:
--------------------------------------------------------------------------------
1 | :host {
2 | width: 500px;
3 | }
4 |
5 | input {
6 | width: 50px;
7 | }
8 |
--------------------------------------------------------------------------------
/ch11/src/app/cart/cart.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | @for(product of cartForm.controls.products.controls; track $index) {
4 |
5 |
6 | }
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ch11/src/app/cart/cart.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { CartComponent } from './cart.component';
4 |
5 | describe('CartComponent', () => {
6 | let component: CartComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | imports: [CartComponent]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(CartComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/ch11/src/app/checkout.guard.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { CanDeactivateFn } from '@angular/router';
3 |
4 | import { checkoutGuard } from './checkout.guard';
5 |
6 | describe('checkoutGuard', () => {
7 | const executeGuard: CanDeactivateFn = (...guardParameters) =>
8 | TestBed.runInInjectionContext(() => checkoutGuard(...guardParameters));
9 |
10 | beforeEach(() => {
11 | TestBed.configureTestingModule({});
12 | });
13 |
14 | it('should be created', () => {
15 | expect(executeGuard).toBeTruthy();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/ch11/src/app/checkout.guard.ts:
--------------------------------------------------------------------------------
1 | import { CanDeactivateFn } from '@angular/router';
2 | import { CartComponent } from './cart/cart.component';
3 | import { inject } from '@angular/core';
4 | import { CartService } from './cart.service';
5 |
6 | export const checkoutGuard: CanDeactivateFn = () => {
7 | const cartService = inject(CartService);
8 | if (cartService.cart) {
9 | const confirmation = confirm(
10 | 'You have pending items in your cart. Do you want to continue?'
11 | );
12 | return confirmation;
13 | }
14 | return true;
15 | };
16 |
--------------------------------------------------------------------------------
/ch11/src/app/copyright.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { CopyrightDirective } from './copyright.directive';
2 |
3 | describe('CopyrightDirective', () => {
4 | it('should create an instance', () => {
5 | const directive = new CopyrightDirective();
6 | expect(directive).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/ch11/src/app/copyright.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, ElementRef } from '@angular/core';
2 |
3 | @Directive({
4 | selector: '[appCopyright]'
5 | })
6 | export class CopyrightDirective {
7 |
8 | constructor(el: ElementRef) {
9 | const currentYear = new Date().getFullYear();
10 | const targetEl: HTMLElement = el.nativeElement;
11 | targetEl.classList.add('copyright');
12 | targetEl.textContent = `Copyright ©${currentYear} All Rights Reserved`;
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/ch11/src/app/price-maximum.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { PriceMaximumDirective } from './price-maximum.directive';
2 |
3 | describe('PriceMaximumDirective', () => {
4 | it('should create an instance', () => {
5 | const directive = new PriceMaximumDirective();
6 | expect(directive).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/ch11/src/app/price-maximum.validator.ts:
--------------------------------------------------------------------------------
1 | import { ValidatorFn, AbstractControl, ValidationErrors } from '@angular/forms';
2 |
3 | export function priceMaximumValidator(price: number): ValidatorFn {
4 | return (control: AbstractControl): ValidationErrors | null => {
5 | const isMax = control.value <= price;
6 | return isMax ? null : { priceMaximum: true };
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/ch11/src/app/product-create/product-create.component.css:
--------------------------------------------------------------------------------
1 | :host {
2 | width: 400px;
3 | }
4 |
5 | input {
6 | width: 200px;
7 | }
8 |
9 | select {
10 | border-radius: 4px;
11 | padding: 8px;
12 | margin-bottom: 16px;
13 | border: 1px solid #BDBDBD;
14 | width: 220px;
15 | }
16 |
--------------------------------------------------------------------------------
/ch11/src/app/product.ts:
--------------------------------------------------------------------------------
1 | export interface Product {
2 | id: number;
3 | title: string;
4 | price: number;
5 | category: string;
6 | }
7 |
--------------------------------------------------------------------------------
/ch11/src/app/products.resolver.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { ResolveFn } from '@angular/router';
3 |
4 | import { productsResolver } from './products.resolver';
5 |
6 | describe('productsResolver', () => {
7 | const executeResolver: ResolveFn = (...resolverParameters) =>
8 | TestBed.runInInjectionContext(() => productsResolver(...resolverParameters));
9 |
10 | beforeEach(() => {
11 | TestBed.configureTestingModule({});
12 | });
13 |
14 | it('should be created', () => {
15 | expect(executeResolver).toBeTruthy();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/ch11/src/app/products.resolver.ts:
--------------------------------------------------------------------------------
1 | import { inject } from '@angular/core';
2 | import { ResolveFn } from '@angular/router';
3 | import { Product } from './product';
4 | import { ProductsService } from './products.service';
5 |
6 | export const productsResolver: ResolveFn = (route, state) => {
7 | const productService = inject(ProductsService);
8 | const limit = Number(route.queryParamMap.get('limit'));
9 | return productService.getProducts(limit);
10 | };
11 |
--------------------------------------------------------------------------------
/ch11/src/app/products.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { ProductsService } from './products.service';
4 |
5 | describe('ProductsService', () => {
6 | let service: ProductsService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(ProductsService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/ch11/src/app/sort.pipe.spec.ts:
--------------------------------------------------------------------------------
1 | import { SortPipe } from './sort.pipe';
2 |
3 | describe('SortPipe', () => {
4 | it('create an instance', () => {
5 | const pipe = new SortPipe();
6 | expect(pipe).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/ch11/src/app/sort.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 | import { Product } from './product';
3 |
4 | @Pipe({
5 | name: 'sort'
6 | })
7 | export class SortPipe implements PipeTransform {
8 |
9 | transform(value: Product[], args: keyof Product = 'title'): Product[] {
10 | if (value) {
11 | return value.sort((a: Product, b: Product) => {
12 | if (a[args] < b[args]) {
13 | return -1;
14 | } else if (b[args] < a[args]) {
15 | return 1;
16 | }
17 | return 0;
18 | });
19 | }
20 | return [];
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/ch11/src/app/user.routes.ts:
--------------------------------------------------------------------------------
1 | import { UserComponent } from './user/user.component';
2 |
3 | export default [
4 | { path: '', component: UserComponent }
5 | ];
6 |
--------------------------------------------------------------------------------
/ch11/src/app/user/user.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch11/src/app/user/user.component.css
--------------------------------------------------------------------------------
/ch11/src/app/user/user.component.html:
--------------------------------------------------------------------------------
1 | user works!
2 |
--------------------------------------------------------------------------------
/ch11/src/app/user/user.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { UserComponent } from './user.component';
4 |
5 | describe('UserComponent', () => {
6 | let component: UserComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | imports: [UserComponent]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(UserComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/ch11/src/app/user/user.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-user',
5 | imports: [],
6 | templateUrl: './user.component.html',
7 | styleUrl: './user.component.css'
8 | })
9 | export class UserComponent {
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/ch11/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MyApp
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/ch11/src/main.ts:
--------------------------------------------------------------------------------
1 | import { bootstrapApplication } from '@angular/platform-browser';
2 | import { appConfig } from './app/app.config';
3 | import { AppComponent } from './app/app.component';
4 |
5 | bootstrapApplication(AppComponent, appConfig)
6 | .catch((err) => console.error(err));
7 |
--------------------------------------------------------------------------------
/ch11/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/app",
7 | "types": []
8 | },
9 | "files": [
10 | "src/main.ts"
11 | ],
12 | "include": [
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/ch11/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/spec",
7 | "types": [
8 | "jasmine"
9 | ]
10 | },
11 | "include": [
12 | "src/**/*.spec.ts",
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/ch12/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 | ij_typescript_use_double_quotes = false
14 |
15 | [*.md]
16 | max_line_length = off
17 | trim_trailing_whitespace = false
18 |
--------------------------------------------------------------------------------
/ch12/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/ch12/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/ch12/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch12/public/favicon.ico
--------------------------------------------------------------------------------
/ch12/src/app/app.settings.ts:
--------------------------------------------------------------------------------
1 | import { InjectionToken } from '@angular/core';
2 |
3 | export interface AppSettings {
4 | title: string;
5 | version: string;
6 | apiUrl: string;
7 | }
8 |
9 | export const appSettings: AppSettings = {
10 | title: 'My e-shop',
11 | version: '1.0',
12 | apiUrl: 'https://fakestoreapi.com'
13 | };
14 |
15 | export const APP_SETTINGS = new InjectionToken('app.settings');
16 |
--------------------------------------------------------------------------------
/ch12/src/app/auth.guard.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { CanActivateFn } from '@angular/router';
3 |
4 | import { authGuard } from './auth.guard';
5 |
6 | describe('authGuard', () => {
7 | const executeGuard: CanActivateFn = (...guardParameters) =>
8 | TestBed.runInInjectionContext(() => authGuard(...guardParameters));
9 |
10 | beforeEach(() => {
11 | TestBed.configureTestingModule({});
12 | });
13 |
14 | it('should be created', () => {
15 | expect(executeGuard).toBeTruthy();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/ch12/src/app/auth.guard.ts:
--------------------------------------------------------------------------------
1 | import { inject } from '@angular/core';
2 | import { CanActivateFn, CanMatchFn, Router } from '@angular/router';
3 | import { AuthService } from './auth.service';
4 |
5 | export const authGuard: CanActivateFn | CanMatchFn = () => {
6 | const authService = inject(AuthService);
7 | const router = inject(Router);
8 |
9 | if (authService.isLoggedIn()) {
10 | return true;
11 | }
12 | return router.parseUrl('/');
13 | };
14 |
--------------------------------------------------------------------------------
/ch12/src/app/auth.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { AuthService } from './auth.service';
4 |
5 | describe('AuthService', () => {
6 | let service: AuthService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(AuthService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/ch12/src/app/auth/auth.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch12/src/app/auth/auth.component.css
--------------------------------------------------------------------------------
/ch12/src/app/auth/auth.component.html:
--------------------------------------------------------------------------------
1 | @if (!authService.isLoggedIn()) {
2 |
5 | } @else {
6 |
9 | }
10 |
--------------------------------------------------------------------------------
/ch12/src/app/auth/auth.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AuthComponent } from './auth.component';
4 |
5 | describe('AuthComponent', () => {
6 | let component: AuthComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | imports: [AuthComponent]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(AuthComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/ch12/src/app/auth/auth.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { AuthService } from '../auth.service';
3 | import { MatButton } from '@angular/material/button';
4 |
5 | @Component({
6 | selector: 'app-auth',
7 | imports: [MatButton],
8 | templateUrl: './auth.component.html',
9 | styleUrl: './auth.component.css'
10 | })
11 | export class AuthComponent {
12 | constructor(public authService: AuthService) {}
13 |
14 | login() {
15 | this.authService.login('david_r', '3478*#54').subscribe();
16 | }
17 |
18 | logout() {
19 | this.authService.logout();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ch12/src/app/cart.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { CartService } from './cart.service';
4 |
5 | describe('CartService', () => {
6 | let service: CartService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(CartService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/ch12/src/app/cart.ts:
--------------------------------------------------------------------------------
1 | export interface Cart {
2 | id: number;
3 | products: { productId :number }[];
4 | }
5 |
--------------------------------------------------------------------------------
/ch12/src/app/cart/cart.component.css:
--------------------------------------------------------------------------------
1 | :host {
2 | width: 500px;
3 | }
4 |
--------------------------------------------------------------------------------
/ch12/src/app/cart/cart.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | @for(product of cartForm.controls.products.controls; track $index) {
4 |
5 | {{products[$index].title}}
6 |
11 |
12 | }
13 |
14 |
15 |
--------------------------------------------------------------------------------
/ch12/src/app/cart/cart.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { CartComponent } from './cart.component';
4 |
5 | describe('CartComponent', () => {
6 | let component: CartComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | imports: [CartComponent]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(CartComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/ch12/src/app/checkout.guard.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { CanDeactivateFn } from '@angular/router';
3 |
4 | import { checkoutGuard } from './checkout.guard';
5 |
6 | describe('checkoutGuard', () => {
7 | const executeGuard: CanDeactivateFn = (...guardParameters) =>
8 | TestBed.runInInjectionContext(() => checkoutGuard(...guardParameters));
9 |
10 | beforeEach(() => {
11 | TestBed.configureTestingModule({});
12 | });
13 |
14 | it('should be created', () => {
15 | expect(executeGuard).toBeTruthy();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/ch12/src/app/checkout/checkout.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch12/src/app/checkout/checkout.component.css
--------------------------------------------------------------------------------
/ch12/src/app/checkout/checkout.component.html:
--------------------------------------------------------------------------------
1 | Cart Checkout
2 |
3 |
4 | You have {{ data }} pending items in your cart.
5 | Do you want to continue?
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/ch12/src/app/checkout/checkout.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { CheckoutComponent } from './checkout.component';
4 |
5 | describe('CheckoutComponent', () => {
6 | let component: CheckoutComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | imports: [CheckoutComponent]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(CheckoutComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/ch12/src/app/checkout/checkout.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, inject } from '@angular/core';
2 | import { MatButton } from '@angular/material/button';
3 | import { MatDialogModule, MAT_DIALOG_DATA } from '@angular/material/dialog';
4 |
5 | @Component({
6 | selector: 'app-checkout',
7 | imports: [MatButton, MatDialogModule],
8 | templateUrl: './checkout.component.html',
9 | styleUrl: './checkout.component.css'
10 | })
11 | export class CheckoutComponent {
12 | data = inject(MAT_DIALOG_DATA);
13 | }
14 |
--------------------------------------------------------------------------------
/ch12/src/app/copyright.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { CopyrightDirective } from './copyright.directive';
2 |
3 | describe('CopyrightDirective', () => {
4 | it('should create an instance', () => {
5 | const directive = new CopyrightDirective();
6 | expect(directive).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/ch12/src/app/copyright.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, ElementRef } from '@angular/core';
2 |
3 | @Directive({
4 | selector: '[appCopyright]'
5 | })
6 | export class CopyrightDirective {
7 |
8 | constructor(el: ElementRef) {
9 | const currentYear = new Date().getFullYear();
10 | const targetEl: HTMLElement = el.nativeElement;
11 | targetEl.classList.add('copyright');
12 | targetEl.textContent = `Copyright ©${currentYear} All Rights Reserved`;
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/ch12/src/app/price-maximum.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { PriceMaximumDirective } from './price-maximum.directive';
2 |
3 | describe('PriceMaximumDirective', () => {
4 | it('should create an instance', () => {
5 | const directive = new PriceMaximumDirective();
6 | expect(directive).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/ch12/src/app/price-maximum.validator.ts:
--------------------------------------------------------------------------------
1 | import { ValidatorFn, AbstractControl, ValidationErrors } from '@angular/forms';
2 |
3 | export function priceMaximumValidator(price: number): ValidatorFn {
4 | return (control: AbstractControl): ValidationErrors | null => {
5 | const isMax = control.value <= price;
6 | return isMax ? null : { priceMaximum: true };
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/ch12/src/app/product-create/product-create.component.css:
--------------------------------------------------------------------------------
1 | :host {
2 | width: 400px;
3 | }
4 |
5 | select {
6 | border-radius: 4px;
7 | padding: 8px;
8 | margin-bottom: 16px;
9 | border: 1px solid #BDBDBD;
10 | width: 220px;
11 | }
12 |
--------------------------------------------------------------------------------
/ch12/src/app/product.ts:
--------------------------------------------------------------------------------
1 | export interface Product {
2 | id: number;
3 | title: string;
4 | price: number;
5 | category: string;
6 | image: string;
7 | }
8 |
--------------------------------------------------------------------------------
/ch12/src/app/products.resolver.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { ResolveFn } from '@angular/router';
3 |
4 | import { productsResolver } from './products.resolver';
5 |
6 | describe('productsResolver', () => {
7 | const executeResolver: ResolveFn = (...resolverParameters) =>
8 | TestBed.runInInjectionContext(() => productsResolver(...resolverParameters));
9 |
10 | beforeEach(() => {
11 | TestBed.configureTestingModule({});
12 | });
13 |
14 | it('should be created', () => {
15 | expect(executeResolver).toBeTruthy();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/ch12/src/app/products.resolver.ts:
--------------------------------------------------------------------------------
1 | import { inject } from '@angular/core';
2 | import { ResolveFn } from '@angular/router';
3 | import { Product } from './product';
4 | import { ProductsService } from './products.service';
5 |
6 | export const productsResolver: ResolveFn = (route, state) => {
7 | const productService = inject(ProductsService);
8 | const limit = Number(route.queryParamMap.get('limit'));
9 | return productService.getProducts(limit);
10 | };
11 |
--------------------------------------------------------------------------------
/ch12/src/app/products.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { ProductsService } from './products.service';
4 |
5 | describe('ProductsService', () => {
6 | let service: ProductsService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(ProductsService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/ch12/src/app/sort.pipe.spec.ts:
--------------------------------------------------------------------------------
1 | import { SortPipe } from './sort.pipe';
2 |
3 | describe('SortPipe', () => {
4 | it('create an instance', () => {
5 | const pipe = new SortPipe();
6 | expect(pipe).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/ch12/src/app/sort.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 | import { Product } from './product';
3 |
4 | @Pipe({
5 | name: 'sort'
6 | })
7 | export class SortPipe implements PipeTransform {
8 |
9 | transform(value: Product[], args: keyof Product = 'title'): Product[] {
10 | if (value) {
11 | return value.sort((a: Product, b: Product) => {
12 | if (a[args] < b[args]) {
13 | return -1;
14 | } else if (b[args] < a[args]) {
15 | return 1;
16 | }
17 | return 0;
18 | });
19 | }
20 | return [];
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/ch12/src/app/user.routes.ts:
--------------------------------------------------------------------------------
1 | import { UserComponent } from './user/user.component';
2 |
3 | export default [
4 | { path: '', component: UserComponent }
5 | ];
6 |
--------------------------------------------------------------------------------
/ch12/src/app/user/user.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch12/src/app/user/user.component.css
--------------------------------------------------------------------------------
/ch12/src/app/user/user.component.html:
--------------------------------------------------------------------------------
1 | user works!
2 |
--------------------------------------------------------------------------------
/ch12/src/app/user/user.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { UserComponent } from './user.component';
4 |
5 | describe('UserComponent', () => {
6 | let component: UserComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | imports: [UserComponent]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(UserComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/ch12/src/app/user/user.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-user',
5 | imports: [],
6 | templateUrl: './user.component.html',
7 | styleUrl: './user.component.css'
8 | })
9 | export class UserComponent {
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/ch12/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MyApp
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/ch12/src/main.ts:
--------------------------------------------------------------------------------
1 | import { bootstrapApplication } from '@angular/platform-browser';
2 | import { appConfig } from './app/app.config';
3 | import { AppComponent } from './app/app.component';
4 |
5 | bootstrapApplication(AppComponent, appConfig)
6 | .catch((err) => console.error(err));
7 |
--------------------------------------------------------------------------------
/ch12/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/app",
7 | "types": []
8 | },
9 | "files": [
10 | "src/main.ts"
11 | ],
12 | "include": [
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/ch12/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/spec",
7 | "types": [
8 | "jasmine"
9 | ]
10 | },
11 | "include": [
12 | "src/**/*.spec.ts",
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/ch13/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 | ij_typescript_use_double_quotes = false
14 |
15 | [*.md]
16 | max_line_length = off
17 | trim_trailing_whitespace = false
18 |
--------------------------------------------------------------------------------
/ch13/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/ch13/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/ch13/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch13/public/favicon.ico
--------------------------------------------------------------------------------
/ch13/src/app/app.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch13/src/app/app.component.css
--------------------------------------------------------------------------------
/ch13/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { RouterOutlet } from '@angular/router';
3 |
4 | @Component({
5 | selector: 'app-root',
6 | imports: [RouterOutlet],
7 | templateUrl: './app.component.html',
8 | styleUrl: './app.component.css'
9 | })
10 | export class AppComponent {
11 | title = 'my-app';
12 | }
13 |
--------------------------------------------------------------------------------
/ch13/src/app/app.config.ts:
--------------------------------------------------------------------------------
1 | import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
2 | import { provideRouter } from '@angular/router';
3 |
4 | import { routes } from './app.routes';
5 | import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
6 |
7 | export const appConfig: ApplicationConfig = {
8 | providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideAnimationsAsync()]
9 | };
10 |
--------------------------------------------------------------------------------
/ch13/src/app/app.routes.ts:
--------------------------------------------------------------------------------
1 | import { Routes } from '@angular/router';
2 | import { RoutedComponent } from './routed/routed.component';
3 |
4 | export const routes: Routes = [
5 | { path: 'routed', component: RoutedComponent }
6 | ];
7 |
--------------------------------------------------------------------------------
/ch13/src/app/async.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Observable, of } from 'rxjs';
3 | import { delay } from 'rxjs/operators';
4 |
5 | const items = ['Microphone', 'Keyboard'];
6 |
7 | @Injectable({
8 | providedIn: 'root'
9 | })
10 | export class AsyncService {
11 |
12 | getItems(): Observable {
13 | return of(items).pipe(delay(500));
14 | }
15 |
16 | setItems(name: string) {
17 | return [...items, name ];
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/ch13/src/app/async/async.component.ts:
--------------------------------------------------------------------------------
1 | import { AsyncPipe } from '@angular/common';
2 | import { Component, OnInit } from '@angular/core';
3 | import { Observable } from 'rxjs';
4 | import { AsyncService } from '../async.service';
5 |
6 | @Component({
7 | selector: 'app-async',
8 | imports: [AsyncPipe],
9 | template: `
10 | @for(item of items$ | async; track item) {
11 | {{ item }}
12 | }
13 | `
14 | })
15 | export class AsyncComponent implements OnInit {
16 | items$: Observable | undefined;
17 |
18 | constructor(private asyncService: AsyncService) {}
19 |
20 | ngOnInit(): void {
21 | this.items$ = this.asyncService.getItems();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ch13/src/app/auth.guard.ts:
--------------------------------------------------------------------------------
1 | import { inject } from '@angular/core';
2 | import { CanActivateFn, Router } from '@angular/router';
3 | import { AuthService } from './auth.service';
4 |
5 | export const authGuard: CanActivateFn = () => {
6 | const authService = inject(AuthService);
7 | const router = inject(Router);
8 |
9 | if (authService.isLoggedIn) {
10 | return true;
11 | }
12 | return router.parseUrl('/');
13 | };
14 |
--------------------------------------------------------------------------------
/ch13/src/app/auth.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | @Injectable({
4 | providedIn: 'root'
5 | })
6 | export class AuthService {
7 | isLoggedIn = false;
8 | }
9 |
--------------------------------------------------------------------------------
/ch13/src/app/bindings/bindings.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, input, output } from '@angular/core';
2 | import { MatButton } from '@angular/material/button';
3 |
4 | @Component({
5 | selector: 'app-bindings',
6 | imports: [MatButton],
7 | template: `
8 | {{ title() }}
9 |
10 | `
11 | })
12 | export class BindingsComponent {
13 | title = input('');
14 | liked = output();
15 | }
16 |
--------------------------------------------------------------------------------
/ch13/src/app/copyright.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, ElementRef } from '@angular/core';
2 |
3 | @Directive({
4 | selector: '[appCopyright]'
5 | })
6 | export class CopyrightDirective {
7 |
8 | constructor(el: ElementRef) {
9 | const currentYear = new Date().getFullYear();
10 | const targetEl: HTMLElement = el.nativeElement;
11 | targetEl.classList.add('copyright');
12 | targetEl.textContent = `Copyright ©${currentYear} All Rights Reserved`;
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/ch13/src/app/deps.service.ts:
--------------------------------------------------------------------------------
1 | import { HttpClient } from '@angular/common/http';
2 | import { Injectable } from '@angular/core';
3 |
4 | @Injectable({
5 | providedIn: 'root'
6 | })
7 | export class DepsService {
8 |
9 | constructor(private http: HttpClient) { }
10 |
11 | getItems() {
12 | return this.http.get('http://some.url');
13 | }
14 |
15 | addItem(item: string) {
16 | return this.http.post('http://some.url', { name: item });
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ch13/src/app/items.resolver.ts:
--------------------------------------------------------------------------------
1 | import { ResolveFn } from '@angular/router';
2 | import { AsyncService } from './async.service';
3 | import { inject } from '@angular/core';
4 |
5 | export const itemsResolver: ResolveFn = () => {
6 | const asyncService = inject(AsyncService);
7 | return asyncService.getItems();
8 | };
9 |
--------------------------------------------------------------------------------
/ch13/src/app/list.pipe.spec.ts:
--------------------------------------------------------------------------------
1 | import { ListPipe } from './list.pipe';
2 |
3 | describe('ListPipe', () => {
4 | it('create an instance', () => {
5 | const pipe = new ListPipe();
6 | expect(pipe).toBeTruthy();
7 | });
8 |
9 | it('should return an array', () => {
10 | const pipe = new ListPipe();
11 | expect(pipe.transform('A,B,C')).toEqual(['A', 'B', 'C']);
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/ch13/src/app/list.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 |
3 | @Pipe({
4 | name: 'list'
5 | })
6 | export class ListPipe implements PipeTransform {
7 |
8 | transform(value: string): string[] {
9 | return value.split(',');
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/ch13/src/app/routed/routed.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { Router } from '@angular/router';
3 |
4 | @Component({
5 | selector: 'app-routed',
6 | template: '{{ title }}'
7 | })
8 | export class RoutedComponent {
9 | title = 'My routed component';
10 |
11 | constructor(private router: Router) {}
12 |
13 | goBack() {
14 | this.router.navigate(['/']);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/ch13/src/app/spy/spy.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Title } from '@angular/platform-browser';
3 |
4 | @Component({
5 | selector: 'app-spy',
6 | template: '{{ caption }}'
7 | })
8 | export class SpyComponent implements OnInit {
9 | caption = '';
10 |
11 | constructor(private title: Title) {}
12 |
13 | ngOnInit(): void {
14 | this.title.setTitle('My Angular app');
15 | this.caption = this.title.getTitle();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/ch13/src/app/stub.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { StubService } from './stub.service';
4 |
5 | describe('StubService', () => {
6 | let service: StubService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(StubService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/ch13/src/app/stub.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | @Injectable({
4 | providedIn: 'root'
5 | })
6 | export class StubService {
7 | name = '';
8 | isBusy = false;
9 | }
10 |
--------------------------------------------------------------------------------
/ch13/src/app/stub/stub.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { StubService } from '../stub.service';
3 |
4 | @Component({
5 | selector: 'app-stub',
6 | template: '{{ msg }}'
7 | })
8 | export class StubComponent implements OnInit {
9 | msg = '';
10 |
11 | constructor(private stubService: StubService) {}
12 |
13 | ngOnInit(): void {
14 | this.msg = this.stubService.isBusy
15 | ? this.stubService.name + ' is on mission'
16 | : this.stubService.name + ' is available';
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ch13/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MyApp
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/ch13/src/main.ts:
--------------------------------------------------------------------------------
1 | import { bootstrapApplication } from '@angular/platform-browser';
2 | import { appConfig } from './app/app.config';
3 | import { AppComponent } from './app/app.component';
4 |
5 | bootstrapApplication(AppComponent, appConfig)
6 | .catch((err) => console.error(err));
7 |
--------------------------------------------------------------------------------
/ch13/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
3 | html, body { height: 100%; }
4 | body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
5 |
--------------------------------------------------------------------------------
/ch13/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/app",
7 | "types": []
8 | },
9 | "files": [
10 | "src/main.ts"
11 | ],
12 | "include": [
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/ch13/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/spec",
7 | "types": [
8 | "jasmine"
9 | ]
10 | },
11 | "include": [
12 | "src/**/*.spec.ts",
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/ch14/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 | ij_typescript_use_double_quotes = false
14 |
15 | [*.md]
16 | max_line_length = off
17 | trim_trailing_whitespace = false
18 |
--------------------------------------------------------------------------------
/ch14/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/ch14/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/ch14/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch14/public/favicon.ico
--------------------------------------------------------------------------------
/ch14/src/app/app.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch14/src/app/app.component.css
--------------------------------------------------------------------------------
/ch14/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { RouterOutlet } from '@angular/router';
3 |
4 | @Component({
5 | selector: 'app-root',
6 | imports: [RouterOutlet],
7 | templateUrl: './app.component.html',
8 | styleUrl: './app.component.css'
9 | })
10 | export class AppComponent {
11 | title = 'my-app';
12 | }
13 |
--------------------------------------------------------------------------------
/ch14/src/app/app.config.ts:
--------------------------------------------------------------------------------
1 | import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
2 | import { provideRouter } from '@angular/router';
3 |
4 | import { routes } from './app.routes';
5 |
6 | export const appConfig: ApplicationConfig = {
7 | providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)]
8 | };
9 |
--------------------------------------------------------------------------------
/ch14/src/app/app.routes.ts:
--------------------------------------------------------------------------------
1 | import { Routes } from '@angular/router';
2 |
3 | export const routes: Routes = [];
4 |
--------------------------------------------------------------------------------
/ch14/src/environments/environment.development.ts:
--------------------------------------------------------------------------------
1 | export const environment = {};
2 |
--------------------------------------------------------------------------------
/ch14/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | export const environment = {};
2 |
--------------------------------------------------------------------------------
/ch14/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MyApp
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/ch14/src/main.ts:
--------------------------------------------------------------------------------
1 | import { bootstrapApplication } from '@angular/platform-browser';
2 | import { appConfig } from './app/app.config';
3 | import { AppComponent } from './app/app.component';
4 |
5 | bootstrapApplication(AppComponent, appConfig)
6 | .catch((err) => console.error(err));
7 |
--------------------------------------------------------------------------------
/ch14/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/ch14/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/app",
7 | "types": []
8 | },
9 | "files": [
10 | "src/main.ts"
11 | ],
12 | "include": [
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/ch14/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/spec",
7 | "types": [
8 | "jasmine"
9 | ]
10 | },
11 | "include": [
12 | "src/**/*.spec.ts",
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/ch15/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 | ij_typescript_use_double_quotes = false
14 |
15 | [*.md]
16 | max_line_length = off
17 | trim_trailing_whitespace = false
18 |
--------------------------------------------------------------------------------
/ch15/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/ch15/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/ch15/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch15/public/favicon.ico
--------------------------------------------------------------------------------
/ch15/public/placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch15/public/placeholder.png
--------------------------------------------------------------------------------
/ch15/src/app/app.config.server.ts:
--------------------------------------------------------------------------------
1 | import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
2 | import { provideServerRendering } from '@angular/platform-server';
3 | import { appConfig } from './app.config';
4 |
5 | const serverConfig: ApplicationConfig = {
6 | providers: [
7 | provideServerRendering(),
8 | ]
9 | };
10 |
11 | export const config = mergeApplicationConfig(appConfig, serverConfig);
12 |
--------------------------------------------------------------------------------
/ch15/src/app/app.settings.ts:
--------------------------------------------------------------------------------
1 | import { InjectionToken } from '@angular/core';
2 |
3 | export interface AppSettings {
4 | title: string;
5 | version: string;
6 | apiUrl: string;
7 | }
8 |
9 | export const appSettings: AppSettings = {
10 | title: 'My e-shop',
11 | version: '1.0',
12 | apiUrl: 'https://fakestoreapi.com'
13 | };
14 |
15 | export const APP_SETTINGS = new InjectionToken('app.settings');
16 |
--------------------------------------------------------------------------------
/ch15/src/app/auth.guard.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { CanActivateFn } from '@angular/router';
3 |
4 | import { authGuard } from './auth.guard';
5 |
6 | describe('authGuard', () => {
7 | const executeGuard: CanActivateFn = (...guardParameters) =>
8 | TestBed.runInInjectionContext(() => authGuard(...guardParameters));
9 |
10 | beforeEach(() => {
11 | TestBed.configureTestingModule({});
12 | });
13 |
14 | it('should be created', () => {
15 | expect(executeGuard).toBeTruthy();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/ch15/src/app/auth.guard.ts:
--------------------------------------------------------------------------------
1 | import { inject } from '@angular/core';
2 | import { CanActivateFn, CanMatchFn, Router } from '@angular/router';
3 | import { AuthService } from './auth.service';
4 |
5 | export const authGuard: CanActivateFn | CanMatchFn = () => {
6 | const authService = inject(AuthService);
7 | const router = inject(Router);
8 |
9 | if (authService.isLoggedIn()) {
10 | return true;
11 | }
12 | return router.parseUrl('/');
13 | };
14 |
--------------------------------------------------------------------------------
/ch15/src/app/auth.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { AuthService } from './auth.service';
4 |
5 | describe('AuthService', () => {
6 | let service: AuthService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(AuthService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/ch15/src/app/auth/auth.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch15/src/app/auth/auth.component.css
--------------------------------------------------------------------------------
/ch15/src/app/auth/auth.component.html:
--------------------------------------------------------------------------------
1 | @if (!authService.isLoggedIn()) {
2 |
5 | } @else {
6 |
9 | }
10 |
--------------------------------------------------------------------------------
/ch15/src/app/auth/auth.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AuthComponent } from './auth.component';
4 |
5 | describe('AuthComponent', () => {
6 | let component: AuthComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | imports: [AuthComponent]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(AuthComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/ch15/src/app/auth/auth.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { AuthService } from '../auth.service';
3 | import { MatButton } from '@angular/material/button';
4 |
5 | @Component({
6 | selector: 'app-auth',
7 | imports: [MatButton],
8 | templateUrl: './auth.component.html',
9 | styleUrl: './auth.component.css'
10 | })
11 | export class AuthComponent {
12 | constructor(public authService: AuthService) {}
13 |
14 | login() {
15 | this.authService.login('david_r', '3478*#54').subscribe();
16 | }
17 |
18 | logout() {
19 | this.authService.logout();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ch15/src/app/cart.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { CartService } from './cart.service';
4 |
5 | describe('CartService', () => {
6 | let service: CartService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(CartService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/ch15/src/app/cart.ts:
--------------------------------------------------------------------------------
1 | export interface Cart {
2 | id: number;
3 | products: { productId :number }[];
4 | }
5 |
--------------------------------------------------------------------------------
/ch15/src/app/cart/cart.component.css:
--------------------------------------------------------------------------------
1 | :host {
2 | width: 500px;
3 | }
4 |
--------------------------------------------------------------------------------
/ch15/src/app/cart/cart.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | @for(product of cartForm.controls.products.controls; track $index) {
4 |
5 | {{products[$index].title}}
6 |
11 |
12 | }
13 |
14 |
15 |
--------------------------------------------------------------------------------
/ch15/src/app/cart/cart.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { CartComponent } from './cart.component';
4 |
5 | describe('CartComponent', () => {
6 | let component: CartComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | imports: [CartComponent]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(CartComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/ch15/src/app/checkout.guard.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { CanDeactivateFn } from '@angular/router';
3 |
4 | import { checkoutGuard } from './checkout.guard';
5 |
6 | describe('checkoutGuard', () => {
7 | const executeGuard: CanDeactivateFn = (...guardParameters) =>
8 | TestBed.runInInjectionContext(() => checkoutGuard(...guardParameters));
9 |
10 | beforeEach(() => {
11 | TestBed.configureTestingModule({});
12 | });
13 |
14 | it('should be created', () => {
15 | expect(executeGuard).toBeTruthy();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/ch15/src/app/checkout/checkout.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch15/src/app/checkout/checkout.component.css
--------------------------------------------------------------------------------
/ch15/src/app/checkout/checkout.component.html:
--------------------------------------------------------------------------------
1 | Cart Checkout
2 |
3 |
4 | You have {{ data }} pending items in your cart.
5 | Do you want to continue?
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/ch15/src/app/checkout/checkout.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { CheckoutComponent } from './checkout.component';
4 |
5 | describe('CheckoutComponent', () => {
6 | let component: CheckoutComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | imports: [CheckoutComponent]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(CheckoutComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/ch15/src/app/checkout/checkout.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, inject } from '@angular/core';
2 | import { MatButton } from '@angular/material/button';
3 | import { MatDialogModule, MAT_DIALOG_DATA } from '@angular/material/dialog';
4 |
5 | @Component({
6 | selector: 'app-checkout',
7 | imports: [MatButton, MatDialogModule],
8 | templateUrl: './checkout.component.html',
9 | styleUrl: './checkout.component.css'
10 | })
11 | export class CheckoutComponent {
12 | data = inject(MAT_DIALOG_DATA);
13 | }
14 |
--------------------------------------------------------------------------------
/ch15/src/app/copyright.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { CopyrightDirective } from './copyright.directive';
2 |
3 | describe('CopyrightDirective', () => {
4 | it('should create an instance', () => {
5 | const directive = new CopyrightDirective();
6 | expect(directive).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/ch15/src/app/featured/featured.component.css:
--------------------------------------------------------------------------------
1 | mat-card {
2 | max-width: 350px;
3 | }
4 |
5 | button {
6 | width: 100%;
7 | }
8 |
--------------------------------------------------------------------------------
/ch15/src/app/featured/featured.component.html:
--------------------------------------------------------------------------------
1 | @if (product$ | async; as product) {
2 |
3 |
4 | MEGA DEAL
5 | {{ product.title }}
6 |
7 |
8 |
9 |
10 |
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/ch15/src/app/featured/featured.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { FeaturedComponent } from './featured.component';
4 |
5 | describe('FeaturedComponent', () => {
6 | let component: FeaturedComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | imports: [FeaturedComponent]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(FeaturedComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/ch15/src/app/price-maximum.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { PriceMaximumDirective } from './price-maximum.directive';
2 |
3 | describe('PriceMaximumDirective', () => {
4 | it('should create an instance', () => {
5 | const directive = new PriceMaximumDirective();
6 | expect(directive).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/ch15/src/app/price-maximum.validator.ts:
--------------------------------------------------------------------------------
1 | import { ValidatorFn, AbstractControl, ValidationErrors } from '@angular/forms';
2 |
3 | export function priceMaximumValidator(price: number): ValidatorFn {
4 | return (control: AbstractControl): ValidationErrors | null => {
5 | const isMax = control.value <= price;
6 | return isMax ? null : { priceMaximum: true };
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/ch15/src/app/product-create/product-create.component.css:
--------------------------------------------------------------------------------
1 | :host {
2 | width: 400px;
3 | }
4 |
5 | select {
6 | border-radius: 4px;
7 | padding: 8px;
8 | margin-bottom: 16px;
9 | border: 1px solid #BDBDBD;
10 | width: 220px;
11 | }
12 |
--------------------------------------------------------------------------------
/ch15/src/app/product.ts:
--------------------------------------------------------------------------------
1 | export interface Product {
2 | id: number;
3 | title: string;
4 | price: number;
5 | category: string;
6 | image: string;
7 | }
8 |
--------------------------------------------------------------------------------
/ch15/src/app/products.resolver.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { ResolveFn } from '@angular/router';
3 |
4 | import { productsResolver } from './products.resolver';
5 |
6 | describe('productsResolver', () => {
7 | const executeResolver: ResolveFn = (...resolverParameters) =>
8 | TestBed.runInInjectionContext(() => productsResolver(...resolverParameters));
9 |
10 | beforeEach(() => {
11 | TestBed.configureTestingModule({});
12 | });
13 |
14 | it('should be created', () => {
15 | expect(executeResolver).toBeTruthy();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/ch15/src/app/products.resolver.ts:
--------------------------------------------------------------------------------
1 | import { inject } from '@angular/core';
2 | import { ResolveFn } from '@angular/router';
3 | import { Product } from './product';
4 | import { ProductsService } from './products.service';
5 |
6 | export const productsResolver: ResolveFn = (route, state) => {
7 | const productService = inject(ProductsService);
8 | const limit = Number(route.queryParamMap.get('limit'));
9 | return productService.getProducts(limit);
10 | };
11 |
--------------------------------------------------------------------------------
/ch15/src/app/products.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { ProductsService } from './products.service';
4 |
5 | describe('ProductsService', () => {
6 | let service: ProductsService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(ProductsService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/ch15/src/app/sort.pipe.spec.ts:
--------------------------------------------------------------------------------
1 | import { SortPipe } from './sort.pipe';
2 |
3 | describe('SortPipe', () => {
4 | it('create an instance', () => {
5 | const pipe = new SortPipe();
6 | expect(pipe).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/ch15/src/app/sort.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 | import { Product } from './product';
3 |
4 | @Pipe({
5 | name: 'sort'
6 | })
7 | export class SortPipe implements PipeTransform {
8 |
9 | transform(value: Product[], args: keyof Product = 'title'): Product[] {
10 | if (value) {
11 | return value.sort((a: Product, b: Product) => {
12 | if (a[args] < b[args]) {
13 | return -1;
14 | } else if (b[args] < a[args]) {
15 | return 1;
16 | }
17 | return 0;
18 | });
19 | }
20 | return [];
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/ch15/src/app/user.routes.ts:
--------------------------------------------------------------------------------
1 | import { UserComponent } from './user/user.component';
2 |
3 | export default [
4 | { path: '', component: UserComponent }
5 | ];
6 |
--------------------------------------------------------------------------------
/ch15/src/app/user/user.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PacktPublishing/Learning-Angular-Fifth-Edition/127a749552d91238e1a5b4a3b66bf006ae97e636/ch15/src/app/user/user.component.css
--------------------------------------------------------------------------------
/ch15/src/app/user/user.component.html:
--------------------------------------------------------------------------------
1 | user works!
2 |
--------------------------------------------------------------------------------
/ch15/src/app/user/user.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { UserComponent } from './user.component';
4 |
5 | describe('UserComponent', () => {
6 | let component: UserComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | imports: [UserComponent]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(UserComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/ch15/src/app/user/user.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-user',
5 | imports: [],
6 | templateUrl: './user.component.html',
7 | styleUrl: './user.component.css'
8 | })
9 | export class UserComponent {
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/ch15/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MyApp
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/ch15/src/main.server.ts:
--------------------------------------------------------------------------------
1 | import { bootstrapApplication } from '@angular/platform-browser';
2 | import { AppComponent } from './app/app.component';
3 | import { config } from './app/app.config.server';
4 |
5 | const bootstrap = () => bootstrapApplication(AppComponent, config);
6 |
7 | export default bootstrap;
8 |
--------------------------------------------------------------------------------
/ch15/src/main.ts:
--------------------------------------------------------------------------------
1 | import { bootstrapApplication } from '@angular/platform-browser';
2 | import { appConfig } from './app/app.config';
3 | import { AppComponent } from './app/app.component';
4 |
5 | bootstrapApplication(AppComponent, appConfig)
6 | .catch((err) => console.error(err));
7 |
--------------------------------------------------------------------------------
/ch15/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/app",
7 | "types": [
8 | "node"
9 | ]
10 | },
11 | "files": [
12 | "src/main.ts",
13 | "src/main.server.ts",
14 | "src/server.ts"
15 | ],
16 | "include": [
17 | "src/**/*.d.ts"
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/ch15/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/spec",
7 | "types": [
8 | "jasmine"
9 | ]
10 | },
11 | "include": [
12 | "src/**/*.spec.ts",
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------