├── 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 |
3 | 4 |
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 | 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 |
3 | 4 |
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 |
3 | 4 |
5 |
6 |
- v{{ settings.version }}
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 |
{{ title }}
2 |
3 |
4 | 5 |
6 |
7 |
- v{{ settings.version }}
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 |
{{ title() }}
2 |
3 |
4 | 5 |
6 |
7 |
- v{{ settings.version }}
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 |
7 | 8 |
9 |
10 |
- v{{ settings.version }}
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 |
- v{{ settings.version }}
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 |
- v{{ settings.version }}
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 |
- v{{ settings.version }}
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 | --------------------------------------------------------------------------------