├── .nvmrc ├── snippets ├── pages │ ├── new.md │ ├── index.md │ ├── json.md │ ├── random.md │ ├── readme.md │ └── slides.md ├── svg-templates.md ├── cheat-sheets-and-checklists.md ├── router-module.md ├── reusing-existing-custom-pipes.md ├── renaming-inputs-and-outputs.md ├── accessing-enums-in-template.md ├── understanding-microsyntax.md ├── component-state-debugging.md ├── ngif-else.md ├── injecting-document.md ├── default-viewencapsulation-value.md ├── preseving-whitespaces.md ├── reusing-code-in-template.md ├── using-app_initializer-to-delay-app-start.md ├── adding-keyboard-shortcuts-to-elements.md ├── global-event-listeners.md ├── safe-navigation-operator.md ├── observables-as-outputs.md ├── style-bindings.md ├── bind-to-host-properties-with-host-binding.md ├── two-way-binding-any-property.md ├── trackby-in-for-loops.md ├── window-location-injection.md ├── ng-content.md ├── passing-template-as-an-input.md ├── loader-component.md ├── access-dom-element.md ├── optional-parameters-in-the-middle.md ├── component-level-providers.md ├── mark-reactive-fields-as-touched.md ├── accessing-all-nested-form-controls.md ├── getting-components-of-different-types-with-viewchild.md ├── svg.md ├── router-custom-preloading.md └── hammerjs-gestures.md ├── config ├── static │ └── style.css └── 30s.config.json ├── typesrcipt ├── snippets │ ├── pages │ │ ├── index.md │ │ ├── new.md │ │ ├── json.md │ │ ├── random.md │ │ ├── readme.md │ │ └── slides.md │ └── accessing-enums-in-template.md ├── config │ ├── static │ │ └── style.css │ └── 30s.config.json ├── package.json └── package-lock.json ├── .editorconfig ├── DEVELOPMENT.md ├── .travis.yml ├── .firebaserc ├── firebase.json ├── .gitignore ├── package.json ├── snippet-template.md ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md ├── COLLABORATING.md ├── LICENSE └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | v10 2 | -------------------------------------------------------------------------------- /snippets/pages/new.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: new.hbs 3 | --- 4 | -------------------------------------------------------------------------------- /snippets/pages/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: index.hbs 3 | --- 4 | -------------------------------------------------------------------------------- /snippets/pages/json.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: json.hbs 3 | --- 4 | 5 | -------------------------------------------------------------------------------- /snippets/pages/random.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: random.hbs 3 | --- 4 | -------------------------------------------------------------------------------- /snippets/pages/readme.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: readme.hbs 3 | --- 4 | -------------------------------------------------------------------------------- /snippets/pages/slides.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: slides.hbs 3 | --- 4 | -------------------------------------------------------------------------------- /config/static/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #fff; 3 | } 4 | 5 | -------------------------------------------------------------------------------- /typesrcipt/snippets/pages/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: index.hbs 3 | --- 4 | -------------------------------------------------------------------------------- /typesrcipt/snippets/pages/new.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: new.hbs 3 | --- 4 | -------------------------------------------------------------------------------- /typesrcipt/snippets/pages/json.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: json.hbs 3 | --- 4 | 5 | -------------------------------------------------------------------------------- /typesrcipt/snippets/pages/random.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: random.hbs 3 | --- 4 | -------------------------------------------------------------------------------- /typesrcipt/snippets/pages/readme.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: readme.hbs 3 | --- 4 | -------------------------------------------------------------------------------- /typesrcipt/snippets/pages/slides.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: slides.hbs 3 | --- 4 | -------------------------------------------------------------------------------- /typesrcipt/config/static/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #12678e; 3 | } 4 | 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | insert_final_newline=true 3 | indent_style=space 4 | indent_size=2 5 | 6 | [{*.html, *.hbs}] 7 | indent_size=4 8 | 9 | -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | # Development 2 | 3 | ## Builder debug 4 | 5 | ```sh 6 | node --inspect-brk ./node_modules/.bin/builder-30-seconds serve 7 | ``` 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "stable" 4 | cache: 5 | directories: 6 | - "node_modules" 7 | before_script: 8 | - npm i 9 | script: 10 | - npm run build 11 | - npm t 12 | -------------------------------------------------------------------------------- /snippets/svg-templates.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: SVG templates 3 | author: kirjs 4 | twitter: kirjs 5 | level: intermediate 6 | tags: 7 | - tip 8 | - svg 9 | --- 10 | 11 | # Content 12 | It is possible to use `.svg` file as a component template: 13 | 14 | ```typescript 15 | @Component({ 16 | templateUrl: 'app.svg' 17 | }) 18 | ``` 19 | -------------------------------------------------------------------------------- /typesrcipt/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typesrcipt", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "builder-30-seconds build", 8 | "start": "builder-30-seconds serve" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "builder-30-seconds": "file:../builder" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "angular-presentation" 4 | }, 5 | "targets": { 6 | "angular-presentation": { 7 | "hosting": { 8 | "angular-30-seconds": [ 9 | "angular-30-seconds" 10 | ], 11 | "angular-30-seconds-staging": [ 12 | "angular-30-seconds-staging" 13 | ] 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /config/30s.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "30 Seconds of Angular", 3 | "snippets": "./snippets", 4 | "static": "./config/static", 5 | "metadata": { 6 | "base": "https://30.codelab.fun/", 7 | "codeRunnerHost": "https://codelab.fun", 8 | "title": "30 Seconds of Angular", 9 | "twitter": "30sec_angular", 10 | "github": "30-seconds/30-seconds-of-angular" 11 | }, 12 | "serveMetadata": { 13 | "isDevMode": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /typesrcipt/config/30s.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "30 Seconds of TypeScript", 3 | "snippets": "./snippets", 4 | "static": "./config/static", 5 | "metadata": { 6 | "base": "https://30.codelab.fun/", 7 | "codeRunnerHost": "https://codelab.fun", 8 | "title": "30 Seconds of TypeScript", 9 | "twitter": "30sec_angular", 10 | "github": "nycJSorg/30-seconds-of-angular" 11 | }, 12 | "serveMetadata": { 13 | "isDevMode": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /snippets/cheat-sheets-and-checklists.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Cheat Sheets and Checklists 3 | author: kirjs 4 | twitter: kirjs 5 | level: beginner 6 | tags: 7 | - tip 8 | - cheat sheet 9 | 10 | links: 11 | - https://malcoded.com/angular-cheat-sheet/ 12 | - https://angular.io/guide/cheatsheet 13 | - https://angular.io/guide/styleguide 14 | --- 15 | 16 | # Content 17 | Check out [Angular Cheat Sheet](https://angular.io/guide/cheatsheet) or ([alternative version](https://malcoded.com/angular-cheat-sheet)) containing lots of useful information condensed in one place. 18 | 19 | Also [Angular Checklist](https://angular-checklist.io) contains is curated list of common mistakes made when developing Angular applications. 20 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": [ 3 | { 4 | "site": "angular-30-seconds", 5 | "public": "static", 6 | "ignore": [ 7 | "firebase.json", 8 | "**/.*", 9 | "**/node_modules/**" 10 | ], 11 | "rewrites": [ 12 | { 13 | "source": "**", 14 | "destination": "/index.html" 15 | } 16 | ] 17 | }, 18 | { 19 | "site": "angular-30-seconds-staging", 20 | "public": "static", 21 | "ignore": [ 22 | "firebase.json", 23 | "**/.*", 24 | "**/node_modules/**" 25 | ], 26 | "rewrites": [ 27 | { 28 | "source": "**", 29 | "destination": "/index.html" 30 | } 31 | ] 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /snippets/router-module.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Router module 3 | author: alQlagin 4 | twitter: alQlagin 5 | level: beginner 6 | tags: 7 | - routing 8 | 9 | links: 10 | - https://angular.io/guide/router#refactor-the-routing-configuration-into-a-routing-module 11 | --- 12 | 13 | # Content 14 | When having a separate routing module, instead of creating an NgModule you can export router configuration directly: 15 | 16 | ```typescript 17 | export const AppRoutingModule = RouterModule.forRoot(routes, config); 18 | ``` 19 | or 20 | 21 | ```typescript 22 | export const FeatureRoutingModule = RouterModule.forChild(routes); 23 | ``` 24 | 25 | and use it in your module 26 | 27 | ```typescript 28 | @NgModule({ 29 | imports: [ 30 | ... 31 | AppRoutingModule, 32 | ... 33 | ] 34 | }) 35 | ``` 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | # See http://help.github.com/ignore-files/ for more about ignoring files. 3 | 4 | # compiled output 5 | /dist 6 | /tmp 7 | /out-tsc 8 | 9 | # dependencies 10 | node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # IDE - VSCode 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | 28 | # misc 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | testem.log 35 | /typings 36 | 37 | # e2e 38 | /e2e/*.js 39 | /e2e/*.map 40 | 41 | # System Files 42 | .DS_Store 43 | Thumbs.db 44 | .firebase 45 | 46 | /static 47 | -------------------------------------------------------------------------------- /snippets/reusing-existing-custom-pipes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Reusing existing custom pipes 3 | author: kirjs 4 | twitter: kirjs 5 | level: intermediate 6 | 7 | tags: 8 | - tip 9 | - pipes 10 | - library 11 | 12 | links: 13 | - https://github.com/danrevah/ngx-pipes 14 | --- 15 | 16 | # Content 17 | If you need a custom `pipe`, before creating one, consider checking out the [NGX Pipes package](https://github.com/danrevah/ngx-pipes) which has 70+ already implemeted custom pipes. 18 | 19 | Here are some examples: 20 | 21 | ```html 22 |

{{ date | timeAgo }}

23 | 24 | 25 |

{{ 'foo bar' | ucfirst }}

26 | 27 | 28 |

3 {{ 'Painting' | makePluralString: 3 }}

29 | 30 | 31 |

{{ [1, 2, 3, 1, 2, 3] | max }}

