来源 本文档直接取自GitHub30-seconds-of-angular 项目。
Table of contents Beginner snippets
Intermediate snippets
Advanced snippets
Beginner snippets Accessing Enums in template Enums are great but they are not visible in Angular templates by default. With this little trick you can make them accessible.
1 2 3 4 5 6 7 8 9 10 11 12 enum Animals { DOG, CAT, DOLPHIN } @Component ({ ... }) export class AppComponent { animalsEnum: typeof Animals = Animals; }
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: enums templates
Cheat Sheets and Checklists Check out Angular Cheat Sheet or (alternative version ) containing lots of useful information condensed in one place.
Also Angular Checklist contains is curated list of common mistakes made when developing Angular applications.
Links https://malcoded.com/angular-cheat-sheet/,https://angular.io/guide/cheatsheet,https://angular.io/guide/styleguide
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: tip cheat sheet
Component State Debugging Debug the component state in the browser console by running:
1 ng.probe($0 ).componentInstance
$0
- is the DOM node currently selected in dev tools ($1
for the previous one and so on).
Bonus
With Ivy renderer engine:
Links https://blog.angularindepth.com/everything-you-need-to-know-about-debugging-angular-applications-d308ed8a51b4
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: good-to-know tips
Default ViewEncapsulation value If you’re using ViewEncapsulation
value which is different than default, it might be daunting to set the value manually for every component.
Luckily you can configure it globally when bootstrapping your app:
1 2 3 4 5 6 platformBrowserDynamic().bootstrapModule(AppModule, [ { defaultEncapsulation: ViewEncapsulation.None } ]);
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: configuration styling
hammerjs-gestures 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,
1 2 3 4 @HostListener ('swiperight' )public swiperight(): void { }
Bonus
Here are samples on how to use all of the hammerjs
event bindings, you can use these events with a HostListener
as well:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <div (pan )="logEvent($event)" > </div > <div (panstart )="logEvent($event)" > </div > <div (panmove )="logEvent($event)" > </div > <div (panend )="logEvent($event)" > </div > <div (pancancel )="logEvent($event)" > </div > <div (panleft )="logEvent($event)" > </div > <div (panright )="logEvent($event)" > </div > <div (panup )="logEvent($event)" > </div > <div (pandown )="logEvent($event)" > </div > <div (pinch )="logEvent($event)" > </div > <div (pinchstart )="logEvent($event)" > </div > <div (pinchmove )="logEvent($event)" > </div > <div (pinchend )="logEvent($event)" > </div > <div (pinchcancel )="logEvent($event)" > </div > <div (pinchin )="logEvent($event)" > </div > <div (pinchout )="logEvent($event)" > </div > <div (press )="logEvent($event)" > </div > <div (pressup )="logEvent($event)" > </div > <div (rotate )="logEvent($event)" > </div > <div (rotatestart )="logEvent($event)" > </div > <div (rotatemove )="logEvent($event)" > </div > <div (rotateend )="logEvent($event)" > </div > <div (rotatecancel )="logEvent($event)" > </div > <div (swipe )="logEvent($event)" > </div > <div (swipeleft )="logEvent($event)" > </div > <div (swiperight )="logEvent($event)" > </div > <div (swipeup )="logEvent($event)" > </div > <div (swipedown )="logEvent($event)" > </div > <div (tap )="logEvent($event)" > </div >
Links 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
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: good-to-know tips components gestures
Loader Component You can create own helper component and use it instead of *ngIf
.
1 2 3 4 5 6 7 8 9 10 @Component ({ selector: 'loader' , template: ` <ng-content *ngIf="!loading else showLoader"></ng-content> <ng-template #showLoader>🕚 Wait 10 seconds!</ng-template> ` }) class LoaderComponent { @Input () loading: boolean ; }
For usage example:
1 <loader [loading ]="isLoading" > 🦊 🦄 🐉</loader >
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:
1 <loader [loading ]="isLoading" > <destroy-the-world > </destroy-the-world > </loader >
Links https://medium.com/claritydesignsystem/ng-content-the-hidden-docs-96a29d70d11b,https://blog.angularindepth.com/https-medium-com-thomasburleson-animated-ghosts-bfc045a51fba
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: tips good-to-know components templates
ng-content With ng-content
you can pass any elements to a component. This simplifies creating reusable components.
1 2 3 4 5 6 7 8 9 @Component ({ selector: 'wrapper' , template: ` <div class="wrapper"> <ng-content></ng-content> </div> ` ,}) export class Wrapper {}
1 2 3 <wrapper > <h1 > Hello World!</h1 > </wrapper >
Links https://medium.com/p/96a29d70d11b
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: good-to-know tips components
ngIf else *ngIf
directive also supports else
statement.
1 2 3 <div *ngIf ="isLoading; else notLoading" > loading...</div > <ng-template #notLoading > not loading</ng-template >
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: ngif templates
Optional parameters in the middle Navigate with matrix params:
the router will navigate to /first;name=foo/details
1 2 3 <a [routerLink ]="['/', 'first', {name: 'foo'}, 'details']" > link with params </a >
Links https://stackblitz.com/edit/angular-xvy5pd
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: routing
In certain cases @Input
and @Output
properties can be named differently than the actual inputs and outputs.
1 2 3 4 5 <div pagination paginationShowFirst ="true" (paginationPageChanged )="onPageChanged($event)" > </div >
1 2 3 4 5 6 7 8 @Directive ({ selector : '[pagination]' })class PaginationComponent { @Input ('paginationShowFirst' ) showFirst: boolean = true ; @Output ('paginationPageChanged' ) pageChanged = new EventEmitter(); }
Note: Use this wisely, see StyleGuide recommedation
Links https://angular.io/guide/styleguide#style-05-13
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: components templates
Safe Navigation Operator The Safe Navigation Operator helps with preventing null-reference exceptions in component template expressions. It returns object property value if it exists or null otherwise.
1 <p > I will work even if student is null or undefined: {{student?.name}} </p >
Bonus
Underneath will be compiled to.
1 (_co.a == null)? null: ((_co.a.b == null)? null: _co.a.b.c));
Links https://github.com/angular/angular/issues/791
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: object property handling tips good to know
trackBy in for loops 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.
So you can provide your custom trackBy function that will return unique identifier for each iterated item. For example, some key value of the item. If this key value matches the previous one, then Angular won’t detect changes.
trackBy takes a function that has index and item args.
1 2 3 4 5 6 7 8 9 10 11 12 13 @Component ({ selector: 'my-app' , template: ` <ul> <li *ngFor="let item of items; trackBy: trackByFn">{{item.id}}</li> </ul> ` }) export class AppComponent { trackByFn (index, item ) { return item.id; } }
If trackBy is given, Angular tracks changes by the return value of the function.
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.
Links https://angular.io/api/common/NgForOf,https://angular.io/api/core/TrackByFunction
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: good-to-know tips components performance
Understanding Microsyntax Under the hood Angular compiles structural directives into ng-template elements, e.g.:
1 2 3 4 5 <div *ngFor ="let item of [1,2,3]" > <ng-template ngFor [ngForOf ]="[1,2,3]" let-item ="$implicit" > </ng-template >
The value passed to *ngFor directive is written using microsyntax. You can learn about it in the docs .
Also check out an interactive tool that shows the expansion by Alexey Zuev
Links https://angular.io/guide/structural-directives#microsyntax,https://alexzuza.github.io/ng-structural-directive-expander/,https://angular.io/guide/structural-directives#inside-ngfor
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: tip structural directive microsyntax
Sometimes we need to work with every single Control is a form. Here’s how it can be done:
1 2 3 4 5 6 7 8 function flattenControls (form: AbstractControl ): AbstractControl [] { let extracted: AbstractControl[] = [ form ]; if (form instanceof FormArray || form instanceof FormGroup) { const children = Object .values(form.controls).map(flattenControls); extracted = extracted.concat(...children); } return extracted; }
For examples use:
1 2 3 4 5 6 flattenControls(form).filter((control ) => control.dirty); flattenControls(form).forEach((control ) => control.markAsTouched({ onlySelf : true }));
Links https://angular.io/guide/reactive-forms
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: reactive forms tips good to know
Adding keyboard shortcuts to elements It’s really easy to add keyboard shortcuts in the template:
1 <textarea (keydown.ctrl.enter )="doSomething()" > </textarea >
Bonus
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <input (keydown.enter )="..." > <input (keydown.a )="..." > <input (keydown.esc )="..." > <input (keydown.shift.esc )="..." > <input (keydown.control )="..." > <input (keydown.alt )="..." > <input (keydown.meta )="..." > <input (keydown.9 )="..." > <input (keydown.tab )="..." > <input (keydown.backspace )="..." > <input (keydown.arrowup )="..." > <input (keydown.shift.arrowdown )="..." > <input (keydown.shift.control.z )="..." > <input (keydown.f4 )="..." >
Links https://alligator.io/angular/binding-keyup-keydown-events
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: tips good-to-know
Bind to host properties with host binding Every rendered angular component is wrapped in a host element (which is the same as component’s selector).
It is possible to bind properties and attributes of host element using @HostBinding decorators, e.g.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import { Component, HostBinding } from '@angular/core' ;@Component ({ selector: 'my-app' , template: ` <div>Use the input below to select host background-color:</div> <input type="color" [(ngModel)]="color"> ` , styles: [ `:host { display: block; height: 100px; }` ] }) export class AppComponent { @HostBinding ('style.background' ) color = '#ff9900' ; }
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: components
Component level providers Generally we get one service instance per the whole application. It is also possible to create an instance of service per component or directive.
1 2 3 4 5 6 @Component ({ selector: 'provide' , template: '<ng-content></ng-content>' , providers: [ Service ] }) export class ProvideComponent {}
1 2 3 4 5 @Directive ({ selector: '[provide]' , providers: [ Service ] }) export class ProvideDirective {}
Links https://angular.io/guide/hierarchical-dependency-injection#component-level-injectors,https://stackblitz.com/edit/angular-cdk-happy-animals
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: tips components dependency-injection
Global event listeners 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.
1 2 3 4 5 6 7 8 9 @Directive ({ selector: '[rightClicker]' }) export class ShortcutsDirective { @HostListener ('window:keydown.ArrowRight' ) doImportantThings ( ) { console .log('You pressed right' ); } }
Bonus
You can have multiple bindings:
1 2 3 4 5 @HostListener ('window:keydown.ArrowRight' )@HostListener ('window:keydown.PageDown' )next ( ) { console .log('Next' ) }
You can also pass params:
1 2 3 4 @HostListener ('window:keydown.ArrowRight' , '$event.target' )next (target ) { console .log('Pressed right on this element: ' + target) }
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: events components
Injecting document Sometimes you need to get access to global document
.
To simplify unit-testing, Angular provides it through dependency injection:
1 2 3 4 5 6 7 8 9 10 11 12 13 import { DOCUMENT } from '@angular/common' ;import { Inject } from '@angular/core' ;@Component ({ selector: 'my-app' , template: `<h1>Edit me </h1>` }) export class AppComponent { constructor (@Inject (DOCUMENT) private document : Document ) { } }
Links https://angular.io/api/common/DOCUMENT
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: dependency injection
Mark reactive fields as touched Here is the way to notify user that there are fields with non-valid values.
markFieldsAsTouched
function FormGroup or FormArray as an argument.
1 2 3 4 5 6 function markFieldsAsTouched (form: AbstractControl ): void { form.markAsTouched({ onlySelf : true }); if (form instanceof FormArray || form instanceof FormGroup) { Object .values(form.controls).forEach(markFieldsAsTouched); } }
Bonus
It’s very useful to check out more general method Accessing all nested form controls by Thekiba to work with controls.
Links https://angular.io/guide/reactive-forms
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: reactive forms validation tips good to know
Observables as outputs EventEmitters
used for @Output
‘s are just Observables with an emit method.
This means that you can just use Observable
instance instead, e.g. we can wire up FormControl value changes directly:
1 2 readonly checkbox = new FormControl();@Output () readonly change = this .checkbox.valueChanges;
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: tip outputs
It’s possible to take a template as @Input
for a component to customize the render
1 2 3 4 5 6 7 8 9 10 @Component ({ template: ` <nav> <ng-container *ngTemplateOutlet="template"></ng-container> </nav> ` ,}) export class SiteMenuComponent { @Input () template: TemplateRef<any >; }
1 2 3 4 5 6 <site-menu [template ]="menu1" > </site-menu > <ng-template #menu1 > <div > <a href ="#" > item1</a > </div > <div > <a href ="#" > item2</a > </div > </ng-template >
Note: ng-content
should be used for most of the cases and it’s simpler and more declarative. Only use this approach if you need extra flexibility that can’t be achieved with ng-content.
Links https://blog.angular-university.io/angular-ng-template-ng-container-ngtemplateoutlet
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: template
Preseving whitespaces By default Angular strips all whitespaces in templates to save bytes. Generally it’s safe.
For rare cases when you need to preserve spaces you can use special ngPreserveWhitespaces
attribute:
1 2 3 4 5 <div ngPreserveWhitespaces > (___()'`; /, /` jgs \\"--\\ </div >
You can also use preserveWhitespaces option on a component.
Links https://twitter.com/mgechev/status/1108913389277839360
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: tip
Reusing code in template While the best way of reusing your code is creating a component, it’s also possible to do it in a template.
To do this you can use ng-template
along with *ngTemplateOutlet
directive.
1 2 3 4 5 6 7 8 9 10 11 <p > <ng-container *ngTemplateOutlet ="fancyGreeting" > </ng-container > </p > <button > <ng-container *ngTemplateOutlet ="fancyGreeting" > </ng-container > </button > <ng-template #fancyGreeting > Hello <b > {{name}}!</b > </ng-template >
Links https://angular.io/api/common/NgTemplateOutlet,https://angular.io/guide/structural-directives#the-ng-template
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: templates
Reusing existing custom pipes If you need a custom pipe
, before creating one, consider checking out the NGX Pipes package which has 70+ already implemeted custom pipes.
Here are some examples:
1 2 3 4 5 6 7 8 9 10 11 <p > {{ date | timeAgo }}</p > <p > {{ 'foo bar' | ucfirst }}</p > <p > 3 {{ 'Painting' | makePluralString: 3 }}</p > <p > {{ [1, 2, 3, 1, 2, 3] | max }}</p >
Links https://github.com/danrevah/ngx-pipes
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: tip pipes library
Style bindings You can use advanced property bindings to set specific style values based on component property values:
1 2 3 4 5 6 7 <p [style.background-color ]="'green'" > I am in green background </p > <p [style.font-size.px ]="isImportant ? '30' : '16'" > May be important text. </p >
Bonus
1 2 3 4 5 6 7 8 <div [style.width.px ]="pxWidth" > </div > <div [style.font-size. %]="percentageSize" > ...</div > <div [style.height.vh ]="vwHeight" > </div >
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: styles
Two-way binding any property 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:
1 2 3 4 5 6 7 8 @Component ({ selector: 'super-input' , template: `...` , }) export class AppComponent { @Input () value: string ; @Output () valueChange = new EventEmitter<string >(); }
Then you can use it as:
1 <super-input [(value )]="value" > </super-input >
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: tip binding
Using APP_INITIALIZER to delay app start It is possible to execute asynchronous task before the app start by providing a function returning promise using APP_INITIALIZER
token.
1 2 3 4 5 6 7 8 9 10 11 @NgModule ({ providers: [ { provide: APP_INITIALIZER, useValue: functionReturningPromise multi: true }, }) export class AppModule {}
Links https://hackernoon.com/hook-into-angular-initialization-process-add41a6b7e,https://angular.io/api/core/APP_INITIALIZER
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: tip
Window Location injection For testing purposes you might want to inject window.location
object in your component. You can achieve this with custom InjectionToken
mechanism provided by Angular.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 export const LOCATION_TOKEN = new InjectionToken<Location>('Window location object' );@NgModule ({ providers: [ { provide : LOCATION_TOKEN, useValue : window .location } ] }) export class SharedModule {}@Component ({}) export class AppComponent { constructor ( @Inject (LOCATION_TOKEN) public location: Location ) {}}
Links https://itnext.io/testing-browser-window-location-in-angular-application-e4e8388508ff,https://angular.io/guide/dependency-injection
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: dependency-injection testing
Advanced snippets Getting components of different types with ViewChild It’s possible to use @ViewChild
(also @ViewChildren
and @ContentChild/Children
) to query for components of different types using dependency injection.
In the example below we can use @ViewChildren(Base)
to get instances of Foo
and Bar
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 abstract class Base {}@Component ({ selector: 'foo' , providers: [{ provide : Base, useExisting : Foo }] }) class Foo extends Base {}@Component ({ selector: 'bar' , providers: [{ provide : Base, useExisting : Bar }] }) class Bar extends Base {}@Component ({ template : `<foo></foo><bar></bar>` })class AppComponent { @ViewChildren (Base) components: QueryList<Base>; }
Links https://www.youtube.com/watch?v=PRRgo6F0cjs
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: good-to-know tips components dependency-injection
Router Custom Preloading Angular allows us to control the way module preloading is handled.
There are 2 strategies provided by @angular/router : PreloadAllModules
and NoPreloading
. The latter enabled by default, only preloading lazy modules on demand.
We can override this behavior by providing custom preloading strategy: In the example below we preload all included modules if the connection is good.
1 2 3 4 5 6 7 8 9 10 11 import { Observable, of } from 'rxjs' ;export class CustomPreloading implements PreloadingStrategy { public preload(route: Route, load : () => Observable<any >): Observable<any > { return preloadingConnection() ? load() : of (null ); } } const routing: ModuleWithProviders = RouterModule.forRoot(routes, { preloadingStrategy: CustomPreloading });
Note that that the example above would not be very efficient for larger apps, as it’ll preload all the modules.
Links 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
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: router
SVG 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:
When binding an SVG attribute, use attr
1 <circle [attr.cx ]="x" [attr.cy ]="y" > </circle >
When creating sub-components, use attribute and not tag selector:1 2 // Not: <child-component > </child-component > <g child-component > </g >
1 @Component ({selector : '[child-component]' })
When using SVG tags in sub-components use svg prefix:1 2 3 4 @Component ({ selector: '[child-component]' , template: `<svg:circle></svg:circle>` })
⭐ Interactive demo of this snippet | ⬆ Back to top | tags: tip SVG