32 | 33 | ``` 34 | 35 | -------------------------------------------------------------------------------- /snippets/renaming-inputs-and-outputs.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Renaming inputs and outputs 3 | author: alQlagin 4 | level: beginner 5 | tags: 6 | - components 7 | - templates 8 | links: 9 | - https://angular.io/guide/styleguide#style-05-13 10 | --- 11 | 12 | # Content 13 | In certain cases `@Input` and `@Output` properties can be named differently than the actual inputs and outputs. 14 | 15 | ```html 16 |
20 |
21 | ``` 22 | 23 | ```typescript 24 | @Directive({ selector: '[pagination]'}) 25 | class PaginationComponent { 26 | @Input('paginationShowFirst') 27 | showFirst: boolean = true; 28 | 29 | @Output('paginationPageChanged') 30 | pageChanged = new EventEmitter(); 31 | } 32 | ``` 33 | > Note: Use this wisely, see [StyleGuide recommedation](https://angular.io/guide/styleguide#style-05-13) 34 | -------------------------------------------------------------------------------- /snippets/accessing-enums-in-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Accessing Enums in template 3 | author: fetis 4 | twitter: fetis26 5 | level: beginner 6 | tags: 7 | - enums 8 | - templates 9 | --- 10 | # Content 11 | Enums are great but they are not visible in Angular templates by default. 12 | With this little trick you can make them accessible. 13 | 14 | ```typescript 15 | enum Animals { 16 | DOG, 17 | CAT, 18 | DOLPHIN 19 | } 20 | 21 | @Component({ 22 | ... 23 | }) 24 | export class AppComponent { 25 | animalsEnum: typeof Animals = Animals; 26 | } 27 | ``` 28 | 29 | # file:app.component.ts 30 | ```typescript 31 | import { Component } from "@angular/core"; 32 | 33 | enum Animals { 34 | DOG, 35 | CAT, 36 | DOLPHIN 37 | } 38 | 39 | @Component({ 40 | selector: "my-app", 41 | template: `
meow
`, 42 | }) 43 | export class AppComponent { 44 | value: Animals = Animals.CAT; 45 | animalsEnum: typeof Animals = Animals; 46 | } 47 | ``` 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "30-seconds-of-angular", 3 | "version": "1.0.1", 4 | "description": "", 5 | "scripts": { 6 | "generate": "echo 'I am deprecated, use `npm run build` insted' && node builder/build", 7 | "build": "builder-30-seconds build", 8 | "start": "builder-30-seconds serve", 9 | "deploy": "npm run generate && firebase deploy --only hosting:angular-30-seconds", 10 | "deploy:staging": "npm run generate && firebase deploy --only hosting:angular-30-seconds-staging" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/nycJSorg/30-seconds-of-angular.git" 15 | }, 16 | "keywords": [], 17 | "author": "", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/nycJSorg/30-seconds-of-angular/issues" 21 | }, 22 | "homepage": "https://github.com/nycJSorg/30-seconds-of-angular#readme", 23 | "devDependencies": {}, 24 | "dependencies": { 25 | "builder-30-seconds": "file:./builder" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /typesrcipt/snippets/accessing-enums-in-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Accessing Enums in template 3 | author: fetis 4 | twitter: fetis26 5 | level: beginner 6 | tags: 7 | - enums 8 | - templates 9 | --- 10 | # Content 11 | Enums are great but they are not visible in Angular templates by default. 12 | With this little trick you can make them accessible. 13 | 14 | ```typescript 15 | enum Animals { 16 | DOG, 17 | CAT, 18 | DOLPHIN 19 | } 20 | 21 | @Component({ 22 | ... 23 | }) 24 | export class AppComponent { 25 | animalsEnum: typeof Animals = Animals; 26 | } 27 | ``` 28 | 29 | # file:app.component.ts 30 | ```typescript 31 | import { Component } from "@angular/core"; 32 | 33 | enum Animals { 34 | DOG, 35 | CAT, 36 | DOLPHIN 37 | } 38 | 39 | @Component({ 40 | selector: "my-app", 41 | template: `
meow
`, 42 | }) 43 | export class AppComponent { 44 | value: Animals = Animals.CAT; 45 | animalsEnum: typeof Animals = Animals; 46 | } 47 | ``` 48 | -------------------------------------------------------------------------------- /snippets/understanding-microsyntax.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Understanding Microsyntax 3 | author: kirjs 4 | twitter: kirjs 5 | level: beginner 6 | tags: 7 | - tip 8 | - structural directive 9 | - microsyntax 10 | 11 | links: 12 | - https://angular.io/guide/structural-directives#microsyntax 13 | - https://alexzuza.github.io/ng-structural-directive-expander/ 14 | - https://angular.io/guide/structural-directives#inside-ngfor 15 | --- 16 | 17 | # Content 18 | Under the hood Angular compiles structural directives into ng-template elements, e.g.: 19 | 20 | ```html 21 | 22 |
23 | 24 | 25 | 26 | ``` 27 | 28 | The value passed to *ngFor directive is written using microsyntax. You can learn about it [in the docs](https://angular.io/guide/structural-directives#microsyntax). 29 | 30 | Also check out an [interactive tool](https://alexzuza.github.io/ng-structural-directive-expander/) that shows the expansion by [Alexey Zuev](https://twitter.com/yurzui) 31 | -------------------------------------------------------------------------------- /snippets/component-state-debugging.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Component State Debugging 3 | author: lironHazan 4 | twitter: lironn_h 5 | level: beginner 6 | tags: 7 | - good-to-know 8 | - tips 9 | links: 10 | - https://blog.angularindepth.com/everything-you-need-to-know-about-debugging-angular-applications-d308ed8a51b4 11 | --- 12 | # Content 13 | 14 | Debug the component state in the browser console by running: 15 | ```typescript 16 | ng.probe($0).componentInstance 17 | ``` 18 | 19 | > `$0` - is the DOM node currently selected in dev tools (`$1` for the previous one and so on). 20 | 21 | # Bonus 22 | 23 | With Ivy renderer engine: 24 | ```typescript 25 | ng.getComponent($0) 26 | ``` 27 | 28 | # file:app.component.ts 29 | ```typescript 30 | import { Component } from '@angular/core'; 31 | 32 | 33 | @Component({ 34 | selector: 'my-app', 35 | template: `

1. Open browser dev tools

36 |

2. Make sure I'm selected in the elements panel

37 |

3. type ng.probe($0).componentInstance in the console to get access to the component instance.

38 | ` 39 | }) 40 | export class AppComponent { 41 | secretProperty = '42'; 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /snippets/ngif-else.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ngIf else 3 | author: fetis 4 | twitter: fetis26 5 | level: beginner 6 | tags: 7 | - ngif 8 | - templates 9 | --- 10 | # Content 11 | `*ngIf` directive also supports `else` statement. 12 | 13 | ```html 14 |
loading...
15 | 16 | not loading 17 | ``` 18 | 19 | # file:app.component.ts 20 | ```typescript 21 | import { Component, HostBinding } from '@angular/core'; 22 | 23 | @Component({ 24 | selector: 'my-app', 25 | template: ` 26 |
loading...
27 | 28 | not loading 29 | `, 30 | }, 31 | 32 | ) 33 | export class AppComponent { 34 | isLoading = false 35 | } 36 | ``` 37 | 38 | 39 | # file:app.module.ts 40 | ```typescript 41 | import { BrowserModule } from '@angular/platform-browser'; 42 | import { NgModule } from '@angular/core'; 43 | import { AppComponent } from './app.component'; 44 | import { FormsModule } from '@angular/forms'; 45 | 46 | @NgModule({ 47 | imports: [BrowserModule, FormsModule], 48 | declarations: [AppComponent], 49 | bootstrap: [AppComponent] 50 | }) 51 | export class AppModule {} 52 | ``` 53 | -------------------------------------------------------------------------------- /typesrcipt/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typesrcipt", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "builder-30-seconds": { 8 | "version": "file:../builder", 9 | "requires": { 10 | "helper-markdown": "^1.0.0", 11 | "highlight.js": "^9.15.6", 12 | "jest": "^24.8.0", 13 | "jstransformer-handlebars": "^1.1.0", 14 | "md-to-json-snippet-generator": "0.0.9", 15 | "merge-dirs": "^0.2.1", 16 | "metalsmith": "^2.3.0", 17 | "metalsmith-collections": "^0.9.0", 18 | "metalsmith-copy": "^0.4.1", 19 | "metalsmith-discover-partials": "^0.1.2", 20 | "metalsmith-layouts": "^2.3.1", 21 | "metalsmith-markdown": "^1.2.0", 22 | "metalsmith-permalinks": "^2.2.0", 23 | "metalsmith-register-helpers": "^0.4.0", 24 | "metalsmith-rename": "^1.0.0", 25 | "metalsmith-serve": "0.0.7", 26 | "metalsmith-static": "0.0.5", 27 | "metalsmith-tags": "^2.0.0", 28 | "metalsmith-validate": "^0.1.4", 29 | "metalsmith-watch": "^1.0.3", 30 | "puppeteer": "^1.15.0", 31 | "yargs": "^13.2.4" 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /snippet-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Feature Description 3 | author: Your name or github handle. 4 | level: beginner|intermediate|advanced 5 | tags: 6 | - tips 7 | - good-to-know 8 | links: 9 | - http://additional.context 10 | --- 11 | # Content 12 | 13 | 14 | # Bonus 15 | 16 | 17 | 18 | 25 | 26 | # file:app.component.ts 27 | ```typescript 28 | import { Component } from '@angular/core'; 29 | 30 | @Component({ 31 | selector: 'my-app', 32 | template: `

Update me!

` 33 | }) 34 | export class AppComponent {} 35 | ```` 36 | 37 | # file:app.module.ts 38 | ```typescript 39 | // This section is optional, remove it if the code below is good. 40 | import { BrowserModule } from '@angular/platform-browser'; 41 | import { NgModule } from '@angular/core'; 42 | import { AppComponent } from './app.component'; 43 | 44 | @NgModule({ 45 | imports: [BrowserModule], 46 | declarations: [AppComponent], 47 | bootstrap: [AppComponent] 48 | }) 49 | export class AppModule {} 50 | ``` 51 | -------------------------------------------------------------------------------- /snippets/injecting-document.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Injecting document 3 | author: kirjs 4 | twitter: kirjs 5 | level: intermediate 6 | tags: 7 | - dependency injection 8 | links: 9 | - https://angular.io/api/common/DOCUMENT 10 | --- 11 | # Content 12 | Sometimes you need to get access to global `document`. 13 | 14 | To simplify unit-testing, Angular provides it through dependency injection: 15 | 16 | ```typescript 17 | import { DOCUMENT } from '@angular/common'; 18 | import { Inject } from '@angular/core'; 19 | 20 | @Component({ 21 | selector: 'my-app', 22 | template: `

Edit me

` 23 | }) 24 | export class AppComponent { 25 | 26 | constructor(@Inject(DOCUMENT) private document: Document) { 27 | // Word with document.location, or other things here.... 28 | } 29 | } 30 | ``` 31 | 32 | # file:app.component.ts 33 | ```typescript 34 | 35 | import { Component } from '@angular/core'; 36 | import { DOCUMENT } from '@angular/common'; 37 | import { Inject } from '@angular/core'; 38 | 39 | @Component({ 40 | selector: 'my-app', 41 | template: `

Edit me

` 42 | }) 43 | export class AppComponent { 44 | constructor(@Inject(DOCUMENT) private document: Document) { 45 | // Don't do this in prod! 46 | document.body.style.backgroundColor = 'pink'; 47 | } 48 | } 49 | ``` 50 | -------------------------------------------------------------------------------- /snippets/default-viewencapsulation-value.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Default ViewEncapsulation value 3 | author: kirjs 4 | twitter: kirjs 5 | level: beginner 6 | tags: 7 | - configuration 8 | - styling 9 | --- 10 | # Content 11 | If you're using `ViewEncapsulation` value which is different than default, it might be daunting to set the value manually for every component. 12 | 13 | Luckily you can configure it globally when bootstrapping your app: 14 | 15 | ```TypeScript 16 | platformBrowserDynamic().bootstrapModule(AppModule, [ 17 | { 18 | // NOTE: Use ViewEncapsulation.None only if you know what you're doing. 19 | defaultEncapsulation: ViewEncapsulation.None 20 | } 21 | ]); 22 | ``` 23 | 24 | # file:app.component.ts 25 | ```typescript 26 | import { Component } from '@angular/core'; 27 | 28 | @Component({ 29 | selector: 'my-app', 30 | template: `

I am red

`, 31 | styles: [` body {background: red}; `] 32 | }) 33 | export class AppComponent {} 34 | ``` 35 | 36 | # file:main.ts 37 | ```typescript 38 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 39 | import { AppModule } from './app.module'; 40 | import { ViewEncapsulation } from '@angular/core' 41 | 42 | platformBrowserDynamic().bootstrapModule(AppModule, [ 43 | { 44 | defaultEncapsulation: ViewEncapsulation.None 45 | } 46 | ]); 47 | ``` 48 | -------------------------------------------------------------------------------- /snippets/preseving-whitespaces.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Preseving whitespaces 3 | author: NothingEverHappens 4 | level: intermediate 5 | tags: 6 | - tip 7 | 8 | links: 9 | - https://twitter.com/mgechev/status/1108913389277839360 10 | --- 11 | 12 | # Content 13 | By default Angular strips all whitespaces in templates to save bytes. Generally it's safe. 14 | 15 | For rare cases when you need to preserve spaces you can use special `ngPreserveWhitespaces` attribute: 16 | 17 | ```html 18 |
19 | (___()'`; 20 | /, /` 21 | jgs \\"--\\ 22 |
23 | ``` 24 | 25 | > You can also use [preserveWhitespaces](https://angular.io/api/core/Component#preserveWhitespaces) option on a component. 26 | 27 | # file:app.component.ts 28 | ```typescript 29 | import { Component } from '@angular/core'; 30 | 31 | @Component({ 32 | preserveWhitespaces: false, 33 | selector: 'my-app', 34 | template: ` 35 | 36 |

Look at mee in dev tools! :(

37 |
38 | __ 39 | (___()''; 40 | /, / 41 | jgs \\"--\\ 42 |
43 | 44 |

Now look at me, I'm cute!!

45 |
46 | __ 47 | (___()''; 48 | /, / 49 | jgs \\"--\\ 50 |
51 | 52 | 53 | 54 | ` 55 | }) 56 | export class AppComponent {} 57 | ``` 58 | -------------------------------------------------------------------------------- /snippets/reusing-code-in-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Reusing code in template 3 | author: fetis 4 | twitter: fetis26 5 | level: intermediate 6 | tags: 7 | - templates 8 | links: 9 | - https://angular.io/api/common/NgTemplateOutlet 10 | - https://angular.io/guide/structural-directives#the-ng-template 11 | --- 12 | 13 | # Content 14 | While the best way of reusing your code is creating a component, it's also possible to do it in a template. 15 | 16 | To do this you can use `ng-template` along with `*ngTemplateOutlet` directive. 17 | 18 | ```html 19 |

20 | 21 |

22 | 23 | 26 | 27 | 28 | Hello {{name}}! 29 | 30 | ``` 31 | 32 | # file:app.component.ts 33 | ```typescript 34 | import { Component } from '@angular/core'; 35 | 36 | @Component({ 37 | selector: 'my-app', 38 | template: ` 39 |

40 | 41 |

42 | 43 | 46 | 47 | 48 | Hello {{name}}! 49 | 50 | `, 51 | }) 52 | export class AppComponent { 53 | name = 'Angular'; 54 | } 55 | ``` 56 | -------------------------------------------------------------------------------- /snippets/using-app_initializer-to-delay-app-start.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Using APP_INITIALIZER to delay app start 3 | author: NothingEverHappens 4 | twitter: kirjs 5 | level: intermediate 6 | tags: 7 | - tip 8 | 9 | links: 10 | - https://hackernoon.com/hook-into-angular-initialization-process-add41a6b7e 11 | - https://angular.io/api/core/APP_INITIALIZER 12 | --- 13 | 14 | # Content 15 | It is possible to execute asynchronous task before the app start by providing a function returning promise using `APP_INITIALIZER` token. 16 | 17 | ```typescript 18 | @NgModule({ 19 | providers: [ 20 | { 21 | provide: APP_INITIALIZER, 22 | useValue: functionReturningPromise 23 | multi: true 24 | }, 25 | }) 26 | export class AppModule {} 27 | 28 | 29 | ``` 30 | 31 | # file:app.module.ts 32 | ```typescript 33 | import { BrowserModule } from '@angular/platform-browser'; 34 | import { NgModule, APP_INITIALIZER } from '@angular/core'; 35 | import { AppComponent } from './app.component'; 36 | 37 | @NgModule({ 38 | imports: [BrowserModule], 39 | providers: [ 40 | { 41 | provide: APP_INITIALIZER, 42 | useValue: ()=>new Promise(resolve => { 43 | // LOL, this app will never load. 44 | // don't do it in prod! 45 | window.setTimeout(resolve, 1000000); 46 | }), 47 | multi: true 48 | } 49 | ], 50 | declarations: [AppComponent], 51 | bootstrap: [AppComponent] 52 | }) 53 | export class AppModule {} 54 | ``` 55 | -------------------------------------------------------------------------------- /snippets/adding-keyboard-shortcuts-to-elements.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Adding keyboard shortcuts to elements 3 | author: kirjs 4 | twitter: kirjs 5 | level: intermediate 6 | tags: 7 | - tips 8 | - good-to-know 9 | links: 10 | - https://alligator.io/angular/binding-keyup-keydown-events 11 | --- 12 | # Content 13 | 14 | It's really easy to add keyboard shortcuts in the template: 15 | ```html 16 | 17 | ``` 18 | 19 | 20 | # Bonus 21 | 22 | ```html 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | ``` 38 | 39 | # file:app.component.ts 40 | ```typescript 41 | import { Component } from '@angular/core'; 42 | 43 | @Component({ 44 | selector: 'my-app', 45 | template: ` 46 |

Type something in the input and hit control+enter to 47 | update the value below:

48 | 49 |

{{value || 'no value'}}

50 | 51 | ` 52 | }) 53 | export class AppComponent { 54 | value: string; 55 | } 56 | ``` 57 | -------------------------------------------------------------------------------- /snippets/global-event-listeners.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Global event listeners 3 | author: kirjs 4 | twitter: kirjs 5 | level: intermediate 6 | tags: 7 | - events 8 | - components 9 | --- 10 | # Content 11 | It is possible to add global event listeners in your Components/Directives with `HostListener`. Angular will take care of unsubscribing once your directive is destroyed. 12 | 13 | ```typescript 14 | @Directive({ 15 | selector: '[rightClicker]' 16 | }) 17 | export class ShortcutsDirective { 18 | @HostListener('window:keydown.ArrowRight') 19 | doImportantThings() { 20 | console.log('You pressed right'); 21 | } 22 | } 23 | ``` 24 | 25 | 26 | # Bonus 27 | You can have multiple bindings: 28 | 29 | ```typescript 30 | @HostListener('window:keydown.ArrowRight') 31 | @HostListener('window:keydown.PageDown') 32 | next() { 33 | console.log('Next') 34 | } 35 | ``` 36 | 37 | You can also pass params: 38 | 39 | ```typescript 40 | @HostListener('window:keydown.ArrowRight', '$event.target') 41 | next(target) { 42 | console.log('Pressed right on this element: ' + target) 43 | } 44 | ``` 45 | 46 | # file:app.component.ts 47 | ```typescript 48 | import { Component, HostListener } from '@angular/core'; 49 | 50 | @Component({ 51 | selector: 'my-app', 52 | template: ` 53 |

Try using your keyboard.

54 |

Last pressed: {{lastPressed}}

55 | ` 56 | }) 57 | export class AppComponent { 58 | lastPressed = 'nothing'; 59 | @HostListener('window:keydown', ['$event.key']) 60 | next(key: string) { 61 | this.lastPressed = key; 62 | } 63 | } 64 | ``` 65 | -------------------------------------------------------------------------------- /snippets/safe-navigation-operator.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Safe Navigation Operator 3 | level: beginner 4 | author: alex-bu-93 5 | tags: 6 | - object property handling 7 | - tips 8 | - good to know 9 | links: 10 | - https://github.com/angular/angular/issues/791 11 | --- 12 | 13 | # Content 14 | The [Safe Navigation Operator](https://angular.io/guide/template-syntax#the-safe-navigation-operator----and-null-property-paths) helps with preventing null-reference exceptions in component template expressions. It returns object property value if it exists or null otherwise. 15 | 16 | ```html 17 |

I will work even if student is null or undefined: {{student?.name}}

18 | ``` 19 | 20 | # Bonus 21 | ```html 22 | {{a?.b?.c}} 23 | ``` 24 | Underneath will be compiled to. 25 | ```html 26 | (_co.a == null)? null: ((_co.a.b == null)? null: _co.a.b.c)); 27 | ``` 28 | 29 | # file:app.component.ts 30 | ```typescript 31 | import { Component } from '@angular/core'; 32 | 33 | @Component({ 34 | selector: 'my-app', 35 | template: ` 36 | This code will generate an error: 37 |

{{student.name}}

38 | 39 | This code will work correctly: 40 |

{{student?.name}}

41 | ` 42 | }) 43 | export class AppComponent { 44 | student: {name: string, otherProperties: any} | null = null 45 | } 46 | ``` 47 | 48 | # file:app.module.ts 49 | ```typescript 50 | import { BrowserModule } from '@angular/platform-browser'; 51 | import { NgModule } from '@angular/core'; 52 | import { AppComponent } from './app.component'; 53 | @NgModule({ 54 | imports: [BrowserModule], 55 | declarations: [AppComponent], 56 | bootstrap: [AppComponent] 57 | }) 58 | export class AppModule {} 59 | ``` 60 | -------------------------------------------------------------------------------- /snippets/observables-as-outputs.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Observables as outputs 3 | author: NothingEverHappens 4 | twitter: kirjs 5 | level: intermediate 6 | tags: 7 | - tip 8 | - outputs 9 | --- 10 | 11 | # Content 12 | `EventEmitters` used for `@Output`'s are just Observables with an emit method. 13 | 14 | This means that you can just use `Observable` instance instead, e.g. we can wire up FormControl value changes directly: 15 | 16 | ```TypeScript 17 | readonly checkbox = new FormControl(); 18 | @Output() readonly change = this.checkbox.valueChanges; 19 | ``` 20 | 21 | # file:app.component.ts 22 | ```typescript 23 | import { Component, Output } from '@angular/core'; 24 | import { FormControl } from '@angular/forms'; 25 | 26 | @Component({ 27 | selector: 'my-child', 28 | template: `` 29 | }) 30 | export class ChildComponent { 31 | readonly control = new FormControl(); 32 | @Output() valueChange = this.control.valueChanges 33 | } 34 | 35 | @Component({ 36 | selector: 'my-app', 37 | template: ` 38 |

{{value}}

39 | ` 40 | }) 41 | export class AppComponent { 42 | value = 'Check or uncheck'; 43 | } 44 | ``` 45 | 46 | # file:app.module.ts 47 | ```typescript 48 | import { BrowserModule } from '@angular/platform-browser'; 49 | import { NgModule } from '@angular/core'; 50 | import { AppComponent, ChildComponent } from './app.component'; 51 | import { ReactiveFormsModule } from '@angular/forms'; 52 | @NgModule({ 53 | imports: [BrowserModule, ReactiveFormsModule], 54 | declarations: [AppComponent, ChildComponent], 55 | bootstrap: [AppComponent] 56 | }) 57 | export class AppModule {} 58 | ``` 59 | -------------------------------------------------------------------------------- /snippets/style-bindings.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Style bindings 3 | level: intermediate 4 | author: irustm 5 | twitter: irustm 6 | tags: 7 | - styles 8 | --- 9 | # Content 10 | You can use advanced property bindings to set specific style values based on component property values: 11 | 12 | ```html 13 |

14 | I am in green background 15 |

16 | 17 |

18 | May be important text. 19 |

20 | ``` 21 | 22 | 23 | # Bonus 24 | 25 | ```html 26 | 27 |
28 | 29 | 30 |
...
31 | 32 | 33 |
34 | ``` 35 | # file:app.component.ts 36 | ```typescript 37 | import { Component } from '@angular/core'; 38 | 39 | @Component({ 40 | selector: 'my-app', 41 | template: ` 42 |
Use the input below to select host background-color:
43 | 44 | 45 |
46 | Change me! 47 |
48 | ` 49 | }) 50 | export class AppComponent { 51 | color = '#ff9900'; 52 | width = 200; 53 | } 54 | ``` 55 | 56 | # file:app.module.ts 57 | ```typescript 58 | import { BrowserModule } from '@angular/platform-browser'; 59 | import { NgModule } from '@angular/core'; 60 | import { AppComponent } from './app.component'; 61 | import { FormsModule } from '@angular/forms'; 62 | 63 | @NgModule({ 64 | imports: [BrowserModule, FormsModule], 65 | declarations: [AppComponent], 66 | bootstrap: [AppComponent] 67 | }) 68 | export class AppModule {} 69 | ``` 70 | -------------------------------------------------------------------------------- /snippets/bind-to-host-properties-with-host-binding.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Bind to host properties with host binding 3 | author: kirjs 4 | twitter: kirjs 5 | level: intermediate 6 | tags: 7 | - components 8 | --- 9 | # Content 10 | Every rendered angular component is wrapped in a host element (which is the same as component's selector). 11 | 12 | It is possible to bind properties and attributes of host element using @HostBinding decorators, e.g. 13 | 14 | ```typescript 15 | import { Component, HostBinding } from '@angular/core'; 16 | 17 | @Component({ 18 | selector: 'my-app', 19 | template: ` 20 |
Use the input below to select host background-color:
21 | 22 | `, 23 | styles: [ 24 | `:host { display: block; height: 100px; }` 25 | ] 26 | }) 27 | export class AppComponent { 28 | @HostBinding('style.background') color = '#ff9900'; 29 | } 30 | ``` 31 | 32 | # file:app.component.ts 33 | ```typescript 34 | import { Component, HostBinding } from '@angular/core'; 35 | 36 | @Component({ 37 | selector: 'my-app', 38 | template: ` 39 |
Use the input below to select host background-color:
40 | 41 | `, 42 | styles: [ 43 | `:host { display: block; height: 100px; }` 44 | ] 45 | }) 46 | export class AppComponent { 47 | @HostBinding('style.background') color = '#ff9900'; 48 | } 49 | ``` 50 | 51 | 52 | # file:app.module.ts 53 | ```typescript 54 | import { BrowserModule } from '@angular/platform-browser'; 55 | import { NgModule } from '@angular/core'; 56 | import { AppComponent } from './app.component'; 57 | import { FormsModule } from '@angular/forms'; 58 | 59 | @NgModule({ 60 | imports: [BrowserModule, FormsModule], 61 | declarations: [AppComponent], 62 | bootstrap: [AppComponent] 63 | }) 64 | export class AppModule {} 65 | ``` 66 | -------------------------------------------------------------------------------- /snippets/two-way-binding-any-property.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Two-way binding any property 3 | author: kirjs 4 | twitter: kirjs 5 | level: intermediate 6 | tags: 7 | - tip 8 | - binding 9 | --- 10 | 11 | # Content 12 | Similar to how you can two-way bind `[(ngModel)]` you can two-way bind custom property on a component, for example `[(value)]`. To do it use appropriate Input/Output naming: 13 | 14 | ```typescript 15 | @Component({ 16 | selector: 'super-input', 17 | template: `...`, 18 | }) 19 | export class AppComponent { 20 | @Input() value: string; 21 | @Output() valueChange = new EventEmitter(); 22 | } 23 | ``` 24 | 25 | Then you can use it as: 26 | ```html 27 | 28 | ``` 29 | 30 | # file:app.component.ts 31 | ```typescript 32 | import { Component, Input, Output, EventEmitter } from '@angular/core'; 33 | 34 | @Component({ 35 | selector: 'super-input', 36 | template: ` 37 | Select the value: 38 | 39 | 40 | `, 41 | }) 42 | export class SuperInputComponent { 43 | @Input() value: string; 44 | @Output() valueChange = new EventEmitter(); 45 | } 46 | 47 | @Component({ 48 | selector: 'my-app', 49 | template: ` 50 |

{{value}}

51 | 52 | ` 53 | }) 54 | export class AppComponent { 55 | value = '0'; 56 | } 57 | ``` 58 | 59 | # file:app.module.ts 60 | ```typescript 61 | import { BrowserModule } from '@angular/platform-browser'; 62 | import { NgModule } from '@angular/core'; 63 | import { AppComponent, SuperInputComponent } from './app.component'; 64 | 65 | @NgModule({ 66 | imports: [BrowserModule], 67 | declarations: [AppComponent, SuperInputComponent], 68 | bootstrap: [AppComponent] 69 | }) 70 | export class AppModule {} 71 | ``` 72 | -------------------------------------------------------------------------------- /snippets/trackby-in-for-loops.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: trackBy in for loops 3 | author: maktarsis 4 | level: beginner 5 | 6 | tags: 7 | - good-to-know 8 | - tips 9 | - components 10 | - performance 11 | 12 | links: 13 | - https://angular.io/api/common/NgForOf 14 | - https://angular.io/api/core/TrackByFunction 15 | --- 16 | 17 | # Content 18 | To avoid the expensive operations, we can help Angular to track which items added or removed i.e. customize the default tracking algorithm by providing a trackBy option to NgForOf. 19 | 20 | So you can provide your custom trackBy function that will return unique identifier for each iterated item. 21 | For example, some key value of the item. If this key value matches the previous one, then Angular won't detect changes. 22 | 23 | **trackBy** takes a function that has _index_ and _item_ args. 24 | 25 | ```typescript 26 | @Component({ 27 | selector: 'my-app', 28 | template: ` 29 |
    30 |
  • {{item.id}}
  • 31 |
32 | ` 33 | }) 34 | export class AppComponent { 35 | trackByFn(index, item) { 36 | return item.id; 37 | } 38 | } 39 | ``` 40 | If trackBy is given, Angular tracks changes by the return value of the function. 41 | 42 | Now when you change the collection, Angular can track which items have been added or removed according to the unique identifier and create/destroy only changed items. 43 | 44 | # file:app.component.ts 45 | ```typescript 46 | import { Component } from '@angular/core'; 47 | 48 | interface Item { 49 | id: string; 50 | } 51 | 52 | @Component({ 53 | selector: 'my-app', 54 | template: ` 55 |
    56 |
  • {{item.id}}
  • 57 |
58 | ` 59 | }) 60 | export class AppComponent { 61 | items = [ 62 | {id: 1}, 63 | {id: 2}, 64 | {id: 3} 65 | ]; 66 | 67 | trackByFn(index: number, item: Item) { 68 | return item.id; 69 | } 70 | } 71 | 72 | ``` 73 | -------------------------------------------------------------------------------- /snippets/window-location-injection.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Window Location injection 3 | author: fetis 4 | twitter: fetis26 5 | level: intermediate 6 | 7 | tags: 8 | - dependency-injection 9 | - testing 10 | 11 | links: 12 | - https://itnext.io/testing-browser-window-location-in-angular-application-e4e8388508ff 13 | - https://angular.io/guide/dependency-injection 14 | 15 | --- 16 | 17 | # Content 18 | For testing purposes you might want to inject `window.location` object in your component. 19 | You can achieve this with custom `InjectionToken` mechanism provided by Angular. 20 | 21 | ```typescript 22 | export const LOCATION_TOKEN = new InjectionToken('Window location object'); 23 | 24 | @NgModule({ 25 | providers: [ 26 | { provide: LOCATION_TOKEN, useValue: window.location } 27 | ] 28 | }) 29 | export class SharedModule {} 30 | 31 | //... 32 | 33 | @Component({ 34 | }) 35 | export class AppComponent { 36 | constructor( 37 | @Inject(LOCATION_TOKEN) public location: Location 38 | ) {} 39 | } 40 | ``` 41 | 42 | # file:app.component.ts 43 | ```typescript 44 | import { Component, Inject } from '@angular/core'; 45 | import { LOCATION_TOKEN } from './app.module'; 46 | 47 | @Component({ 48 | selector: 'my-app', 49 | template: `{{ location.href }}` 50 | }) 51 | export class AppComponent { 52 | constructor( 53 | @Inject(LOCATION_TOKEN) public location: Location 54 | ) {} 55 | 56 | useIt() { 57 | this.location.assign('xxx'); 58 | } 59 | } 60 | ``` 61 | # file:app.module.ts 62 | ```typescript 63 | import { BrowserModule } from '@angular/platform-browser'; 64 | import { NgModule, InjectionToken } from '@angular/core'; 65 | import { AppComponent } from './app.component'; 66 | 67 | export const LOCATION_TOKEN = new InjectionToken('Window location object'); 68 | 69 | @NgModule({ 70 | imports: [BrowserModule], 71 | declarations: [AppComponent], 72 | providers: [ 73 | { provide: LOCATION_TOKEN, useValue: window.location } 74 | ], 75 | bootstrap: [AppComponent] 76 | }) 77 | export class AppModule {} 78 | ``` 79 | -------------------------------------------------------------------------------- /snippets/ng-content.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ng-content 3 | level: beginner 4 | author: thekiba 5 | twitter: thekiba_io 6 | tags: 7 | - good-to-know 8 | - tips 9 | - components 10 | links: 11 | - https://medium.com/p/96a29d70d11b 12 | --- 13 | 14 | # Content 15 | With `ng-content` you can pass any elements to a component. 16 | This simplifies creating reusable components. 17 | 18 | ```typescript 19 | @Component({ 20 | selector: 'wrapper', 21 | template: ` 22 |
23 | 24 |
25 | `, 26 | }) 27 | export class Wrapper {} 28 | ``` 29 | 30 | ```html 31 | 32 |

Hello World!

33 |
34 | ``` 35 | 36 | # file:app.component.ts 37 | ```typescript 38 | import { Component } from '@angular/core'; 39 | 40 | function template(useClass: string): string { 41 | return ` 42 |
43 | 44 |
45 | `; 46 | } 47 | 48 | @Component({ 49 | selector: 'app-card', 50 | template: template('card') 51 | }) 52 | export class Card {} 53 | 54 | @Component({ 55 | selector: 'app-card-header', 56 | template: template('card-header') 57 | }) 58 | export class CardHeader {} 59 | 60 | @Component({ 61 | selector: 'app-card-body', 62 | template: template('card-body') 63 | }) 64 | export class CardBody {} 65 | 66 | @Component({ 67 | selector: 'my-app', 68 | template: ` 69 | 70 | 71 | Header 72 | 73 | 74 | Body 75 | 76 | 77 | ` 78 | }) 79 | export class AppComponent {} 80 | ``` 81 | 82 | # file:app.module.ts 83 | ```typescript 84 | import { BrowserModule } from '@angular/platform-browser'; 85 | import { NgModule } from '@angular/core'; 86 | import { AppComponent, CardBody, CardHeader, Card } from './app.component'; 87 | import { FormsModule } from '@angular/forms'; 88 | @NgModule({ 89 | imports: [BrowserModule, FormsModule], 90 | declarations: [AppComponent, CardBody, CardHeader, Card], 91 | bootstrap: [AppComponent] 92 | }) 93 | export class AppModule {} 94 | ``` 95 | -------------------------------------------------------------------------------- /snippets/passing-template-as-an-input.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Passing template as an input 3 | author: fetis 4 | twitter: fetis26 5 | level: intermediate 6 | tags: 7 | - template 8 | links: 9 | - https://blog.angular-university.io/angular-ng-template-ng-container-ngtemplateoutlet 10 | --- 11 | # Content 12 | It's possible to take a template as `@Input` for a component to customize the render 13 | 14 | 15 | ```typescript 16 | @Component({ 17 | template: ` 18 | 21 | `, 22 | }) 23 | export class SiteMenuComponent { 24 | @Input() template: TemplateRef; 25 | } 26 | ``` 27 | ```html 28 | 29 | 30 | 31 | 32 | 33 | 34 | ``` 35 | > Note: `ng-content` should be used for most of the cases and it's simpler and more declarative. 36 | > Only use this approach if you need extra flexibility that can't be achieved with ng-content. 37 | 38 | # file:app.component.ts 39 | ```typescript 40 | import { Component, Input, TemplateRef } from '@angular/core'; 41 | 42 | @Component({ 43 | selector: 'site-menu', 44 | template: ` 45 | 48 | `, 49 | }) 50 | export class SiteMenuComponent { 51 | @Input() template: TemplateRef 52 | } 53 | 54 | @Component({ 55 | selector: 'my-app', 56 | template: ` 57 | 58 | 59 | 60 | 61 | 62 | 63 | ` 64 | }) 65 | export class AppComponent {} 66 | ``` 67 | 68 | # file:app.module.ts 69 | ```typescript 70 | // This section is optional, remove it if the code below is good. 71 | import { BrowserModule } from '@angular/platform-browser'; 72 | import { NgModule } from '@angular/core'; 73 | import { AppComponent, SiteMenuComponent } from './app.component'; 74 | 75 | @NgModule({ 76 | imports: [BrowserModule], 77 | declarations: [AppComponent, SiteMenuComponent], 78 | bootstrap: [AppComponent] 79 | }) 80 | export class AppModule {} 81 | ``` 82 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ![contribution guidelines](https://i.imgur.com/FaNNcVH.png) 2 | 3 | **30 seconds of code** is a community effort, so feel free to contribute in any way you can. Every contribution helps! 4 | 5 | Here's what you can do to help: 6 | 7 | - Submit [pull requests](https://github.com/nycJSorg/30-seconds-of-angular/pulls) with snippets that you have created (see below for guidelines). 8 | - [Open issues](https://github.com/nycJSorg/30-seconds-of-angular/issues/new) for things you want to see added or modified. 9 | - Be part of the discussion by helping out with [existing issues](https://github.com/nycJSorg/30-seconds-of-angular/issues). 10 | - Fix typos in existing snippets, improve snippet descriptions and explanations or provide better examples. 11 | 12 | ### Snippet submission and Pull request guidelines 13 | 14 | - **DO NOT MODIFY THE README.md!** Make changes to individual snippet files. **Travis CI** will automatically build the `README.md` files when your pull request is merged. 15 | - **Snippet filenames** must correspond to the title of the snippet. For example, if your snippet is titled `title: Awesome Snippet` the filename should be `awesome-snippet.md`. 16 | - Use `kebab-case`, not `camelCase` or `snake_case`. 17 | - Avoid capitalization of words, except if the whole word is capitalized (e.g. `URL` should be capitalized in the filename and the snippet title). 18 | - **Snippet level** should be `beginner`, `invermediate` or `advanced` 19 | - **Snippet titles** should briefly describe what the snippet is about 20 | - Snippet titles must be unique (although if you cannot find a better title, just add some placeholder at the end of the filename and title and we will figure it out). 21 | - **Snippet content** should have a brief description and a code snippet and must not exceed 25 lines 22 | - **Snippet demo** 23 | - It's possible to add an interactive demo by adding headers for appropriate files: `#file:app.component.ts`, `#file:app.module.ts`, or `#file:main.ts` headers. 24 | - Snippets *should* solve real-world problems, no matter how simple. 25 | - Snippets *should* be abstract enough to be applied to different scenarios. 26 | - You can start creating a new snippet, by using the [snippet template](snippet-template.md) to format your snippets. 27 | 28 | -------------------------------------------------------------------------------- /snippets/loader-component.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Loader Component 3 | level: beginner 4 | author: thekiba 5 | twitter: thekiba_io 6 | tags: 7 | - tips 8 | - good-to-know 9 | - components 10 | - templates 11 | 12 | links: 13 | - https://medium.com/claritydesignsystem/ng-content-the-hidden-docs-96a29d70d11b 14 | - https://blog.angularindepth.com/https-medium-com-thomasburleson-animated-ghosts-bfc045a51fba 15 | --- 16 | 17 | # Content 18 | You can create own helper component and use it instead of `*ngIf`. 19 | 20 | ```typescript 21 | @Component({ 22 | selector: 'loader', 23 | template: ` 24 | 25 | 🕚 Wait 10 seconds! 26 | ` 27 | }) 28 | class LoaderComponent { 29 | @Input() loading: boolean; 30 | } 31 | ``` 32 | 33 | For usage example: 34 | ```html 35 | 🦊 🦄 🐉 36 | ``` 37 | 38 | > Note that the content will be eagerly evaluated, e.g. in the snippet below `destroy-the-world` will be created before the loading even starts: 39 | 40 | ```html 41 | 42 | ``` 43 | 44 | # file:app.component.ts 45 | ```typescript 46 | import { Component, Input } from '@angular/core'; 47 | 48 | @Component({ 49 | selector: 'loader', 50 | template: ` 51 | 52 | 🕚 Wait 10 seconds! 53 | ` 54 | }) 55 | export class LoaderComponent { 56 | @Input() loading: boolean; 57 | } 58 | 59 | @Component({ 60 | selector: 'my-app', 61 | template: ` 62 | 65 | 66 | 🦊 🦄 🐉 67 | 68 | ` 69 | }) 70 | export class AppComponent { 71 | isLoading: boolean = true; 72 | } 73 | ``` 74 | 75 | # file:app.module.ts 76 | ```typescript 77 | import { BrowserModule } from '@angular/platform-browser'; 78 | import { NgModule } from '@angular/core'; 79 | import { AppComponent, LoaderComponent } from './app.component'; 80 | import { FormsModule } from '@angular/forms'; 81 | 82 | @NgModule({ 83 | imports: [BrowserModule, FormsModule], 84 | declarations: [AppComponent, LoaderComponent], 85 | bootstrap: [AppComponent] 86 | }) 87 | export class AppModule {} 88 | ``` 89 | -------------------------------------------------------------------------------- /snippets/access-dom-element.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Access DOM Element 3 | author: alQlagin 4 | level: intermediate 5 | links: 6 | - https://stackblitz.com/edit/angular-tooltipjs-directive?file=src%2Fapp%2Ftooltip.directive.ts 7 | - https://angular.io/api/core/ElementRef 8 | - https://angular.io/guide/security 9 | tags: 10 | - components 11 | - dom 12 | --- 13 | # Content 14 | In rare cases when you need to access DOM element directly, you can get it by requiring `ElementRef` in your construtor. 15 | ```typescript 16 | @Directive({selector: '[tooltip]'}) 17 | export class TooltipDirective implements OnInit, OnDestroy { 18 | private readonly tooltip: Tooltip; 19 | constructor( 20 | private elementRef: ElementRef, 21 | ) { } 22 | } 23 | ``` 24 | > Note: Try avoiding direct DOM manipulation except the cases when it's the only option, such as interaction with 3rd party libraries. 25 | 26 | # file:app.component.ts 27 | ```typescript 28 | import { Component } from '@angular/core'; 29 | 30 | import { Attribute, Directive, ElementRef, OnInit, OnDestroy, HostListener } from '@angular/core'; 31 | import Tooltip from 'https://unpkg.com/popper.js'; 32 | 33 | @Directive({ 34 | selector: '[tooltip]' 35 | }) 36 | export class TooltipDirective implements OnInit, OnDestroy { 37 | 38 | private tooltip: Tooltip; 39 | constructor( 40 | private elementRef: ElementRef, 41 | @Attribute('title') private text: string 42 | ) { } 43 | 44 | ngOnInit() { 45 | const content = document.createElement('div'); 46 | content.innerText = this.text; 47 | this.tooltip = new Tooltip(this.elementRef.nativeElement, content); 48 | } 49 | 50 | ngOnDestroy() { 51 | if (this.tooltip) { 52 | document.body.removeChild(this.tooltip.popper); 53 | this.tooltip.dispose(); 54 | } 55 | } 56 | 57 | @HostListener('mouseover') 58 | over() { 59 | document.body.appendChild(this.tooltip.popper); 60 | } 61 | 62 | @HostListener('mouseout') 63 | out() { 64 | document.body.removeChild(this.tooltip.popper); 65 | } 66 | } 67 | 68 | @Component({ 69 | selector: 'my-app', 70 | template: `

Hover me

` 71 | }) 72 | export class AppComponent {} 73 | ``` 74 | # file:app.module.ts 75 | ```typescript 76 | import { BrowserModule } from '@angular/platform-browser'; 77 | import { NgModule } from '@angular/core'; 78 | import { AppComponent, TooltipDirective } from './app.component'; 79 | 80 | @NgModule({ 81 | imports: [BrowserModule], 82 | declarations: [AppComponent, TooltipDirective], 83 | bootstrap: [AppComponent] 84 | }) 85 | export class AppModule {} 86 | ``` 87 | -------------------------------------------------------------------------------- /snippets/optional-parameters-in-the-middle.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Optional parameters in the middle 3 | author: irustm 4 | level: beginner 5 | tags: 6 | - routing 7 | links: 8 | - https://stackblitz.com/edit/angular-xvy5pd 9 | --- 10 | # Content 11 | Navigate with matrix params: 12 | 13 | the router will navigate to `/first;name=foo/details` 14 | ```html 15 | 16 | link with params 17 | 18 | ``` 19 | 20 | # file:app.component.ts 21 | import { Component, OnInit } from '@angular/core'; 22 | import { ActivatedRoute } from '@angular/router'; 23 | 24 | @Component({ 25 | selector: 'my-app', 26 | template: ` 27 | home
28 | link without params
29 | link with params
30 | 31 | `, 32 | styleUrls: [ './app.component.css' ] 33 | }) 34 | export class AppComponent { 35 | } 36 | 37 | @Component({ 38 | selector: 'first', 39 | template: ` 40 | first component 41 | 42 | `, 43 | }) 44 | export class FirstComponent implements OnInit { 45 | params: any; 46 | constructor(private activatedRoute: ActivatedRoute){} 47 | ngOnInit(){ 48 | this.activatedRoute.params.subscribe(params => { 49 | console.log(`FirstComponent:`, params); 50 | }); 51 | } 52 | } 53 | @Component({ 54 | selector: 'details', 55 | template: ` 56 | Details 57 | `, 58 | }) 59 | export class DetailComponent { 60 | constructor(private activatedRoute: ActivatedRoute){} 61 | ngOnInit(){ 62 | this.activatedRoute.params.subscribe(params => { 63 | console.log(`DetailComponent:`, params); 64 | }); 65 | } 66 | } 67 | 68 | 69 | 70 | # file:app.module.ts 71 | import { NgModule } from '@angular/core'; 72 | import { APP_BASE_HREF } from '@angular/common'; 73 | import { BrowserModule } from '@angular/platform-browser'; 74 | import { FormsModule } from '@angular/forms'; 75 | import { RouterModule, Route } from '@angular/router'; 76 | 77 | import { AppComponent, DetailComponent, FirstComponent } from './app.component'; 78 | 79 | const ROUTES: Route[] = [ 80 | { 81 | path: 'first', 82 | component: FirstComponent, 83 | children: [{ 84 | path: 'details', 85 | component: DetailComponent 86 | } 87 | ] 88 | }, 89 | ]; 90 | @NgModule({ 91 | imports: [ BrowserModule, FormsModule, RouterModule.forRoot(ROUTES) ], 92 | declarations: [ AppComponent, DetailComponent, FirstComponent ], 93 | bootstrap: [ AppComponent ], 94 | providers: [{ provide: APP_BASE_HREF, useValue: '/angular/30-seconds' }] 95 | }) 96 | export class AppModule { } 97 | -------------------------------------------------------------------------------- /snippets/component-level-providers.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Component level providers 3 | level: intermediate 4 | author: thekiba 5 | twitter: thekiba_io 6 | tags: 7 | - tips 8 | - components 9 | - dependency-injection 10 | links: 11 | - https://angular.io/guide/hierarchical-dependency-injection#component-level-injectors 12 | - https://stackblitz.com/edit/angular-cdk-happy-animals 13 | --- 14 | 15 | # Content 16 | Generally we get one service instance per the whole application. 17 | It is also possible to create an instance of service per component or directive. 18 | 19 | ```typescript 20 | @Component({ 21 | selector: 'provide', 22 | template: '', 23 | providers: [ Service ] 24 | }) 25 | export class ProvideComponent {} 26 | ``` 27 | 28 | ```typescript 29 | @Directive({ 30 | selector: '[provide]', 31 | providers: [ Service ] 32 | }) 33 | export class ProvideDirective {} 34 | ``` 35 | 36 | # file:app.component.ts 37 | ```typescript 38 | import { Injectable, Component, OnInit, OnDestroy, Host } from '@angular/core'; 39 | 40 | @Injectable() 41 | export class ItemsLinker implements OnDestroy { 42 | private links: Set = new Set(); 43 | 44 | link(item: ItemComponent) { 45 | this.links.add(item); 46 | } 47 | 48 | unlink(item: ItemComponent) { 49 | this.links.delete(item); 50 | } 51 | 52 | ngOnDestroy() { 53 | this.links.clear(); 54 | } 55 | } 56 | 57 | @Component({ 58 | selector: 'item', 59 | template: '' 60 | }) 61 | export class ItemComponent implements OnInit, OnDestroy { 62 | 63 | constructor(@Host() private linker: ItemsLinker) {} 64 | 65 | ngOnInit() { 66 | this.linker.link(this); 67 | } 68 | ngOnDestroy() { 69 | this.linker.unlink(this); 70 | } 71 | } 72 | 73 | @Component({ 74 | selector: 'items', 75 | template: '', 76 | providers: [ ItemsLinker ] 77 | }) 78 | export class ItemsComponent {} 79 | 80 | @Component({ 81 | selector: 'my-app', 82 | template: ` 83 | 84 | 🦊 85 | 🦄 86 | 🐉 87 | 88 | ` 89 | }) 90 | export class AppComponent {} 91 | ``` 92 | 93 | # file:app.module.ts 94 | ```typescript 95 | import { BrowserModule } from '@angular/platform-browser'; 96 | import { NgModule } from '@angular/core'; 97 | import { AppComponent, ItemsComponent, ItemComponent } from './app.component'; 98 | 99 | @NgModule({ 100 | imports: [BrowserModule], 101 | declarations: [ 102 | AppComponent, 103 | ItemsComponent, 104 | ItemComponent 105 | ], 106 | bootstrap: [AppComponent] 107 | }) 108 | export class AppModule {} 109 | ``` 110 | -------------------------------------------------------------------------------- /snippets/mark-reactive-fields-as-touched.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Mark reactive fields as touched 3 | author: alex-bu-93 4 | level: intermediate 5 | tags: 6 | - reactive forms validation 7 | - tips 8 | - good to know 9 | links: 10 | - https://angular.io/guide/reactive-forms 11 | --- 12 | 13 | # Content 14 | Here is the way to notify user that there are fields with non-valid values. 15 | 16 | `markFieldsAsTouched` function FormGroup or FormArray as an argument. 17 | 18 | ```typescript 19 | function markFieldsAsTouched(form: AbstractControl): void { 20 | form.markAsTouched({ onlySelf: true }); 21 | if (form instanceof FormArray || form instanceof FormGroup) { 22 | Object.values(form.controls).forEach(markFieldsAsTouched); 23 | } 24 | } 25 | ``` 26 | 27 | # file:app.component.ts 28 | ```typescript 29 | import { Component } from '@angular/core'; 30 | import { AbstractControl, FormControl, FormGroup, FormArray, Validators } from '@angular/forms'; 31 | 32 | @Component({ 33 | selector: 'my-app', 34 | template: ` 35 |
fieldOne
36 |
37 | 38 |
39 |
40 |
Need to fill
41 |
42 |
43 | 44 |
45 |
46 |
Need to fill
47 |
48 |
49 | 50 |
` 51 | }) 52 | export class AppComponent { 53 | 54 | formGroup: FormGroup = new FormGroup({ 55 | 'fieldOne': new FormControl(null, Validators.required), 56 | 'fieldTwo': new FormControl(null, Validators.required) 57 | }); 58 | 59 | onSubmit(): void { 60 | if (this.formGroup.valid) { 61 | // Work on your validated data 62 | } else { 63 | this.markFieldsAsTouched(this.formGroup); 64 | } 65 | } 66 | 67 | markFieldsAsTouched(form: AbstractControl): void { 68 | form.markAsTouched({onlySelf: true}); 69 | if (form instanceof FormArray || form instanceof FormGroup) { 70 | Object.values(form.controls).forEach(this.markFieldsAsTouched); 71 | } 72 | } 73 | } 74 | ``` 75 | 76 | On 'Submit' trigger function 'markFieldsAsTouched' with your reactive form passed as arg. 77 | It will mark form controls as 'touch' and in case of non-validity control will get error property. 78 | 79 | Based on control error property you can add validation notification via, for example, *ngIf directive in template 80 | 81 | # Bonus 82 | 83 | It's very useful to check out more general method [Accessing all nested form controls](#accessing-all-nested-form-controls) by [Thekiba](https://twitter.com/thekiba_io) to work with controls. 84 | -------------------------------------------------------------------------------- /snippets/accessing-all-nested-form-controls.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Accessing all nested form controls 3 | level: intermediate 4 | author: thekiba 5 | twitter: thekiba_io 6 | tags: 7 | - reactive forms 8 | - tips 9 | - good to know 10 | links: 11 | - https://angular.io/guide/reactive-forms 12 | --- 13 | 14 | # Content 15 | Sometimes we need to work with every single Control is a form. Here's how it can be done: 16 | 17 | ```typescript 18 | function flattenControls(form: AbstractControl): AbstractControl[] { 19 | let extracted: AbstractControl[] = [ form ]; 20 | if (form instanceof FormArray || form instanceof FormGroup) { 21 | const children = Object.values(form.controls).map(flattenControls); 22 | extracted = extracted.concat(...children); 23 | } 24 | return extracted; 25 | } 26 | ``` 27 | 28 | For examples use: 29 | ```typescript 30 | // returns all dirty abstract controls 31 | flattenControls(form).filter((control) => control.dirty); 32 | 33 | // mark all controls as touched 34 | flattenControls(form).forEach((control) => 35 | control.markAsTouched({ onlySelf: true })); 36 | ``` 37 | 38 | # file:app.component.ts 39 | ```typescript 40 | import { Component } from '@angular/core'; 41 | import { AbstractControl, FormControl, FormGroup, FormArray, FormBuilder } from '@angular/forms'; 42 | 43 | @Component({ 44 | selector: 'my-app', 45 | template: ` 46 |

Count of dirty controls: {{ dirtyControls.length }}

47 | 48 | ` 49 | }) 50 | export class AppComponent { 51 | 52 | form: AbstractControl; 53 | 54 | get dirtyControls(): AbstractControl[] { 55 | return flattenControls(this.form).filter(control => control.dirty); 56 | } 57 | 58 | constructor(private fb: FormBuilder) { 59 | this.form = fb.group({ 60 | a: fb.control(''), 61 | b: fb.array([ 62 | fb.control(''), 63 | fb.group({}), 64 | fb.array([]) 65 | ]) 66 | }); 67 | } 68 | 69 | markAsDirty(form: AbstractControl): void { 70 | for (const control of flattenControls(this.form)) { 71 | control.markAsDirty({ onlySelf: true }); 72 | } 73 | } 74 | } 75 | 76 | function flattenControls(form: AbstractControl): AbstractControl[] { 77 | let extracted: AbstractControl[] = [ form ]; 78 | if (form instanceof FormArray || form instanceof FormGroup) { 79 | const children = Object.values(form.controls).map(flattenControls); 80 | extracted = extracted.concat(...children); 81 | } 82 | return extracted; 83 | } 84 | ``` 85 | 86 | # file:app.module.ts 87 | ```typescript 88 | import { BrowserModule } from '@angular/platform-browser'; 89 | import { NgModule } from '@angular/core'; 90 | import { AppComponent } from './app.component'; 91 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 92 | @NgModule({ 93 | imports: [BrowserModule, FormsModule, ReactiveFormsModule], 94 | declarations: [AppComponent], 95 | bootstrap: [AppComponent] 96 | }) 97 | export class AppModule {} 98 | ``` 99 | -------------------------------------------------------------------------------- /snippets/getting-components-of-different-types-with-viewchild.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Getting components of different types with ViewChild 3 | level: advanced 4 | author: thekiba 5 | twitter: thekiba_io 6 | tags: 7 | - good-to-know 8 | - tips 9 | - components 10 | - dependency-injection 11 | links: 12 | - https://www.youtube.com/watch?v=PRRgo6F0cjs 13 | --- 14 | 15 | # Content 16 | It's possible to use `@ViewChild` (also `@ViewChildren` and `@ContentChild/Children`) to query for components of different types using dependency injection. 17 | 18 | In the example below we can use `@ViewChildren(Base)` to get instances of `Foo` and `Bar`. 19 | 20 | ```typescript 21 | abstract class Base {} 22 | 23 | @Component({ 24 | selector: 'foo', 25 | providers: [{ provide: Base, useExisting: Foo }] 26 | }) 27 | class Foo extends Base {} 28 | 29 | @Component({ 30 | selector: 'bar', 31 | providers: [{ provide: Base, useExisting: Bar }] 32 | }) 33 | class Bar extends Base {} 34 | 35 | // Now we can require both types of components using Base. 36 | @Component({ template: `` }) 37 | class AppComponent { 38 | @ViewChildren(Base) components: QueryList; 39 | } 40 | ``` 41 | 42 | # file:app.component.ts 43 | ```typescript 44 | import { Component, ViewChildren, QueryList, AfterViewInit } from '@angular/core'; 45 | 46 | export abstract class Animal { 47 | abstract say(); 48 | } 49 | 50 | @Component({ 51 | selector: 'fox', 52 | template: `🦊`, 53 | providers: [{ provide: Animal, useExisting: Fox }] 54 | }) 55 | export class Fox extends Animal { 56 | say() { 57 | console.log('Joff-tchoff-tchoffo-tchoffo-tchoff!'); 58 | } 59 | } 60 | 61 | @Component({ 62 | selector: 'rice', 63 | template: `🍚`, 64 | providers: [{ provide: Animal, useExisting: Rice }] 65 | }) 66 | export class Rice extends Animal { 67 | say() { 68 | console.log('lol'); 69 | } 70 | } 71 | 72 | @Component({ 73 | selector: 'dragon', 74 | template: `🐉`, 75 | providers: [{ provide: Animal, useExisting: Dragon }] 76 | }) 77 | export class Dragon extends Animal { 78 | say() { 79 | console.log('Wa-pa-pa-pa-pa-pa-pow!'); 80 | } 81 | } 82 | 83 | @Component({ 84 | selector: 'my-app', 85 | template: ` 86 | 87 | 88 | 89 | ` 90 | }) 91 | export class AppComponent implements AfterViewInit { 92 | @ViewChildren(Animal) animals: QueryList; 93 | 94 | ngAfterViewInit() { 95 | animals.forEach((animal) => animal.say()); 96 | } 97 | } 98 | ``` 99 | 100 | # file:app.module.ts 101 | ```typescript 102 | import { BrowserModule } from '@angular/platform-browser'; 103 | import { NgModule } from '@angular/core'; 104 | import { AppComponent, Fox, Rice, Dragon } from './app.component'; 105 | 106 | @NgModule({ 107 | imports: [BrowserModule], 108 | declarations: [AppComponent, Fox, Rice, Dragon], 109 | bootstrap: [AppComponent] 110 | }) 111 | export class AppModule {} 112 | ``` 113 | -------------------------------------------------------------------------------- /snippets/svg.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: SVG 3 | author: kirjs 4 | twitter: kirjs 5 | level: advanced 6 | tags: 7 | - tip 8 | - SVG 9 | --- 10 | 11 | # Content 12 | It is possible to use SVG tags in your Angular component, to create beautiful graphs and visualizations. There are 3 things you need to know: 13 | 14 | 1. When binding an SVG attribute, use `attr` 15 | ```html 16 | 17 | ``` 18 | 19 | 2. When creating sub-components, use attribute and not tag selector: 20 | ```html 21 | // Not: 22 | 23 | ``` 24 | ```typescript 25 | @Component({selector: '[child-component]' }) 26 | ``` 27 | 28 | 3. When using SVG tags in sub-components use svg prefix: 29 | ```typescript 30 | @Component({ 31 | selector: '[child-component]', 32 | template: `` 33 | }) 34 | ``` 35 | 36 | # file:app.component.ts 37 | ```typescript 38 | import { Component, Input } from '@angular/core'; 39 | 40 | function generateData() { 41 | return Array.from(new Array(10)).map(index => ({ 42 | index, 43 | value: Math.round(Math.random() * 80) 44 | })); 45 | } 46 | 47 | @Component({ 48 | selector: '[kirjs-ticks]', 49 | template: ` 50 | 56 | {{ i }} 57 | 58 | ` 59 | }) 60 | export class TicksComponent { 61 | @Input() data; 62 | @Input() barWidth = 30; 63 | padding = 10; 64 | barSpace = this.padding + this.barWidth; 65 | 66 | getIndex(i: number) { 67 | return i; 68 | } 69 | } 70 | 71 | @Component({ 72 | selector: 'my-app', 73 | template: ` 74 | 75 | 81 | 88 | 89 | {{ item.value }} 90 | 91 | 92 | 93 | ` 94 | }) 95 | export class AppComponent { 96 | barWidth = 30; 97 | padding = 10; 98 | barSpace = this.padding + this.barWidth; 99 | data = generateData(); 100 | 101 | constructor() { 102 | window.setInterval(() => { 103 | this.data = generateData(); 104 | }, 1000); 105 | } 106 | 107 | getIndex(a, b) { 108 | return a; 109 | } 110 | } 111 | 112 | ``` 113 | 114 | # file:app.module.ts 115 | ```typescript 116 | import { BrowserModule } from '@angular/platform-browser'; 117 | import { NgModule } from '@angular/core'; 118 | import { AppComponent, TicksComponent } from './app.component'; 119 | 120 | @NgModule({ 121 | imports: [BrowserModule], 122 | declarations: [AppComponent, TicksComponent], 123 | bootstrap: [AppComponent] 124 | }) 125 | export class AppModule {} 126 | ``` 127 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at 30secondsofcode@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /COLLABORATING.md: -------------------------------------------------------------------------------- 1 | # Collaborator team rules and guidelines for community moderation 2 | 3 | **As a contributor/member of the community, remember that you can always open issues about moderation and problems with how community moderation is done. We are always open to constructive criticism and feedback!** 4 | 5 | ## Responsibilities of a collaborator 6 | 7 | As a member of the team that manages **30 seconds of code**, you have the following responsibilities: 8 | 9 | - **Be part of the conversation in the issue tracker.** That includes (but is not limited to) helping out new members, discussing new features and explaining decisions to people. 10 | - **Review pull requests.** You do not have to read through all of the pull requests and review them, but taking the time each day to review a few can help a great deal. 11 | - **Be civil and polite.** If you are about to lose your temper, take a step back and do something else. We want our interactions with the community to be polite so that more people can join the project and contribute in any way they can. Remember to always thank contributors for their help, even if it's minor changes or changes that did not make it into the project. This way we can reward and encourage people to keep being part of the community. 12 | - **Contribute when you want, moderate when you can.** If you have a lot on your plate outside of this project, it's alright. It's better to take a break for a few days rather than hastily deal with issues and pull requests that might break things. 13 | 14 | ## Guidelines for merging pull requests and making changes to the project 15 | 16 | - **[Usual guidelines](https://github.com/30-seconds/30-seconds-of-code/blob/master/CONTRIBUTING.md) apply.** Make sure to follow them, like everybody else. 17 | - **For a pull request to be considered ready to merge, there should be at least 2 (preferably 3) reviews approving it for merge.** There are, however, certain exceptions: 18 | - **If a pull request only fixes typos**, there is no need to wait for a second reviewer (unless you are not certain these were not typos in the first place). 19 | - **If a pull request only clarifies a snippet's description or enforces the style guide for an existing snippet**, you might be able to merge it without getting a second reviewer to review it, but only if you are certain about it. 20 | - **Make sure pull requests pass the Travis CI build**, otherwise try and find out what's wrong and inform the author of the pull request. 21 | - **Changes to build scripts, guidelines and things that might break the processes we have in place need to be reviewed by [@Chalarangelo](https://github.com/Chalarangelo)** (this is temporary, but we need a baseline to make sure we break as few things as possible in the beginning). 22 | - **After merging a pull request, make sure to check for untagged snippets and tag them appropriately.** Try to keep all snippets tagged, so that the list and website are up to date. 23 | - **If you make changes or additions to existing snippets or if you want to add your own snippets, you will go through the pull request process that everyone else goes.** Exceptions apply similarly to the ones mentioned above about merging pull requests (i.e. typos, description clarification and the way script and build process changes are handled). Pull requests suggested by collaborators should be reviewed by at least two other collaborators to be considered ready to merge. 24 | - **Pull requests that are inactive for over a week should be closed or put on hold.** 25 | -------------------------------------------------------------------------------- /snippets/router-custom-preloading.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Router Custom Preloading 3 | author: maktarsis 4 | twitter: maktarsis 5 | level: advanced 6 | 7 | tags: 8 | - router 9 | links: 10 | - https://angular.io/api/router/PreloadingStrategy 11 | - https://vsavkin.com/angular-router-preloading-modules-ba3c75e424cb 12 | - https://medium.com/@adrianfaciu/custom-preloading-strategy-for-angular-modules-b3b5c873681a 13 | - https://coryrylan.com/blog/custom-preloading-and-lazy-loading-strategies-with-angular 14 | 15 | --- 16 | 17 | # Content 18 | Angular allows us to control the way module preloading is handled. 19 | 20 | There are 2 strategies provided by **@angular/router**: `PreloadAllModules` and `NoPreloading`. The latter enabled by default, only preloading lazy modules on demand. 21 | 22 | We can override this behavior by providing custom preloading strategy: In the example below we preload all included modules if the connection is good. 23 | 24 | ```typescript 25 | import { Observable, of } from 'rxjs'; 26 | 27 | export class CustomPreloading implements PreloadingStrategy { 28 | public preload(route: Route, load: () => Observable): Observable { 29 | return preloadingConnection() ? load() : of(null); 30 | } 31 | } 32 | 33 | const routing: ModuleWithProviders = RouterModule.forRoot(routes, { 34 | preloadingStrategy: CustomPreloading 35 | }); 36 | ``` 37 | > Note that that the example above would not be very efficient for larger apps, as it'll preload all the modules. 38 | 39 | # file:app.component.ts 40 | ```typescript 41 | import { Component } from '@angular/core'; 42 | @Component({ 43 | selector: 'my-app', 44 | template: `` 45 | }) 46 | export class AppComponent {} 47 | ``` 48 | 49 | # file:app.module.ts 50 | ```typescript 51 | import { BrowserModule } from '@angular/platform-browser'; 52 | import { NgModule, ModuleWithProviders } from '@angular/core'; 53 | import { PreloadingStrategy, Route, Routes, RouterModule } from '@angular/router'; 54 | import { Observable, of } from 'rxjs'; 55 | import { AppComponent } from './app.component'; 56 | 57 | function preloadingConnection(): boolean { 58 | const connection = navigator['connection']; 59 | if (connection) { 60 | const effectiveType = connection.effectiveType || ''; 61 | if (connection.saveData || effectiveType.includes('2g')) { 62 | return false; 63 | } 64 | } 65 | return true; 66 | } 67 | 68 | class CustomPreloading implements PreloadingStrategy { 69 | public preload(route: Route, load: () => Observable): Observable { 70 | return preloadingConnection() ? load() : of(null); 71 | } 72 | } 73 | 74 | const routes: Routes = [ 75 | { 76 | path: '', 77 | redirectTo: 'items', 78 | pathMatch: 'full' 79 | }, 80 | { 81 | path: 'items', 82 | loadChildren: 'app/items/items.module#ItemsModule' 83 | }, 84 | { 85 | path: 'item', 86 | loadChildren: 'app/details/details.module#DetailsModule' 87 | } 88 | ]; 89 | 90 | const routing: ModuleWithProviders = RouterModule.forRoot(routes, { 91 | preloadingStrategy: CustomPreloading 92 | }); 93 | 94 | @NgModule({ 95 | imports: [routing], 96 | exports: [RouterModule], 97 | providers: [CustomPreloading] 98 | }) 99 | class RoutingModule {} 100 | 101 | @NgModule({ 102 | imports: [BrowserModule, RoutingModule], 103 | declarations: [AppComponent], 104 | bootstrap: [AppComponent] 105 | }) 106 | export class AppModule {} 107 | ``` 108 | -------------------------------------------------------------------------------- /snippets/hammerjs-gestures.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: hammerjs-gestures 3 | level: beginner 4 | author: MichaelSolati 5 | twitter: MichaelSolati 6 | tags: 7 | - good-to-know 8 | - tips 9 | - components 10 | - gestures 11 | links: 12 | - https://github.com/angular/angular/blob/master/packages/platform-browser/src/dom/events/hammer_gestures.ts 13 | - http://hammerjs.github.io/api/#hammer.manager 14 | - https://angular.io/api/platform-browser/HammerGestureConfig 15 | --- 16 | 17 | # Content 18 | 19 | To act upon swipes, pans, and pinhces as well as the other mobile gestures, you can use `hammerjs` with `HostListener` decorator, or an event binding, 20 | 21 | ```bash 22 | npm install hammerjs 23 | ``` 24 | 25 | ```typescript 26 | @HostListener('swiperight') 27 | public swiperight(): void { 28 | // Run code when a user swipes to the right 29 | } 30 | ``` 31 | 32 | 33 | # Bonus 34 | 35 | Here are samples on how to use all of the `hammerjs` event bindings, you can use these events with a `HostListener` as well: 36 | 37 | ```HTML 38 | 39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | 49 | 50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | 58 | 59 |
60 |
61 | 62 | 63 |
64 |
65 |
66 |
67 |
68 | 69 | 70 |
71 |
72 |
73 |
74 |
75 | 76 | 77 |
78 | ``` 79 | 80 | # file:app.component.ts 81 | 82 | ```typescript 83 | // Using a HostListener 84 | import { Component, HostListener } from '@angular/core'; 85 | 86 | @Component({ 87 | selector: 'my-app', 88 | template: 'Please do not swipe right' 89 | }) 90 | export class AppComponent { 91 | @HostListener('swiperight') 92 | public swiperight(): void { 93 | // Run code when a user swipes to the right 94 | alert('STOP SWIPING TO THE RIGHT'); 95 | } 96 | } 97 | ``` 98 | 99 | # file:main.ts 100 | 101 | ```typescript 102 | import 'https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js'; 103 | import { enableProdMode } from '@angular/core'; 104 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 105 | 106 | import { AppModule } from './app/app.module'; 107 | import { environment } from './environments/environment'; 108 | 109 | if (environment.production) { 110 | enableProdMode(); 111 | } 112 | 113 | platformBrowserDynamic().bootstrapModule(AppModule) 114 | .catch(err => console.error(err)); 115 | 116 | ``` 117 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | 117 | 118 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Logo 30 Seconds of Angular](/builder/public/img/logo.png)](https://github.com/nycJSorg/30-seconds-of-angular) 2 | 3 | 4 | [![Build Status](https://travis-ci.com/30-seconds/30-seconds-of-angular.svg?branch=master)](https://travis-ci.com/nycJSorg/30-seconds-of-angular) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com) 5 | 6 | > Curated collection of useful Angular snippets that you can understand in 30 seconds or less. 7 | 8 | 9 | 10 | * Use Ctrl + F or command + F to search for a snippet. 11 | * Snippets are written in Angular 8+. 12 | 13 | # 30 Seconds of Angular 14 | 15 | ## Table of contents 16 | 17 | Beginner snippets 18 | 19 | * [Accessing Enums in template](#accessing-enums-in-template) 20 | * [Cheat Sheets and Checklists](#cheat-sheets-and-checklists) 21 | * [Component State Debugging](#component-state-debugging) 22 | * [Default ViewEncapsulation value](#default-viewencapsulation-value) 23 | * [hammerjs-gestures](#hammerjs-gestures) 24 | * [Loader Component](#loader-component) 25 | * [ng-content](#ng-content) 26 | * [ngIf else](#ngif-else) 27 | * [Optional parameters in the middle](#optional-parameters-in-the-middle) 28 | * [Renaming inputs and outputs](#renaming-inputs-and-outputs) 29 | * [Safe Navigation Operator](#safe-navigation-operator) 30 | * [trackBy in for loops](#trackby-in-for-loops) 31 | * [Understanding Microsyntax](#understanding-microsyntax) 32 | 33 | Intermediate snippets 34 | 35 | * [Accessing all nested form controls](#accessing-all-nested-form-controls) 36 | * [Adding keyboard shortcuts to elements](#adding-keyboard-shortcuts-to-elements) 37 | * [Bind to host properties with host binding](#bind-to-host-properties-with-host-binding) 38 | * [Component level providers](#component-level-providers) 39 | * [Global event listeners](#global-event-listeners) 40 | * [Injecting document](#injecting-document) 41 | * [Mark reactive fields as touched](#mark-reactive-fields-as-touched) 42 | * [Observables as outputs](#observables-as-outputs) 43 | * [Passing template as an input](#passing-template-as-an-input) 44 | * [Preseving whitespaces](#preseving-whitespaces) 45 | * [Reusing code in template](#reusing-code-in-template) 46 | * [Reusing existing custom pipes](#reusing-existing-custom-pipes) 47 | * [Style bindings](#style-bindings) 48 | * [Two-way binding any property](#two-way-binding-any-property) 49 | * [Using APP_INITIALIZER to delay app start](#using-app_initializer-to-delay-app-start) 50 | * [Window Location injection](#window-location-injection) 51 | 52 | Advanced snippets 53 | 54 | * [Getting components of different types with ViewChild](#getting-components-of-different-types-with-viewchild) 55 | * [Router Custom Preloading](#router-custom-preloading) 56 | * [SVG](#svg) 57 | 58 | 59 | 60 | ## Beginner snippets 61 | 62 | ### Accessing Enums in template 63 | Enums are great but they are not visible in Angular templates by default. 64 | With this little trick you can make them accessible. 65 | 66 | ```typescript 67 | enum Animals { 68 | DOG, 69 | CAT, 70 | DOLPHIN 71 | } 72 | 73 | @Component({ 74 | ... 75 | }) 76 | export class AppComponent { 77 | animalsEnum: typeof Animals = Animals; 78 | } 79 | ``` 80 | 81 | 82 | 83 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/accessing-enums-in-template) | [⬆ Back to top](#table-of-contents) | tags: [enums](https://30.codelab.fun/tags/enums) [templates](https://30.codelab.fun/tags/templates) 84 |

85 | ### Cheat Sheets and Checklists 86 | Check out [Angular Cheat Sheet](https://angular.io/guide/cheatsheet) or ([alternative version](https://malcoded.com/angular-cheat-sheet)) containing lots of useful information condensed in one place. 87 | 88 | Also [Angular Checklist](https://angular-checklist.io) contains is curated list of common mistakes made when developing Angular applications. 89 | 90 | 91 | #### Links 92 | https://malcoded.com/angular-cheat-sheet/,https://angular.io/guide/cheatsheet,https://angular.io/guide/styleguide 93 | 94 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/cheat-sheets-and-checklists) | [⬆ Back to top](#table-of-contents) | tags: [tip](https://30.codelab.fun/tags/tip) [cheat sheet](https://30.codelab.fun/tags/cheat-sheet) 95 |

96 | ### Component State Debugging 97 | Debug the component state in the browser console by running: 98 | ```typescript 99 | ng.probe($0).componentInstance 100 | ``` 101 | 102 | > `$0` - is the DOM node currently selected in dev tools (`$1` for the previous one and so on). 103 | 104 |
105 | Bonus 106 | 107 | With Ivy renderer engine: 108 | ```typescript 109 | ng.getComponent($0) 110 | ``` 111 |
112 | 113 | #### Links 114 | https://blog.angularindepth.com/everything-you-need-to-know-about-debugging-angular-applications-d308ed8a51b4 115 | 116 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/component-state-debugging) | [⬆ Back to top](#table-of-contents) | tags: [good-to-know](https://30.codelab.fun/tags/good-to-know) [tips](https://30.codelab.fun/tags/tips) 117 |

118 | ### Default ViewEncapsulation value 119 | If you're using `ViewEncapsulation` value which is different than default, it might be daunting to set the value manually for every component. 120 | 121 | Luckily you can configure it globally when bootstrapping your app: 122 | 123 | ```TypeScript 124 | platformBrowserDynamic().bootstrapModule(AppModule, [ 125 | { 126 | // NOTE: Use ViewEncapsulation.None only if you know what you're doing. 127 | defaultEncapsulation: ViewEncapsulation.None 128 | } 129 | ]); 130 | ``` 131 | 132 | 133 | 134 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/default-viewencapsulation-value) | [⬆ Back to top](#table-of-contents) | tags: [configuration](https://30.codelab.fun/tags/configuration) [styling](https://30.codelab.fun/tags/styling) 135 |

136 | ### hammerjs-gestures 137 | To act upon swipes, pans, and pinhces as well as the other mobile gestures, you can use `hammerjs` with `HostListener` decorator, or an event binding, 138 | 139 | ```bash 140 | npm install hammerjs 141 | ``` 142 | 143 | ```typescript 144 | @HostListener('swiperight') 145 | public swiperight(): void { 146 | // Run code when a user swipes to the right 147 | } 148 | ``` 149 | 150 |
151 | Bonus 152 | 153 | Here are samples on how to use all of the `hammerjs` event bindings, you can use these events with a `HostListener` as well: 154 | 155 | ```HTML 156 | 157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 | 167 | 168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 | 176 | 177 |
178 |
179 | 180 | 181 |
182 |
183 |
184 |
185 |
186 | 187 | 188 |
189 |
190 |
191 |
192 |
193 | 194 | 195 |
196 | ``` 197 |
198 | 199 | #### Links 200 | https://github.com/angular/angular/blob/master/packages/platform-browser/src/dom/events/hammer_gestures.ts,http://hammerjs.github.io/api/#hammer.manager,https://angular.io/api/platform-browser/HammerGestureConfig 201 | 202 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/hammerjs-gestures) | [⬆ Back to top](#table-of-contents) | tags: [good-to-know](https://30.codelab.fun/tags/good-to-know) [tips](https://30.codelab.fun/tags/tips) [components](https://30.codelab.fun/tags/components) [gestures](https://30.codelab.fun/tags/gestures) 203 |

204 | ### Loader Component 205 | You can create own helper component and use it instead of `*ngIf`. 206 | 207 | ```typescript 208 | @Component({ 209 | selector: 'loader', 210 | template: ` 211 | 212 | 🕚 Wait 10 seconds! 213 | ` 214 | }) 215 | class LoaderComponent { 216 | @Input() loading: boolean; 217 | } 218 | ``` 219 | 220 | For usage example: 221 | ```html 222 | 🦊 🦄 🐉 223 | ``` 224 | 225 | > Note that the content will be eagerly evaluated, e.g. in the snippet below `destroy-the-world` will be created before the loading even starts: 226 | 227 | ```html 228 | 229 | ``` 230 | 231 | 232 | #### Links 233 | https://medium.com/claritydesignsystem/ng-content-the-hidden-docs-96a29d70d11b,https://blog.angularindepth.com/https-medium-com-thomasburleson-animated-ghosts-bfc045a51fba 234 | 235 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/loader-component) | [⬆ Back to top](#table-of-contents) | tags: [tips](https://30.codelab.fun/tags/tips) [good-to-know](https://30.codelab.fun/tags/good-to-know) [components](https://30.codelab.fun/tags/components) [templates](https://30.codelab.fun/tags/templates) 236 |

237 | ### ng-content 238 | With `ng-content` you can pass any elements to a component. 239 | This simplifies creating reusable components. 240 | 241 | ```typescript 242 | @Component({ 243 | selector: 'wrapper', 244 | template: ` 245 |
246 | 247 |
248 | `, 249 | }) 250 | export class Wrapper {} 251 | ``` 252 | 253 | ```html 254 | 255 |

Hello World!

256 |
257 | ``` 258 | 259 | 260 | #### Links 261 | https://medium.com/p/96a29d70d11b 262 | 263 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/ng-content) | [⬆ Back to top](#table-of-contents) | tags: [good-to-know](https://30.codelab.fun/tags/good-to-know) [tips](https://30.codelab.fun/tags/tips) [components](https://30.codelab.fun/tags/components) 264 |

265 | ### ngIf else 266 | `*ngIf` directive also supports `else` statement. 267 | 268 | ```html 269 |
loading...
270 | 271 | not loading 272 | ``` 273 | 274 | 275 | 276 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/ngif-else) | [⬆ Back to top](#table-of-contents) | tags: [ngif](https://30.codelab.fun/tags/ngif) [templates](https://30.codelab.fun/tags/templates) 277 |

278 | ### Optional parameters in the middle 279 | Navigate with matrix params: 280 | 281 | the router will navigate to `/first;name=foo/details` 282 | ```html 283 | 284 | link with params 285 | 286 | ``` 287 | 288 | 289 | #### Links 290 | https://stackblitz.com/edit/angular-xvy5pd 291 | 292 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/optional-parameters-in-the-middle) | [⬆ Back to top](#table-of-contents) | tags: [routing](https://30.codelab.fun/tags/routing) 293 |

294 | ### Renaming inputs and outputs 295 | In certain cases `@Input` and `@Output` properties can be named differently than the actual inputs and outputs. 296 | 297 | ```html 298 |
302 |
303 | ``` 304 | 305 | ```typescript 306 | @Directive({ selector: '[pagination]'}) 307 | class PaginationComponent { 308 | @Input('paginationShowFirst') 309 | showFirst: boolean = true; 310 | 311 | @Output('paginationPageChanged') 312 | pageChanged = new EventEmitter(); 313 | } 314 | ``` 315 | > Note: Use this wisely, see [StyleGuide recommedation](https://angular.io/guide/styleguide#style-05-13) 316 | 317 | 318 | #### Links 319 | https://angular.io/guide/styleguide#style-05-13 320 | 321 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/renaming-inputs-and-outputs) | [⬆ Back to top](#table-of-contents) | tags: [components](https://30.codelab.fun/tags/components) [templates](https://30.codelab.fun/tags/templates) 322 |

323 | ### Safe Navigation Operator 324 | The [Safe Navigation Operator](https://angular.io/guide/template-syntax#the-safe-navigation-operator----and-null-property-paths) helps with preventing null-reference exceptions in component template expressions. It returns object property value if it exists or null otherwise. 325 | 326 | ```html 327 |

I will work even if student is null or undefined: {{student?.name}}

328 | ``` 329 | 330 |
331 | Bonus 332 | 333 | ```html 334 | {{a?.b?.c}} 335 | ``` 336 | Underneath will be compiled to. 337 | ```html 338 | (_co.a == null)? null: ((_co.a.b == null)? null: _co.a.b.c)); 339 | ``` 340 |
341 | 342 | #### Links 343 | https://github.com/angular/angular/issues/791 344 | 345 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/safe-navigation-operator) | [⬆ Back to top](#table-of-contents) | tags: [object property handling](https://30.codelab.fun/tags/object-property-handling) [tips](https://30.codelab.fun/tags/tips) [good to know](https://30.codelab.fun/tags/good-to-know) 346 |

347 | ### trackBy in for loops 348 | To avoid the expensive operations, we can help Angular to track which items added or removed i.e. customize the default tracking algorithm by providing a trackBy option to NgForOf. 349 | 350 | So you can provide your custom trackBy function that will return unique identifier for each iterated item. 351 | For example, some key value of the item. If this key value matches the previous one, then Angular won't detect changes. 352 | 353 | **trackBy** takes a function that has _index_ and _item_ args. 354 | 355 | ```typescript 356 | @Component({ 357 | selector: 'my-app', 358 | template: ` 359 |
    360 |
  • {{item.id}}
  • 361 |
362 | ` 363 | }) 364 | export class AppComponent { 365 | trackByFn(index, item) { 366 | return item.id; 367 | } 368 | } 369 | ``` 370 | If trackBy is given, Angular tracks changes by the return value of the function. 371 | 372 | Now when you change the collection, Angular can track which items have been added or removed according to the unique identifier and create/destroy only changed items. 373 | 374 | 375 | #### Links 376 | https://angular.io/api/common/NgForOf,https://angular.io/api/core/TrackByFunction 377 | 378 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/trackby-in-for-loops) | [⬆ Back to top](#table-of-contents) | tags: [good-to-know](https://30.codelab.fun/tags/good-to-know) [tips](https://30.codelab.fun/tags/tips) [components](https://30.codelab.fun/tags/components) [performance](https://30.codelab.fun/tags/performance) 379 |

380 | ### Understanding Microsyntax 381 | Under the hood Angular compiles structural directives into ng-template elements, e.g.: 382 | 383 | ```html 384 | 385 |
386 | 387 | 388 | 389 | ``` 390 | 391 | The value passed to *ngFor directive is written using microsyntax. You can learn about it [in the docs](https://angular.io/guide/structural-directives#microsyntax). 392 | 393 | Also check out an [interactive tool](https://alexzuza.github.io/ng-structural-directive-expander/) that shows the expansion by [Alexey Zuev](https://twitter.com/yurzui) 394 | 395 | 396 | #### Links 397 | https://angular.io/guide/structural-directives#microsyntax,https://alexzuza.github.io/ng-structural-directive-expander/,https://angular.io/guide/structural-directives#inside-ngfor 398 | 399 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/understanding-microsyntax) | [⬆ Back to top](#table-of-contents) | tags: [tip](https://30.codelab.fun/tags/tip) [structural directive](https://30.codelab.fun/tags/structural-directive) [microsyntax](https://30.codelab.fun/tags/microsyntax) 400 |

401 | 402 | ## Intermediate snippets 403 | 404 | ### Accessing all nested form controls 405 | Sometimes we need to work with every single Control is a form. Here's how it can be done: 406 | 407 | ```typescript 408 | function flattenControls(form: AbstractControl): AbstractControl[] { 409 | let extracted: AbstractControl[] = [ form ]; 410 | if (form instanceof FormArray || form instanceof FormGroup) { 411 | const children = Object.values(form.controls).map(flattenControls); 412 | extracted = extracted.concat(...children); 413 | } 414 | return extracted; 415 | } 416 | ``` 417 | 418 | For examples use: 419 | ```typescript 420 | // returns all dirty abstract controls 421 | flattenControls(form).filter((control) => control.dirty); 422 | 423 | // mark all controls as touched 424 | flattenControls(form).forEach((control) => 425 | control.markAsTouched({ onlySelf: true })); 426 | ``` 427 | 428 | 429 | #### Links 430 | https://angular.io/guide/reactive-forms 431 | 432 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/accessing-all-nested-form-controls) | [⬆ Back to top](#table-of-contents) | tags: [reactive forms](https://30.codelab.fun/tags/reactive-forms) [tips](https://30.codelab.fun/tags/tips) [good to know](https://30.codelab.fun/tags/good-to-know) 433 |

434 | ### Adding keyboard shortcuts to elements 435 | It's really easy to add keyboard shortcuts in the template: 436 | ```html 437 | 438 | ``` 439 | 440 |
441 | Bonus 442 | 443 | ```html 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | ``` 459 |
460 | 461 | #### Links 462 | https://alligator.io/angular/binding-keyup-keydown-events 463 | 464 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/adding-keyboard-shortcuts-to-elements) | [⬆ Back to top](#table-of-contents) | tags: [tips](https://30.codelab.fun/tags/tips) [good-to-know](https://30.codelab.fun/tags/good-to-know) 465 |

466 | ### Bind to host properties with host binding 467 | Every rendered angular component is wrapped in a host element (which is the same as component's selector). 468 | 469 | It is possible to bind properties and attributes of host element using @HostBinding decorators, e.g. 470 | 471 | ```typescript 472 | import { Component, HostBinding } from '@angular/core'; 473 | 474 | @Component({ 475 | selector: 'my-app', 476 | template: ` 477 |
Use the input below to select host background-color:
478 | 479 | `, 480 | styles: [ 481 | `:host { display: block; height: 100px; }` 482 | ] 483 | }) 484 | export class AppComponent { 485 | @HostBinding('style.background') color = '#ff9900'; 486 | } 487 | ``` 488 | 489 | 490 | 491 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/bind-to-host-properties-with-host-binding) | [⬆ Back to top](#table-of-contents) | tags: [components](https://30.codelab.fun/tags/components) 492 |

493 | ### Component level providers 494 | Generally we get one service instance per the whole application. 495 | It is also possible to create an instance of service per component or directive. 496 | 497 | ```typescript 498 | @Component({ 499 | selector: 'provide', 500 | template: '', 501 | providers: [ Service ] 502 | }) 503 | export class ProvideComponent {} 504 | ``` 505 | 506 | ```typescript 507 | @Directive({ 508 | selector: '[provide]', 509 | providers: [ Service ] 510 | }) 511 | export class ProvideDirective {} 512 | ``` 513 | 514 | 515 | #### Links 516 | https://angular.io/guide/hierarchical-dependency-injection#component-level-injectors,https://stackblitz.com/edit/angular-cdk-happy-animals 517 | 518 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/component-level-providers) | [⬆ Back to top](#table-of-contents) | tags: [tips](https://30.codelab.fun/tags/tips) [components](https://30.codelab.fun/tags/components) [dependency-injection](https://30.codelab.fun/tags/dependency-injection) 519 |

520 | ### Global event listeners 521 | It is possible to add global event listeners in your Components/Directives with `HostListener`. Angular will take care of unsubscribing once your directive is destroyed. 522 | 523 | ```typescript 524 | @Directive({ 525 | selector: '[rightClicker]' 526 | }) 527 | export class ShortcutsDirective { 528 | @HostListener('window:keydown.ArrowRight') 529 | doImportantThings() { 530 | console.log('You pressed right'); 531 | } 532 | } 533 | ``` 534 | 535 |
536 | Bonus 537 | 538 | You can have multiple bindings: 539 | 540 | ```typescript 541 | @HostListener('window:keydown.ArrowRight') 542 | @HostListener('window:keydown.PageDown') 543 | next() { 544 | console.log('Next') 545 | } 546 | ``` 547 | 548 | You can also pass params: 549 | 550 | ```typescript 551 | @HostListener('window:keydown.ArrowRight', '$event.target') 552 | next(target) { 553 | console.log('Pressed right on this element: ' + target) 554 | } 555 | ``` 556 |
557 | 558 | 559 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/global-event-listeners) | [⬆ Back to top](#table-of-contents) | tags: [events](https://30.codelab.fun/tags/events) [components](https://30.codelab.fun/tags/components) 560 |

561 | ### Injecting document 562 | Sometimes you need to get access to global `document`. 563 | 564 | To simplify unit-testing, Angular provides it through dependency injection: 565 | 566 | ```typescript 567 | import { DOCUMENT } from '@angular/common'; 568 | import { Inject } from '@angular/core'; 569 | 570 | @Component({ 571 | selector: 'my-app', 572 | template: `

Edit me

` 573 | }) 574 | export class AppComponent { 575 | 576 | constructor(@Inject(DOCUMENT) private document: Document) { 577 | // Word with document.location, or other things here.... 578 | } 579 | } 580 | ``` 581 | 582 | 583 | #### Links 584 | https://angular.io/api/common/DOCUMENT 585 | 586 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/injecting-document) | [⬆ Back to top](#table-of-contents) | tags: [dependency injection](https://30.codelab.fun/tags/dependency-injection) 587 |

588 | ### Mark reactive fields as touched 589 | Here is the way to notify user that there are fields with non-valid values. 590 | 591 | `markFieldsAsTouched` function FormGroup or FormArray as an argument. 592 | 593 | ```typescript 594 | function markFieldsAsTouched(form: AbstractControl): void { 595 | form.markAsTouched({ onlySelf: true }); 596 | if (form instanceof FormArray || form instanceof FormGroup) { 597 | Object.values(form.controls).forEach(markFieldsAsTouched); 598 | } 599 | } 600 | ``` 601 | 602 |
603 | Bonus 604 | 605 | It's very useful to check out more general method [Accessing all nested form controls](#accessing-all-nested-form-controls) by [Thekiba](https://twitter.com/thekiba_io) to work with controls. 606 |
607 | 608 | #### Links 609 | https://angular.io/guide/reactive-forms 610 | 611 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/mark-reactive-fields-as-touched) | [⬆ Back to top](#table-of-contents) | tags: [reactive forms validation](https://30.codelab.fun/tags/reactive-forms-validation) [tips](https://30.codelab.fun/tags/tips) [good to know](https://30.codelab.fun/tags/good-to-know) 612 |

613 | ### Observables as outputs 614 | `EventEmitters` used for `@Output`'s are just Observables with an emit method. 615 | 616 | This means that you can just use `Observable` instance instead, e.g. we can wire up FormControl value changes directly: 617 | 618 | ```TypeScript 619 | readonly checkbox = new FormControl(); 620 | @Output() readonly change = this.checkbox.valueChanges; 621 | ``` 622 | 623 | 624 | 625 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/observables-as-outputs) | [⬆ Back to top](#table-of-contents) | tags: [tip](https://30.codelab.fun/tags/tip) [outputs](https://30.codelab.fun/tags/outputs) 626 |

627 | ### Passing template as an input 628 | It's possible to take a template as `@Input` for a component to customize the render 629 | 630 | 631 | ```typescript 632 | @Component({ 633 | template: ` 634 | 637 | `, 638 | }) 639 | export class SiteMenuComponent { 640 | @Input() template: TemplateRef; 641 | } 642 | ``` 643 | ```html 644 | 645 | 646 | 647 | 648 | 649 | 650 | ``` 651 | > Note: `ng-content` should be used for most of the cases and it's simpler and more declarative. 652 | > Only use this approach if you need extra flexibility that can't be achieved with ng-content. 653 | 654 | 655 | #### Links 656 | https://blog.angular-university.io/angular-ng-template-ng-container-ngtemplateoutlet 657 | 658 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/passing-template-as-an-input) | [⬆ Back to top](#table-of-contents) | tags: [template](https://30.codelab.fun/tags/template) 659 |

660 | ### Preseving whitespaces 661 | By default Angular strips all whitespaces in templates to save bytes. Generally it's safe. 662 | 663 | For rare cases when you need to preserve spaces you can use special `ngPreserveWhitespaces` attribute: 664 | 665 | ```html 666 |
667 | (___()'`; 668 | /, /` 669 | jgs \\"--\\ 670 |
671 | ``` 672 | 673 | > You can also use [preserveWhitespaces](https://angular.io/api/core/Component#preserveWhitespaces) option on a component. 674 | 675 | 676 | #### Links 677 | https://twitter.com/mgechev/status/1108913389277839360 678 | 679 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/preseving-whitespaces) | [⬆ Back to top](#table-of-contents) | tags: [tip](https://30.codelab.fun/tags/tip) 680 |

681 | ### Reusing code in template 682 | While the best way of reusing your code is creating a component, it's also possible to do it in a template. 683 | 684 | To do this you can use `ng-template` along with `*ngTemplateOutlet` directive. 685 | 686 | ```html 687 |

688 | 689 |

690 | 691 | 694 | 695 | 696 | Hello {{name}}! 697 | 698 | ``` 699 | 700 | 701 | #### Links 702 | https://angular.io/api/common/NgTemplateOutlet,https://angular.io/guide/structural-directives#the-ng-template 703 | 704 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/reusing-code-in-template) | [⬆ Back to top](#table-of-contents) | tags: [templates](https://30.codelab.fun/tags/templates) 705 |

706 | ### Reusing existing custom pipes 707 | If you need a custom `pipe`, before creating one, consider checking out the [NGX Pipes package](https://github.com/danrevah/ngx-pipes) which has 70+ already implemeted custom pipes. 708 | 709 | Here are some examples: 710 | 711 | ```html 712 |

{{ date | timeAgo }}

713 | 714 | 715 |

{{ 'foo bar' | ucfirst }}

716 | 717 | 718 |

3 {{ 'Painting' | makePluralString: 3 }}

719 | 720 | 721 |

{{ [1, 2, 3, 1, 2, 3] | max }}

722 | 723 | ``` 724 | 725 | 726 | #### Links 727 | https://github.com/danrevah/ngx-pipes 728 | 729 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/reusing-existing-custom-pipes) | [⬆ Back to top](#table-of-contents) | tags: [tip](https://30.codelab.fun/tags/tip) [pipes](https://30.codelab.fun/tags/pipes) [library](https://30.codelab.fun/tags/library) 730 |

731 | ### Style bindings 732 | You can use advanced property bindings to set specific style values based on component property values: 733 | 734 | ```html 735 |

736 | I am in green background 737 |

738 | 739 |

740 | May be important text. 741 |

742 | ``` 743 | 744 |
745 | Bonus 746 | 747 | ```html 748 | 749 |
750 | 751 | 752 |
...
753 | 754 | 755 |
756 | ``` 757 |
758 | 759 | 760 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/style-bindings) | [⬆ Back to top](#table-of-contents) | tags: [styles](https://30.codelab.fun/tags/styles) 761 |

762 | ### Two-way binding any property 763 | Similar to how you can two-way bind `[(ngModel)]` you can two-way bind custom property on a component, for example `[(value)]`. To do it use appropriate Input/Output naming: 764 | 765 | ```typescript 766 | @Component({ 767 | selector: 'super-input', 768 | template: `...`, 769 | }) 770 | export class AppComponent { 771 | @Input() value: string; 772 | @Output() valueChange = new EventEmitter(); 773 | } 774 | ``` 775 | 776 | Then you can use it as: 777 | ```html 778 | 779 | ``` 780 | 781 | 782 | 783 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/two-way-binding-any-property) | [⬆ Back to top](#table-of-contents) | tags: [tip](https://30.codelab.fun/tags/tip) [binding](https://30.codelab.fun/tags/binding) 784 |

785 | ### Using APP_INITIALIZER to delay app start 786 | It is possible to execute asynchronous task before the app start by providing a function returning promise using `APP_INITIALIZER` token. 787 | 788 | ```typescript 789 | @NgModule({ 790 | providers: [ 791 | { 792 | provide: APP_INITIALIZER, 793 | useValue: functionReturningPromise 794 | multi: true 795 | }, 796 | }) 797 | export class AppModule {} 798 | 799 | 800 | ``` 801 | 802 | 803 | #### Links 804 | https://hackernoon.com/hook-into-angular-initialization-process-add41a6b7e,https://angular.io/api/core/APP_INITIALIZER 805 | 806 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/using-app_initializer-to-delay-app-start) | [⬆ Back to top](#table-of-contents) | tags: [tip](https://30.codelab.fun/tags/tip) 807 |

808 | ### Window Location injection 809 | For testing purposes you might want to inject `window.location` object in your component. 810 | You can achieve this with custom `InjectionToken` mechanism provided by Angular. 811 | 812 | ```typescript 813 | export const LOCATION_TOKEN = new InjectionToken('Window location object'); 814 | 815 | @NgModule({ 816 | providers: [ 817 | { provide: LOCATION_TOKEN, useValue: window.location } 818 | ] 819 | }) 820 | export class SharedModule {} 821 | 822 | //... 823 | 824 | @Component({ 825 | }) 826 | export class AppComponent { 827 | constructor( 828 | @Inject(LOCATION_TOKEN) public location: Location 829 | ) {} 830 | } 831 | ``` 832 | 833 | 834 | #### Links 835 | https://itnext.io/testing-browser-window-location-in-angular-application-e4e8388508ff,https://angular.io/guide/dependency-injection 836 | 837 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/window-location-injection) | [⬆ Back to top](#table-of-contents) | tags: [dependency-injection](https://30.codelab.fun/tags/dependency-injection) [testing](https://30.codelab.fun/tags/testing) 838 |

839 | 840 | ## Advanced snippets 841 | 842 | ### Getting components of different types with ViewChild 843 | It's possible to use `@ViewChild` (also `@ViewChildren` and `@ContentChild/Children`) to query for components of different types using dependency injection. 844 | 845 | In the example below we can use `@ViewChildren(Base)` to get instances of `Foo` and `Bar`. 846 | 847 | ```typescript 848 | abstract class Base {} 849 | 850 | @Component({ 851 | selector: 'foo', 852 | providers: [{ provide: Base, useExisting: Foo }] 853 | }) 854 | class Foo extends Base {} 855 | 856 | @Component({ 857 | selector: 'bar', 858 | providers: [{ provide: Base, useExisting: Bar }] 859 | }) 860 | class Bar extends Base {} 861 | 862 | // Now we can require both types of components using Base. 863 | @Component({ template: `` }) 864 | class AppComponent { 865 | @ViewChildren(Base) components: QueryList; 866 | } 867 | ``` 868 | 869 | 870 | #### Links 871 | https://www.youtube.com/watch?v=PRRgo6F0cjs 872 | 873 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/getting-components-of-different-types-with-viewchild) | [⬆ Back to top](#table-of-contents) | tags: [good-to-know](https://30.codelab.fun/tags/good-to-know) [tips](https://30.codelab.fun/tags/tips) [components](https://30.codelab.fun/tags/components) [dependency-injection](https://30.codelab.fun/tags/dependency-injection) 874 |

875 | ### Router Custom Preloading 876 | Angular allows us to control the way module preloading is handled. 877 | 878 | There are 2 strategies provided by **@angular/router**: `PreloadAllModules` and `NoPreloading`. The latter enabled by default, only preloading lazy modules on demand. 879 | 880 | We can override this behavior by providing custom preloading strategy: In the example below we preload all included modules if the connection is good. 881 | 882 | ```typescript 883 | import { Observable, of } from 'rxjs'; 884 | 885 | export class CustomPreloading implements PreloadingStrategy { 886 | public preload(route: Route, load: () => Observable): Observable { 887 | return preloadingConnection() ? load() : of(null); 888 | } 889 | } 890 | 891 | const routing: ModuleWithProviders = RouterModule.forRoot(routes, { 892 | preloadingStrategy: CustomPreloading 893 | }); 894 | ``` 895 | > Note that that the example above would not be very efficient for larger apps, as it'll preload all the modules. 896 | 897 | 898 | #### Links 899 | https://angular.io/api/router/PreloadingStrategy,https://vsavkin.com/angular-router-preloading-modules-ba3c75e424cb,https://medium.com/@adrianfaciu/custom-preloading-strategy-for-angular-modules-b3b5c873681a,https://coryrylan.com/blog/custom-preloading-and-lazy-loading-strategies-with-angular 900 | 901 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/router-custom-preloading) | [⬆ Back to top](#table-of-contents) | tags: [router](https://30.codelab.fun/tags/router) 902 |

903 | ### SVG 904 | It is possible to use SVG tags in your Angular component, to create beautiful graphs and visualizations. There are 3 things you need to know: 905 | 906 | 1. When binding an SVG attribute, use `attr` 907 | ```html 908 | 909 | ``` 910 | 911 | 2. When creating sub-components, use attribute and not tag selector: 912 | ```html 913 | // Not: 914 | 915 | ``` 916 | ```typescript 917 | @Component({selector: '[child-component]' }) 918 | ``` 919 | 920 | 3. When using SVG tags in sub-components use svg prefix: 921 | ```typescript 922 | @Component({ 923 | selector: '[child-component]', 924 | template: `` 925 | }) 926 | ``` 927 | 928 | 929 | 930 |
[⭐ Interactive demo of this snippet](https://30.codelab.fun/svg) | [⬆ Back to top](#table-of-contents) | tags: [tip](https://30.codelab.fun/tags/tip) [SVG](https://30.codelab.fun/tags/svg) 931 |

932 | --------------------------------------------------------------------------------