- The Angular CLI is a command-line interface tool that you use to initialize, develop, scaffold, and maintain Angular applications directly from a command shell.
- Install the CLI using the npm package manager:
npm install -g @angular/cli
-
Invoke the tool on the command line through the
ng
executable. Online help is available on the command line. Enter the following to list commands or options for a given command (such as generate) with a short description.ng help ng generate --help
-
To create, build, and serve a new, basic Angular project on a development server, go to the parent directory of your new workspace use the following commands:
ng new my-first-project cd my-first-project ng serve
-
The ng new command creates an Angular workspace folder and generates a new application skeleton. A workspace can contain multiple applications and libraries. The initial application created by the ng new command is at the top level of the workspace. When you generate an additional application or library in a workspace, it goes into a projects/ subfolder.
-
A newly generated application contains the source files for a root module, with a root component and template. Each application has a src folder that contains the logic, data, and assets.
-
You can edit the generated files directly, or add to and modify them using CLI commands. Use the ng generate command to add new files for additional components and services, and code for new pipes, directives, and so on. Commands such as add and generate, which create or operate on applications and libraries, must be executed from within a workspace or project folder.
-
A single workspace configuration file,
angular.json
, is created at the top level of the workspace. This is where you can set per-project defaults for CLI command options, and specify configurations to use when the CLI builds a project for different targets. -
The
ng config
command lets you set and retrieve configuration values from the command line, or you can edit theangular.json
file directly. -
NOTE:
- Option names in the configuration file must use
camelCase
, while option names supplied to commands must be dash-case.
- Option names in the configuration file must use
-
Command syntax is shown as follows:
ng [optional-arg] [options]
-
Most commands, and some options, have aliases. Aliases are shown in the syntax statement for each command.
-
Option names are prefixed with a double dash (
--
) characters. Option aliases are prefixed with a single dash (-
) character. Arguments are not prefixed. For example:ng build my-app -c production
-
Typically, the name of a generated artifact can be given as an argument to the command or specified with the
--name
option. -
Arguments and option names must be given in
dash-case
. For example:--my-option-name
-
- Boolean options have two forms:
--this-option
sets the flag totrue
,--no-this-option
sets it tofalse
. If neither option is supplied, the flag remains in its default state, as listed in the reference documentation.
- Array options can be provided in two forms:
--option value1 value2
or--option value1 --option value2
.
- Options that specify files can be given as absolute paths, or as paths relative to the current working directory, which is generally either the workspace or project root.
- The
ng generate
andng add
commands take, as an argument, the artifact or library to be generated or added to the current project. In addition to any general options, each artifact or library defines its own options in a schematic. Schematic options are supplied to the command in the same format as immediate command options.
COMMAND | ALIAS | DESCRIPTION |
---|---|---|
add | Adds support for an external library to your project. | |
analytics | Configures the gathering of Angular CLI usage metrics. See https://angular.io/cli/usage-analytics-gathering | |
build | b | Compiles an Angular application or library into an output directory named dist/ at the given output path. |
cache | Configure persistent disk cache and retrieve cache statistics. | |
completion | Set up Angular CLI autocompletion for your terminal. | |
config | Retrieves or sets Angular configuration values in the angular.json file for the workspace. |
|
deploy | Invokes the deploy builder for a specified project or for the default project in the workspace. | |
doc | d | Opens the official Angular documentation (angular.io) in a browser, and searches for a given keyword. |
e2e | e | Builds and serves an Angular application, then runs end-to-end tests. |
extract-i18n | Extracts i18n messages from source code. | |
generate | g | Generates and/or modifies files based on a schematic. |
lint | Runs linting tools on Angular application code in a given project folder. | |
new | n | Creates a new Angular workspace. |
run | Runs an Architect target with an optional custom builder configuration defined in your project. | |
serve | s | Builds and serves your application, rebuilding on file changes. |
test | t | Runs unit tests in a project. |
update | Updates your workspace and its dependencies. See https://update.angular.io/. | |
version | v | Outputs Angular CLI version. |
-
Formats a value according to digit options and locale rules. Locale determines group sizing and separator, decimal point character, and other locale-specific configurations.
{{ value_expression | number [ : digitsInfo [ : locale ] ] }}
- CommonModule
- value
- string | number
- The value to be formatted.
-
digitsInfo
- string
- Sets digit and decimal representation. See more.
- Optional. Default is undefined.
-
locale
- string
- Specifies what locale format rules to use. See more.
- Optional. Default is undefined.
-
The value's decimal representation is specified by the digitsInfo parameter, written in the following format:
{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}
-
minIntegerDigits
: The minimum number of integer digits before the decimal point. Default is 1. -
minFractionDigits
: The minimum number of digits after the decimal point. Default is 0. -
maxFractionDigits
: The maximum number of digits after the decimal point. Default is 3.
-
-
If the formatted value is truncated it will be rounded using the "to-nearest" method:
{{3.6 | number: '1.0-0'}} <!--will output '4'--> {{-3.6 | number:'1.0-0'}} <!--will output '-4'-->
locale
will format a value according to locale rules. Locale determines group sizing and separator, decimal point character, and other locale-specific configurations.
When not supplied, uses the value of LOCALE_ID, which is en-US
by default.
The following code shows how the pipe transforms values according to various format specifications, where the caller's default locale is en-US
.
@Component({
selector: 'number-pipe',
template: `<div>
<p>
No specified formatting:
{{pi | number}}
<!--output: '3.142'-->
</p>
<p>
With digitsInfo parameter specified:
{{pi | number:'4.1-5'}}
<!--output: '0,003.14159'-->
</p>
<p>
With digitsInfo and
locale parameters specified:
{{pi | number:'4.1-5':'fr'}}
<!--output: '0 003,14159'-->
</p>
</div>`
})
export class NumberPipeComponent {
pi: number = 3.14159265359;
}
-
Transforms a number to a currency string, formatted according to locale rules that determine group sizing and separator, decimal-point character, and other locale-specific configurations.
{{ value_expression | currency [ : currencyCode [ : display [ : digitsInfo [ : locale ] ] ] ] }}
- CommonModule
- value
- string | number
- The number to be formatted as currency.
-
currencyCode
-
string
-
The
ISO 4217
currency code, such asUSD
for the US dollar andEUR
for the euro. The default currency code can be configured using theDEFAULT_CURRENCY_CODE
injection token. -
Optional. Default is
this._defaultCurrencyCode
.
-
-
display
-
string | boolean
-
The format for the currency indicator. One of the following:
-
code
: Show the code (such as USD). -
symbol
(default): Show the symbol (such as$
). -
symbol-narrow
: Use the narrow symbol for locales that have two symbols for their currency. For example, the Canadian dollar CAD has the symbolCA$
and the symbol-narrow$
. If the locale has no narrow symbol, uses the standard symbol for the locale. -
String: Use the given string value instead of a code or a symbol. For example, an empty string will suppress the currency & symbol.
-
Boolean (marked deprecated in v5):
true
for symbol andfalse
for code.
-
-
Optional. Default is
'symbol'
.
-
-
digitsInfo
-
string
-
Decimal representation options, specified by a string in the following format:
{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}
.-
minIntegerDigits
: The minimum number of integer digits before the decimal point. Default is1
. -
minFractionDigits
: The minimum number of digits after the decimal point. Default is2
. -
maxFractionDigits
: The maximum number of digits after the decimal point. Default is2
. If not provided, the number will be formatted with the proper amount of digits, depending on what theISO 4217
specifies. For example, the Canadian dollar has 2 digits, whereas the Chilean peso has none.
-
-
Optional. Default is
undefined
.
-
-
locale
-
string
-
A locale code for the locale format rules to use. When not supplied, uses the value of
LOCALE_ID
, which isen-US
by default. SeeSetting your app locale
. -
Optional. Default is
undefined
.
-
-
Deprecation notice:
-
The default currency code is currently always
USD
but this is deprecated from v9. -
In v11 the default currency code will be taken from the current locale identified by the
LOCALE_ID
token. See thei18n guide
for more information. -
If you need the previous behavior then set it by creating a
DEFAULT_CURRENCY_CODE
provider in your application NgModule:{provide: DEFAULT_CURRENCY_CODE, useValue: 'USD'}
-
-
The following code shows how the pipe transforms numbers into text strings, according to various format specifications, where the caller's default locale is
en-US
.@Component({ selector: 'currency-pipe', template: `<div> <!--output '$0.26'--> <p>A: {{a | currency}}</p> <!--output 'CA$0.26'--> <p>A: {{a | currency:'CAD'}}</p> <!--output 'CAD0.26'--> <p>A: {{a | currency:'CAD':'code'}}</p> <!--output 'CA$0,001.35'--> <p>B: {{b | currency:'CAD':'symbol':'4.2-2'}}</p> <!--output '$0,001.35'--> <p>B: {{b | currency:'CAD':'symbol-narrow':'4.2-2'}}</p> <!--output '0 001,35 CA$'--> <p>B: {{b | currency:'CAD':'symbol':'4.2-2':'fr'}}</p> <!--output 'CLP1' because CLP has no cents--> <p>B: {{b | currency:'CLP'}}</p> </div>` }) export class CurrencyPipeComponent { a: number = 0.259; b: number = 1.3495; }
-
Formats a date value according to locale rules.
{{ value_expression | date [ : format [ : timezone [ : locale ] ] ] }}
CommonModule
- value: string | number | Date
-
format
- string
- Optional. Default is
'mediumDate'
.
-
timezone
- string
- Optional. Default is
undefined
.
-
locale
- string
- Optional. Default is
undefined
.
-
DatePipe
is executed only when it detects a pure change to the input value. A pure change is either a change to a primitive input value (such asString
,Number
,Boolean
, orSymbol
), or a changed object reference (such asDate
,Array
,Function
, orObject
). -
Note that mutating a
Date
object does not cause the pipe to be rendered again. To ensure that the pipe is executed, you must create a newDate
object. -
Only the
en-US
locale data comes with Angular. To localize dates in another language, you must import the corresponding locale data. See the I18n guide for more information. -
The time zone of the formatted value can be specified either by passing it in as the second parameter of the pipe, or by setting the default through the
DATE_PIPE_DEFAULT_TIMEZONE
injection token. The value that is passed in as the second parameter takes precedence over the one defined using the injection token.
- The result of this pipe is not reevaluated when the input is mutated. To avoid the need to reformat the date on every change-detection cycle, treat the date as an immutable object and change the reference when the pipe needs to run again.
Option | Equivalent to | Examples (given in en-US locale) |
---|---|---|
'short' | 'M/d/yy, h:mm a' | 6/15/15, 9:03 AM |
'medium' | 'MMM d, y, h:mm:ss a' Jun 15, 2015, 9:03:01 AM | |
'long' | 'MMMM d, y, h:mm:ss a z' | June 15, 2015 at 9:03:01 AM GMT+1 |
'full' | 'EEEE, MMMM d, y, h:mm:ss a zzzz' | Monday, June 15, 2015 at 9:03:01 AM GMT+01:00 |
'shortDate' | 'M/d/yy' | 6/15/15 |
'mediumDate' | 'MMM d, y' | Jun 15, 2015 |
'longDate' | 'MMMM d, y' | June 15, 2015 |
'fullDate' | 'EEEE, MMMM d, y' | Monday, June 15, 2015 |
'shortTime' | 'h:mm a' | 9:03 AM |
'mediumTime' | 'h:mm:ss a' | 9:03:01 AM |
'longTime' | 'h:mm:ss a z' | 9:03:01 AM GMT+1 |
'fullTime' | 'h:mm:ss a zzzz' | 9:03:01 AM GMT+01:00 |
-
You can construct a format string using symbols to specify the components of a date-time value, as described in the following table. Format details depend on the locale. Fields marked with (*) are only available in the extra data set for the given locale.
Field type Format Description Example Value Era G, GG & GGG Abbreviated AD GGGG Wide Anno Domini GGGGG Narrow A Year y Numeric: minimum digits 2, 20, 201, 2017, 20173 yy Numeric: 2 digits + zero padded 02, 20, 01, 17, 73 yyy Numeric: 3 digits + zero padded 002, 020, 201, 2017, 20173 yyyy Numeric: 4 digits or more + zero padded 0002, 0020, 0201, 2017, 20173 Week-numbering year Y Numeric: minimum digits 2, 20, 201, 2017, 20173 YY Numeric: 2 digits + zero padded 02, 20, 01, 17, 73 YYY Numeric: 3 digits + zero padded 002, 020, 201, 2017, 20173 YYYY Numeric: 4 digits or more + zero padded 0002, 0020, 0201, 2017, 20173 Month M Numeric: 1 digit 9, 12 MM Numeric: 2 digits + zero padded 09, 12 MMM Abbreviated Sep MMMM Wide September MMMMM Narrow S Month standalone L Numeric: 1 digit 9, 12 LL Numeric: 2 digits + zero padded 09, 12 LLL Abbreviated Sep LLLL Wide September LLLLL Narrow S Week of year w Numeric: minimum digits 1... 53 ww Numeric: 2 digits + zero padded 01... 53 Week of month W Numeric: 1 digit 1... 5 Day of month d Numeric: minimum digits 1 dd Numeric: 2 digits + zero padded 01 Week day E, EE & EEE Abbreviated Tue EEEE Wide Tuesday EEEEE Narrow T EEEEEE Short Tu Week day standalone c, cc Numeric: 1 digit 2 ccc Abbreviated Tue cccc Wide Tuesday ccccc Narrow T cccccc Short Tu Period a, aa & aaa Abbreviated am/pm or AM/PM aaaa Wide (fallback to a
when missing)ante meridiem/post meridiem aaaaa Narrow a/p Period* B, BB & BBB Abbreviated mid. BBBB Wide am, pm, midnight, noon, morning, afternoon, evening, night BBBBB Narrow md Period standalone* b, bb & bbb Abbreviated mid. bbbb Wide am, pm, midnight, noon, morning, afternoon, evening, night bbbbb Narrow md Hour 1-12 h Numeric: minimum digits 1, 12 hh Numeric: 2 digits + zero padded 01, 12 Hour 0-23 H Numeric: minimum digits 0, 23 HH Numeric: 2 digits + zero padded 00, 23 Minute m Numeric: minimum digits 8, 59 mm Numeric: 2 digits + zero padded 08, 59 Second s Numeric: minimum digits 0... 59 ss Numeric: 2 digits + zero padded 00... 59 Fractional seconds S Numeric: 1 digit 0... 9 SS Numeric: 2 digits + zero padded 00... 99 SSS Numeric: 3 digits + zero padded (= milliseconds) 000... 999 Zone z, zz & zzz Short specific non location format (fallback to O) GMT-8 zzzz Long specific non location format (fallback to OOOO) GMT-08:00 Z, ZZ & ZZZ ISO8601 basic format -0800 ZZZZ Long localized GMT format GMT-8:00 ZZZZZ ISO8601 extended format + Z indicator for offset 0 (= XXXXX) -08:00 O, OO & OOO Short localized GMT format GMT-8 OOOO Long localized GMT format GMT-08:00
-
These examples transform a date into various formats, assuming that
dateObj
is a JavaScript Date object for year: 2015, month: 6, day: 15, hour: 21, minute: 43, second: 11, given in the local time for theen-US
locale.{{ dateObj | date }} // output is 'Jun 15, 2015' {{ dateObj | date:'medium' }} // output is 'Jun 15, 2015, 9:43:11 PM' {{ dateObj | date:'shortTime' }} // output is '9:43 PM' {{ dateObj | date:'mm:ss' }} // output is '43:11'
-
The following component uses a date pipe to display the current date in different formats.
@Component({ selector: 'date-pipe', template: `<div> <p>Today is {{today | date}}</p> <p>Or if you prefer, {{today | date:'fullDate'}}</p> <p>The time is {{today | date:'h:mm a z'}}</p> </div>` }) // Get the current date and time as a date-time value. export class DatePipeComponent { today: number = Date.now(); }
-
Angular throws an ExpressionChangedAfterItHasBeenCheckedError when an expression value has been changed after change detection has completed. Angular only throws this error in development mode.
-
In development mode, Angular performs an additional check after each change detection run, to ensure the bindings haven't changed. This catches errors where the view is left in an inconsistent state. This can occur, for example, if a method or getter returns a different value each time it is called, or if a child component changes values on its parent. If either of these occurs, this is a sign that change detection is not stabilized. Angular throws the error to ensure data is always reflected correctly in the view, which prevents erratic UI behavior or a possible infinite loop.
-
This error commonly occurs when you've added template expressions or have begun to implement lifecycle hooks like ngAfterViewInit or ngOnChanges. It is also common when dealing with loading status and asynchronous operations, or when a child component changes its parent bindings.
-
The source maps generated by the CLI are very useful when debugging. Navigate up the call stack until you find a template expression where the value displayed in the error has changed.
-
Ensure that there are no changes to the bindings in the template after change detection is run. This often means refactoring to use the correct component lifecycle hook for your use case. If the issue exists within ngAfterViewInit, the recommended solution is to use a constructor or ngOnInit to set initial values, or use ngAfterContentInit for other value bindings.
-
If you are binding to methods in the view, ensure that the invocation does not update any of the other bindings in the template.
-
Async validators must return a promise or an observable, and emit/resolve them whether the validation fails or succeeds. In particular, they must implement the
AsyncValidatorFn API
export function isTenAsync(control: AbstractControl): Observable<ValidationErrors | null> { const v: number = control.value; if (v !== 10) { // Emit an object with a validation error. return of({ 'notTen': true, 'requiredValue': 10 }); } // Emit null, to indicate no error occurred. return of(null); }
- Did you mistakenly use a synchronous validator instead of an async validator?
- A cyclic dependency exists when a
dependency of a service
directly or indirectly depends on the service itself. For example, if UserService depends on EmployeeService, which also depends on UserService. Angular will have to instantiate EmployeeService to create UserService, which depends on UserService, itself.
-
Use the call stack to determine where the cyclical dependency exists. You will be able to see if any child dependencies rely on the original file by mapping out the component, module, or service's dependencies, and identifying the loop causing the problem.
-
Break this loop (or circle) of dependency to resolve this error. This most commonly means removing or refactoring the dependencies to not be reliant on one another.
- You see this error when you try to inject a service but have not declared a corresponding provider. A provider is a mapping that supplies a value that you can inject into the constructor of a class in your application.
-
Work backwards from the object where the error states that a
provider
is missing:No provider for ${this}!
. This is commonly thrown inservices
, which require non-existing providers. -
To fix the error ensure that your service is registered in the list of providers of an
NgModule
or has the@Injectable
decorator with a providedIn property at top. -
The most common solution is to add a provider in
@Injectable
usingprovidedIn
:@Injectable({ providedIn: 'app' })
NG0203: inject()
must be called from an injection context such as a constructor, a factory function, a field initializer, or a function used with EnvironmentInjector#runInContext
.
-
You see this error when you try to use the
inject()
function outside of the allowed injection context. The injection context is available during the class creation and initialization. It is also available to functions used withEnvironmentInjector#runInContext
. -
In practice the
inject()
calls are allowed in a constructor, a constructor parameter and a field initializer:@Injectable({providedIn: 'root'}) export class Car { radio: Radio|undefined; // OK: field initializer spareTyre = inject(Tyre); constructor() { // OK: constructor body this.radio = inject(Radio); } }
-
It is also legal to call
inject
from a provider's factory:providers: [ {provide: Car, useFactory: () => { // OK: a class factory const engine = inject(Engine); return new Car(engine); }} ]
-
Calls to the
inject()
function outside of the class creation orrunInContext
will result in error. Most notably, calls toinject()
are disallowed after a class instance was created, in methods (including lifecycle hooks):@Component({ ... }) export class CarComponent { ngOnInit() { // ERROR: too late, the component instance was already created const engine = inject(Engine); engine.start(); } }
-
Work backwards from the stack trace of the error to identify a place where the disallowed call to
inject()
is located. -
To fix the error move the
inject()
call to an allowed place (usually a class constructor or a field initializer).
NgFor
could not find an iterable differ for the value passed in. Make sure it's an iterable, like anArray
.
- When using ngFor in a template, you must use some type of Iterable, like
Array
,Set
,Map
, etc. If you're trying to iterate over the keys in an object, you should look at theKeyValue pipe
instead.
- Two or more components use the same
element selector
. Because there can only be a single component associated with an element, selectors must be unique strings to prevent ambiguity for Angular.
-
Use the element name from the error message to search for places where you're using the same selector declaration in your codebase:
@Component({ selector: 'YOUR_STRING', … })
-
Ensure that each component has a unique CSS selector. This will guarantee that Angular renders the component you expect.
-
If you're having trouble finding multiple components with this selector tag name, check for components from imported component libraries, such as Angular Material. Make sure you're following the best practices for your selectors to prevent collisions.
-
Angular can't find a directive with
{{ PLACEHOLDER }}
export name. The export name is specified in the exportAs property of the directive decorator. This is common when using FormsModule or Material modules in templates and you've forgotten toimport the corresponding modules
. -
This is the runtime equivalent of a common compiler error
NG8003: No directive found with export
.
-
Use the export name to trace the templates or modules using this export.
-
Ensure that all dependencies are
properly imported and declared in your NgModules
. For example, if the export not found isngForm
, we need to importFormsModule
and declare it in the list of imports in*.module.ts
to resolve the error.import { FormsModule } from '@angular/forms'; @NgModule({ … imports: [ FormsModule, …
-
If you recently added an import, you may need to restart your server to see these changes.
-
Angular can't find a pipe with this name.
-
The pipe referenced in the template has not been named or declared properly.
-
In order for a
pipe
to be used:-
it must be declared as a part of an
NgModule
(added to thedeclarations
array) or marked as standalone (by adding thestandalone: true
flag to the Pipe decorator). -
it must be imported in an
NgModule
or a standalone component where it is used. -
the name used in a template must match the name defined in the Pipe decorator.
-
-
Use the pipe name to trace where the pipe is declared and used.
-
To resolve this error, ensure that:
- If the pipe is local to the NgModule, it is uniquely named in the pipe's decorator and declared in the NgModule.
- If the pipe is standalone or from another NgModule, it is added to the imports field of the current NgModule or standalone component.
-
If you recently added an import or declaration, you may need to restart your server to see these changes.
-
To make the metadata extraction in the Angular compiler faster, the decorators @NgModule, @Pipe, @Component, @Directive, and @Injectable accept only object literals as arguments.
-
This is an intentional change in Ivy, which enforces stricter argument requirements for decorators than View Engine. Ivy requires this approach because it compiles decorators by moving the expressions into other locations in the class output.
-
Move all declarations:
const moduleDefinition = {…} @NgModule(moduleDefinition) export class AppModule { constructor() {} }
-
into the decorator:
@NgModule({…}) export class AppModule { constructor() {} }
- There is no injection token for a constructor parameter at compile time.
InjectionTokens
are tokens that can be used in a Dependency Injection Provider.
-
Look at the parameter that throws the error, and all uses of the class. This error is commonly thrown when a constructor defines parameters with primitive types such as
string
,number
,boolean
, andObject
. -
Use the
@Injectable
method or@Inject
decorator from@angular/core
to ensure that the type you are injecting is reified (has a runtime representation). Make sure to add a provider to this decorator so that you do not throwNG0201: No Provider Found
.
-
The selector of a component using
ViewEncapsulation.ShadowDom
doesn't match the custom element tag name requirements. -
In order for a tag name to be considered a valid custom element name, it has to:
- Be in lower case.
- Contain a hyphen.
- Start with a letter (a-z).
-
Rename your component's selector so that it matches the requirements.
-
Before:
@Component({ selector: 'comp', encapsulation: ViewEncapsulation.ShadowDom … })
-
After:
@Component({ selector: 'app-comp', encapsulation: ViewEncapsulation.ShadowDom … })
-
-
A component, directive, or pipe that is referenced by this component would require the compiler to add an import that would lead to a cycle of imports. For example, consider a scenario where a
ParentComponent
references aChildComponent
in its template:-
parent.component.ts
import { Component } from '@angular/core'; @Component({selector: 'app-parent', template: '<app-child></app-child>'}) export class ParentComponent {}
-
child.component.ts
import { Component } from '@angular/core'; import { ParentComponent } from './parent.component'; @Component({selector: 'app-child', template: 'The child!'}) export class ChildComponent { constructor(private parent: ParentComponent) {} }
-
-
There is already an import from
child.component.ts
toparent.component.ts
since theChildComponent
references theParentComponent
in its constructor. -
NOTE:
-
The parent component's template contains
<child></child>
. The generated code for this template must therefore contain a reference to theChildComponent
class. In order to make this reference, the compiler would have to add an import fromparent.component.ts
tochild.component.ts
, which would cause an import cycle:parent.component.ts -> child.component.ts -> parent.component.ts
-
- To avoid adding imports that create cycles, additional code is added to the NgModule class where the component that wires up the dependencies is declared. This is known as "remote scoping".
- Unfortunately, "remote scoping" code is side-effectful —which prevents tree shaking— and cannot be used in libraries. So when building libraries using the
"compilationMode": "partial"
setting, any component that would require a cyclic import will cause thisNG3003
compiler error to be raised.
-
The cycle that would be generated is shown as part of the error message. For example:
The component ChildComponent is used in the template but importing it would create a cycle: /parent.component.ts -> /child.component.ts -> /parent.component.ts
-
Use this to identify how the referenced component, pipe, or directive has a dependency back to the component being compiled. Here are some ideas for fixing the problem:
-
Try to rearrange your dependencies to avoid the cycle. For example, using an intermediate interface that is stored in an independent file that can be imported to both dependent files without causing an import cycle.
-
Move the classes that reference each other into the same file, to avoid any imports between them.
-
Convert import statements to type-only imports (using
import type
syntax) if the imported declarations are only used as types, as type-only imports do not contribute to cycles.
-
-
Using
module.id
as an NgModuleid
is a common anti-pattern and is likely not serving a useful purpose in your code. -
NgModules can be declared with an id:
@NgModule({ id: 'my_module' }) export class MyModule {}
-
Declaring an id makes the NgModule available for lookup via the getNgModuleById() operation. This functionality is rarely used, mainly in very specific bundling scenarios when lazily loading NgModules without obtaining direct references to them. In most Angular code, ES dynamic import() (import('./path/to/module')) should be used instead, as this provides a direct reference to the NgModule being loaded without the need for a global registration side effect.
-
If you are not using getNgModuleById, you do not need to provide ids for your NgModules. Providing one has a significant drawback: it makes the NgModule non-tree-shakable, which can have an impact on your bundle size.
-
In particular, the pattern of specifying id: module.id results from a misunderstanding of @NgModule.id. In earlier versions of Angular, it was sometimes necessary to include the property moduleId: module.id in @Component metadata.
-
Using module.id for @NgModule.id likely results from confusion between @Component.moduleId and @NgModule.id. module.id would not typically be useful for getNgModuleById() operations as the id needs to be a well-known string, and module.id is usually opaque to consumers.
- You can remove the
id: module.id
declaration from your NgModules. The compiler ignores this declaration and issues this warning instead.
- This error represents the import or export of an
@NgModule()
that doesn't have valid metadata.
-
The library might have been processed with ngcc. If this is the case, try removing and reinstalling node_modules. This error is likely due to the library being published for Angular Ivy, which cannot be used in this View Engine application. If that is not the case then it might be a View Engine based library that was converted to Ivy by ngcc during a postinstall step.
-
Check the peer dependencies to ensure that you're using a compatible version of Angular.
- One or more elements cannot be resolved during compilation because the element is not defined by the HTML spec, or there is no component or directive with such element selector.
- This is the compiler equivalent of a common runtime error
NG0304: '${tagName}' is not a known element: ....
- This is the compiler equivalent of a common runtime error
-
Use the element name in the error to find the file(s) where the element is being used.
-
Check that the name and selector are correct.
-
Make sure that the component is correctly imported inside your NgModule or standalone component, by checking its presence in the imports field. If the component is declared in an NgModule (meaning that it is not standalone) make sure that it is exported correctly from it, by checking its presence in the exports field.
-
When using custom elements or web components, ensure that you add
CUSTOM_ELEMENTS_SCHEMA
to the application module. -
If this does not resolve the error, check the imported libraries for any recent changes to the exports and properties you are using, and restart your server.
-
An attribute or property cannot be resolved during compilation.
-
This error arises when attempting to bind to a property that does not exist. Any property binding must correspond to either:
- A native property on the HTML element, or
- An
@Input()
property of a component or directive applied to the element.
-
The runtime error for this is
NG0304: '${tagName}' is not a known element: …'
.
- Look at documentation for the specific binding syntax used. This is usually a typo or incorrect import. There may also be a missing direction with property selector 'name' or missing input.
-
Angular can't find a directive with
{{ PLACEHOLDER }}
export name. This is common with a missing import or a missingexportAs
on a directive. -
This is the compiler equivalent of a common runtime error
NG0301: Export Not Found
.
-
Use the string name of the export not found to trace the templates or modules using this export.
-
Ensure that all dependencies are properly imported and declared in our Modules. For example, if the export not found is
ngForm
, we will need to importFormsModule
and declare it in our list of imports in *.module.ts to resolve the missing export error.import { FormsModule } from '@angular/forms'; @NgModule({ … imports: [ FormsModule, …
-
If you recently added an import, you will need to restart your server to see these changes.
- There are many coding patterns that are technically valid to the compiler or runtime, but which may have complex nuances or caveats. These patterns may not have the intended effect expected by a developer, which often leads to bugs. The Angular compiler includes "extended diagnostics" which identify many of these patterns, in order to warn developers about the potential issues and enforce common best practices within a codebase.
-
Currently, Angular supports the following extended diagnostics:
- NG8101 - invalidBananaInBox
- NG8102 - nullishCoalescingNotNullable
-
Extended diagnostics are warnings by default and do not block compilation. Each diagnostic can be configured as either:
- warning (default) - The compiler emits the diagnostic as a warning but does not block compilation. The compiler will still exit with status code 0, even if warnings are emitted.
- error - The compiler emits the diagnostic as an error and fails the compilation. The compiler will exit with a non-zero status code if one or more errors are emitted.
- suppress - The compiler does not emit the diagnostic at all.
-
Check severity can be configured in the project's
tsconfig.json
file:{ "angularCompilerOptions": { "extendedDiagnostics": { // The categories to use for specific diagnostics. "checks": { // Maps check name to its category. "invalidBananaInBox": "suppress" }, // The category to use for any diagnostics not listed in `checks` above. "defaultCategory": "error" } } }
-
The
checks
field maps the name of individual diagnostics to their associated category. SeeDiagnostics
for a complete list of extended diagnostics and the name to use for configuring them. -
The
defaultCategory
field is used for any diagnostics that are not explicitly listed under checks. If not set, such diagnostics will be treated as warning. -
Extended diagnostics will emit when
strictTemplates
is enabled. This is required to allow the compiler to better understand Angular template types and provide accurate and meaningful diagnostics.
-
The Angular team intends to add or enable new extended diagnostics in minor versions of Angular (see semver). This means that upgrading Angular may show new warnings in your existing codebase. This enables the team to deliver features more quickly and to make extended diagnostics more accessible to developers.
-
However, setting
"defaultCategory": "error"
will promote such warnings to hard errors. This can cause a minor version upgrade to introduce compilation errors, which may be seen as a semver non-compliant breaking change. Any new diagnostics can be suppressed or demoted to warnings via the above configuration, so the impact of a new diagnostic should be minimal to projects that treat extended diagnostics as errors by default. Defaulting to error is a very powerful tool; just be aware of this semver caveat when deciding if error is the right default for your project.
-
The Angular team is always open to suggestions about new diagnostics that could be added. Extended diagnostics should generally:
- Detect a common, non-obvious developer mistake with Angular templates
- Clearly articulate why this pattern can lead to bugs or unintended behavior
- Suggest one or more clear solutions
- Have a low, preferably zero, false-positive rate
- Apply to the vast majority of Angular applications (not specific to an unofficial library)
- Improve program correctness or performance (not style, that responsibility falls to a linter)
- This diagnostic detects a backwards "banana-in-box" syntax for two-way bindings.
<div ([var])="otherVar"></div>
- As it stands, ([var]) is actually an event binding with the name [var]. The author likely intended this as a two-way binding to a variable named var but, as written, this binding has no effect.
- Fix the typo. As the name suggests, the banana ( should go inside the box []. In this case:
<div [(var)]="otherVar"></div>
strictTemplates
must be enabled for any extended diagnostic to emit.invalidBananaInBox
has no additional requirements beyondstrictTemplates
.
- This diagnostic can be disabled by editing the project's
tsconfig.json
file:{ "angularCompilerOptions": { "extendedDiagnostics": { "checks": { "invalidBananaInBox": "suppress" } } } }
-
This diagnostic detects a useless nullish coalescing operator (
??
) characters in Angular templates. Specifically, it looks for operations where the input is not "nullable", meaning its type does not includenull
orundefined
. For such values, the right side of the??
will never be used.import {Component} from '@angular/core'; @Component({ // Template displays `foo` if present, falls back to 'bar' if it is `null` // or `undefined`. template: `{{ foo ?? 'bar' }}`, // … }) class MyComponent { // `foo` is declared as a `string` which *cannot* be `null` or `undefined`. foo: string = 'test'; }
- Using the nullish coalescing operator with a non-nullable input has no effect and is indicative of a discrepancy between the allowed type of a value and how it is presented in the template. A developer might reasonably assume that the right side of the nullish coalescing operator is displayed in some case, but it will never actually be displayed. This can lead to confusion about the expected output of the program.
-
Update the template and declared type to be in sync. Double-check the type of the input and confirm whether it is actually expected to be nullable.
-
If the input should be nullable, add
null
orundefined
to its type to indicate this.import {Component} from '@angular/core'; @Component({ template: `{{ foo ?? 'bar' }}`, // … }) class MyComponent { // `foo` is now nullable. If it is ever set to `null`, 'bar' will be displayed. foo: string | null = 'test'; }
-
If the input should not be nullable, delete the ?? operator and its right operand, since the value is guaranteed by TypeScript to always be non-nullable.
import {Component} from '@angular/core'; @Component({ // Template always displays `foo`, which is guaranteed to never be `null` or // `undefined`. template: `{{ foo }}`, // … }) class MyComponent { foo: string = 'test'; }
strictTemplates
must be enabled for any extended diagnostic to emit.strictNullChecks
must also be enabled to emit any nullishCoalescingNotNullable diagnostics.
-
This diagnostic can be disabled by editing the project's
tsconfig.json
file:{ "angularCompilerOptions": { "extendedDiagnostics": { "checks": { "nullishCoalescingNotNullable": "suppress" } } } }
-
Angular has its own vocabulary. Most Angular terms are common English words or computing terms that have a specific meaning within the Angular system.
-
This glossary lists the most prominent terms and a few less familiar ones with unusual or unexpected definitions.
-
The Angular ahead-of-time (AOT) compiler converts Angular HTML and TypeScript code into efficient JavaScript code during the build phase. The build phase occurs before the browser downloads and runs the rendered code. This is the best compilation mode for production environments, with decreased load time and increased performance compared to just-in-time (JIT) compilation.
-
By compiling your application using the ngc command-line tool, you can bootstrap directly to a module factory, so you do not need to include the Angular compiler in your JavaScript bundle.
- An Angular component packaged as a custom element.
- An Angular specific specification for layout of npm packages that is used by all first-party Angular packages, and most third-party Angular libraries.
- A structure that provides metadata for a class.
-
App shell is a way to render a portion of your application using a route at build time. This gives users a meaningful first paint of your application that appears quickly because the browser can render static HTML and CSS without the need to initialize JavaScript. To learn more, see The App Shell Model.
-
You can use the Angular CLI to generate an app shell. This can improve the user experience by quickly launching a static rendered page while the browser downloads the full client version and switches to it automatically after the code loads. A static rendered page is a skeleton common to all pages. To learn more, see Service Worker and PWA.
-
The tool that the Angular CLI uses to perform complex tasks such as compilation and test running, according to a provided configuration. Architect is a shell that runs a builder with a given target configuration. The builder is defined in an npm package.
-
In the workspace configuration file, an "architect" section provides configuration options for Architect builders.
-
For example, a built-in builder for linting is defined in the package @angular-devkit/build_angular:tslint, which uses the TSLint tool to perform linting, with a configuration specified in a tslint.json file.
-
Use the ng run Angular CLI command to invoke a builder by specifying a target configuration associated with that builder. Integrators can add builders to enable tools and workflows to run through the Angular CLI. For example, a custom builder can replace the third-party tools used by the built-in implementations for Angular CLI commands, such as ng build or ng test.
- A category of directive that can listen to and modify the behavior of other HTML elements, attributes, properties, and components. They are usually represented as HTML attributes, hence the name.
-
Generally, the practice of setting a variable or property to a data value. Within Angular, typically refers to data binding, which coordinates DOM object properties with data object properties.
-
Sometimes refers to a dependency-injection binding between a token and a dependency provider.
-
A way to initialize and launch an application or system.
-
In Angular, the AppModule root NgModule of an application has a bootstrap property that identifies the top-level components of the application. During the bootstrap process, Angular creates and inserts these components into the index.html host web page. You can bootstrap multiple applications in the same index.html. Each application contains its own components.
-
A function that uses the Architect API to perform a complex process such as build or test. The builder code is defined in an npm package.
-
For example, BrowserBuilder runs a webpack build for a browser target and KarmaBuilder starts the Karma server and runs a webpack build for unit tests.
-
The ng run Angular CLI command invokes a builder with a specific target configuration. The workspace configuration file, angular.json, contains default configurations for built-in builders.
-
Angular uses capitalization conventions to distinguish the names of various types, as described in the naming guidelines section of the Style Guide. Here is a summary of the case types:
-
camelCase
- Symbols, properties, methods, pipe names, non-component directive selectors, constants.
- Standard or lower camel case uses lowercase on the first letter of the item.
- selectedHero
-
UpperCamelCase/PascalCase
- Class names, including classes that define components, interfaces, NgModules, directives, and pipes.
- Upper camel case uses uppercase on the first letter of the item.
- HeroComponent
-
dash-case/kebab-case
- Descriptive part of file names, component selectors.
- app-hero-list
-
underscore_case/snake_case
- Not typically used in Angular.
- Snake case uses words connected with underscores.
- convert_link_mode
-
UPPER_UNDERSCORE_CASE/UPPER_SNAKE_CASE/SCREAMING_SNAKE_CASE
- Traditional for constants.
- This case is acceptable, but camelCase is preferred.
- Upper snake case uses words in all capital letters connected with underscores.
- FIX_ME
-
The mechanism by which the Angular framework synchronizes the state of the UI of an application with the state of the data. The change detector checks the current state of the data model whenever it runs, and maintains it as the previous state to compare on the next iteration.
-
As the application logic updates component data, values that are bound to DOM properties in the view can change. The change detector is responsible for updating the view to reflect the current data model. Similarly, the user can interact with the UI, causing events that change the state of the data model. These events can trigger change detection.
-
Using the default change-detection strategy, the change detector goes through the view hierarchy on each VM turn to check every data-bound property in the template. In the first phase, it compares the current state of the dependent data with the previous state, and collects changes. In the second phase, it updates the page DOM to reflect any new data values.
-
If you set the
OnPush
change-detection strategy, the change detector runs only when explicitly invoked, or when it is triggered by an Input reference change or event handler. This typically improves performance. To learn more, see Optimize the change detection in Angular.
-
A decorator that appears immediately before a class definition, which declares the class to be of the given type, and provides metadata suitable to the type.
-
The following decorators can declare Angular class types.
@Component()
@Directive()
@Pipe()
@Injectable()
@NgModule()
A decorator statement immediately before a field in a class definition that declares the type of that field. Some examples are @Input
and @Output
.
- In Angular, a set of related schematics collected in an npm package.
-
The Angular CLI is a command-line tool for managing the Angular development cycle. Use it to create the initial filesystem scaffolding for a workspace or project, and to run schematics that add and modify code for initial generic versions of various elements. The Angular CLI supports all stages of the development cycle, including building, testing, bundling, and deployment.
-
To begin using the Angular CLI for a new project, see Local Environment Setup. To learn more about the full capabilities of the Angular CLI, see the Angular CLI command reference. See also Schematics CLI.
-
A class with the @Component() decorator that associates it with a companion template. Together, the component class and template define a view. A component is a special type of directive. The @Component() decorator extends the @Directive() decorator with template-oriented features.
-
An Angular component class is responsible for exposing data and handling most of the display and user-interaction logic of the view through data binding.
-
Read more about component classes, templates, and views in Introduction to Angular concepts.
- See
workspace configuration
-
A way to insert DOM content from outside a component into the view of the component in a designated spot.
-
To learn more, see Responding to changes in content.
-
A web platform feature, currently supported by most browsers and available in other browsers through polyfills. See Browser support.
-
The custom element feature extends HTML by allowing you to define a tag whose content is created and controlled by JavaScript code. A custom element is recognized by a browser when it is added to the CustomElementRegistry. A custom element is also referenced as a web component.
-
You can use the API to transform an Angular component so that it can be registered with the browser and used in any HTML that you add directly to the DOM within an Angular application. The custom element tag inserts the view of the component, with change-detection and data-binding functionality, into content that would otherwise be displayed without Angular processing. See Angular element. See also dynamic component loading.
-
A process that allows applications to display data values to a user and respond to user actions. User actions include clicks, touches, keystrokes, and so on.
-
In data binding, you declare the relationship between an HTML widget and a data source and let the framework handle the details. Data binding is an alternative to manually pushing application data values into HTML, attaching event listeners, pulling changed values from the screen, and updating application data values.
-
Read about the following forms of binding of the Template Syntax in Angular:
- Interpolation
- Property binding
- Event binding
- Attribute binding
- Class and style binding
- Two-way data binding with ngModel
-
A class that you can add to the declarations list of an NgModule. You can declare components, directives, and pipes, unless they have the standalone flag in their decorators set to true, which makes them standalone. Note: standalone components/directives/pipes are not declarables. More info about standalone classes can be found below.
-
Do not declare the following:
- A class already declared as standalone.
- A class that is already declared in another NgModule.
- An array of directives imported from another package. For example, do not declare FORMS_DIRECTIVES from @angular/forms.
- NgModule classes.
- Service classes.
- Non-Angular classes and objects, such as strings, numbers, functions, entity models, configurations, business logic, and helper classes.
-
Note that declarables can also be declared as standalone and simply be imported inside other standalone components or existing NgModules, to learn more, see the Standalone components guide.
-
A function that modifies a class or property definition. Decorators are an experimental (stage 3) JavaScript language feature. A decorator is also referenced as an annotation. TypeScript adds support for decorators.
-
Angular defines decorators that attach metadata to classes or properties so that it knows what those classes or properties mean and how they should work.
-
To learn more, see class decorator. See also class field decorator.
-
A design pattern and mechanism for creating and delivering some parts of an application (dependencies) to other parts of an application that require them.
-
In Angular, dependencies are typically services, but they also can be values, such as strings or functions. An injector for an application (created automatically during bootstrap) instantiates dependencies when needed, using a configured provider of the service or value. Learn more in Dependency Injection in Angular.
- A lookup token associated with a dependency provider, for use with the dependency injection system.
-
A class that can modify the structure of the DOM or modify attributes in the DOM and component data model. A directive class definition is immediately preceded by a @Directive() decorator that supplies metadata.
-
A directive class is usually associated with an HTML element or attribute, and that element or attribute is often referred to as the directive itself. When Angular finds a directive in an HTML template, it creates the matching directive class instance and gives the instance control over that portion of the browser DOM.
-
Angular has three categories of directive:
-
Components use @Component() to associate a template with a class. @Component() is an extension of @Directive().
-
Attribute directives modify behavior and appearance of page elements.
-
Structural directives modify the structure of the DOM.
-
-
Angular supplies a number of built-in directives that begin with the ng prefix. You can also create new directives to implement your own functionality. You associate a selector with a custom directive; this extends the template syntax that you can use in your applications. A selector is an HTML tag, such as
<my-directive>
. -
UpperCamelCase, such as NgIf, refers to a directive class. You can use UpperCamelCase when describing properties and directive behavior.
-
lowerCamelCase, such as ngIf refers to the attribute name of a directive. You can use lowerCamelCase when describing how to apply the directive to an element in the HTML template.
- A special-purpose library or API. To learn more, see Domain-specific language. Angular extends TypeScript with domain-specific languages for a number of domains relevant to Angular applications, defined in NgModules such as animations, forms, and routing and navigation.
-
A technique for adding a component to the DOM at run time. Requires that you exclude the component from compilation and then connect it to the change-detection and event-handling framework of Angular when you add it to the DOM.
-
See also custom element, which provides an easier path with the same result.
- NgModules or components that are loaded on launch are referenced as eager-loaded, to distinguish them from those that are loaded at run time that are referenced as lazy-loaded. See also lazy loading.
-
The official JavaScript language specification.
-
Not all browsers support the latest ECMAScript standard, but you can use a transpiler to write code using the latest features, which will then be transpiled to code that runs on versions that are supported by browsers. An example of a transpiler is TypeScript. To learn more, see Browser Support.
-
Angular defines an ElementRef class to wrap render-specific native UI elements. In most cases, this allows you to use Angular templates and data binding to access DOM elements without reference to the native element.
-
The documentation generally refers to elements as distinct from DOM elements. Elements are instances of a ElementRef class. DOM elements are able to be accessed directly, if necessary.
-
To learn more, see also custom element.
- A JavaScript module that is intended to be imported by a user of an npm package. An entry-point module typically re-exports symbols from other internal modules. A package can contain multiple entry points. For example, the @angular/core package has two entry-point modules, which can be imported using the module names @angular/core and @angular/core/testing.
-
An instance of FormControl, which is a fundamental building block for Angular forms. Together with FormGroup and FormArray, tracks the value, validation, and status of a form input element.
-
Read more forms in the Introduction to forms in Angular.
-
The "source of truth" for the value and validation status of a form input element at a given point in time. When using reactive forms, the form model is created explicitly in the component class. When using template-driven forms, the form model is implicitly created by directives.
-
Learn more about reactive and template-driven forms in the Introduction to forms in Angular.
-
A check that runs when form values change and reports whether the given values are correct and complete, according to the defined constraints. Reactive forms apply validator functions. Template-driven forms use validator directives.
-
To learn more, see Form Validation.
- The inability to alter the state of a value after its creation. Reactive forms perform immutable changes in that each change to the data model produces a new data model rather than modifying the existing one. Template-driven forms perform mutable changes with NgModel and two-way data binding to modify the existing data model in place.
- An Angular class or other definition that provides a dependency using the dependency injection mechanism. An injectable service class must be marked by the @Injectable() decorator. Other items, such as constant values, can also be injectable.
-
An object in the Angular dependency-injection system that can find a named dependency in its cache or create a dependency using a configured provider. Injectors are created for NgModules automatically as part of the bootstrap process and are inherited through the component hierarchy.
-
An injector provides a singleton instance of a dependency, and can inject this same instance in multiple components.
-
A hierarchy of injectors at the NgModule and component level can provide different instances of a dependency to their own components and child components.
-
You can configure injectors with different providers that can provide different implementations of the same dependency.
-
-
Learn more about the injector hierarchy in Hierarchical Dependency Injectors.
-
When defining a directive, the @Input() decorator on a directive property makes that property available as a target of a property binding. Data values flow into an input property from the data source identified in the template expression to the right of the equal sign.
-
To learn more, see @Input() and @Output() decorator functions.
-
A form of property data binding in which a template expression between double-curly braces renders as text. That text can be concatenated with neighboring text before it is assigned to an element property or displayed between element tags, as in this example.
<label>My current hero is {{hero.name}}</label>
-
Read more in the Interpolation guide.
- Ivy is the historical code name for the current compilation and rendering pipeline in Angular. It is now the only supported engine, so everything uses Ivy.
- To learn more, see
ECMAScript
. To learn more, see alsoTypeScript
.
-
The Angular just-in-time (JIT) compiler converts your Angular HTML and TypeScript code into efficient JavaScript code at run time, as part of bootstrapping.
-
JIT compilation is the default (as opposed to AOT compilation) when you run the ng build and ng serve Angular CLI commands, and is a good choice during development. JIT mode is strongly discouraged for production use because it results in large application payloads that hinder the bootstrap performance.
-
Compare to ahead-of-time (AOT) compilation.
-
A process that speeds up application load time by splitting the application into multiple bundles and loading them on demand. For example, dependencies can be lazy loaded as needed. The example differs from eager-loaded modules that are required by the root module and are loaded on launch.
-
The router makes use of lazy loading to load child views only when the parent view is activated. Similarly, you can build custom elements that can be loaded into an Angular application when needed.
-
In Angular, a project that provides functionality that can be included in other Angular applications. A library is not a complete Angular application and cannot run independently.
-
To add re-usable Angular functionality to non-Angular web applications, use Angular custom elements.
-
Library developers can use the Angular CLI to generate scaffolding for a new library in an existing workspace, and can publish a library as an npm package.
-
Application developers can use the Angular CLI to add a published library for use with an application in the same workspace.
-
-
See also schematic.
-
An interface that allows you to tap into the lifecycle of directives and components as they are created, updated, and destroyed.
-
Each interface has a single hook method whose name is the interface name prefixed with ng. For example, the OnInit interface has a hook method named ngOnInit.
-
Angular runs these hook methods in the following order:
HOOK METHOD DETAILS 1 ngOnChanges When an input or output binding value changes. 2 ngOnInit After the first ngOnChanges. 3 ngDoCheck Developer's custom change detection. 4 ngAfterContentInit After component content initialized. 5 ngAfterContentChecked After every check of component content. 6 ngAfterViewInit After the views of a component are initialized. 7 ngAfterViewChecked After every check of the views of a component. 8 ngOnDestroy Just before the directive is destroyed. -
To learn more, see Lifecycle Hooks.
-
In general, a module collects a block of code dedicated to a single purpose. Angular uses standard JavaScript modules and also defines an Angular module, NgModule.
-
In JavaScript, or ECMAScript, each file is a module and all objects defined in the file belong to that module. Objects can be exported, making them public, and public objects can be imported for use by other modules.
-
Angular ships as a collection of JavaScript modules. A collection of JavaScript modules are also referenced as a library. Each Angular library name begins with the @angular prefix. Install Angular libraries with the npm package manager and import parts of them with JavaScript import declarations.
-
Compare to NgModule.
- Angular compatibility compiler. If you build your application using Ivy, but it depends on libraries that have not been compiled with Ivy, the Angular CLI uses ngcc to automatically update the dependent libraries to use Ivy.
-
A class definition preceded by the @NgModule() decorator, which declares and serves as a manifest for a block of code dedicated to an application domain, a workflow, or a closely related set of capabilities.
-
Like a JavaScript module, an NgModule can export functionality for use by other NgModules and import public functionality from other NgModules. The metadata for an NgModule class collects components, directives, and pipes that the application uses along with the list of imports and exports. See also declarable.
-
NgModules are typically named after the file in which the exported thing is defined. For example, the Angular DatePipe class belongs to a feature module named date_pipe in the file date_pipe.ts. You import them from an Angular scoped package such as @angular/core.
-
Every Angular application has a root module. By convention, the class is named AppModule and resides in a file named app.module.ts.
-
To learn more, see NgModules.
-
The npm package manager is used to distribute and load Angular modules and libraries.
-
Learn more about how Angular uses Npm Packages.
ngc
is a Typescript-to-Javascript transpiler that processes Angular decorators, metadata, and templates, and emits JavaScript code. The most recent implementation is internally referred to asngtsc
because it is a minimalistic wrapper around the TypeScript compilertsc
that adds a transform for processing Angular code.
-
A producer of multiple values, which it pushes to subscribers. Used for asynchronous event handling throughout Angular. You execute an observable by subscribing to it with its subscribe() method, passing callbacks for notifications of new values, errors, or completion.
-
Observables can deliver in one the following ways a single value or multiple values of any type to subscribers.
- Synchronously as a function delivers a value to the requester
- Scheduled
-
A subscriber receives notification of new values as they are produced and notification of either normal completion or error completion.
-
Angular uses a third-party library named Reactive Extensions (RxJS). To learn more, see Observables.
- An object passed to the subscribe() method for an observable. The object defines the callbacks for the subscriber.
-
When defining a directive, the @Output{} decorator on a directive property makes that property available as a target of event binding. Events stream out of this property to the receiver identified in the template expression to the right of the equal sign.
-
To learn more, see @Input() and @Output() decorator functions.
-
A class which is preceded by the @Pipe{} decorator and which defines a function that transforms input values to output values for display in a view. Angular defines various pipes, and you can define new pipes.
-
To learn more, see Pipes.
-
In Angular terminology, a platform is the context in which an Angular application runs. The most common platform for Angular applications is a web browser, but it can also be an operating system for a mobile device, or a web server.
-
Support for the various Angular run-time platforms is provided by the @angular/platform-* packages. These packages allow applications that make use of @angular/core and @angular/common to execute in different environments by providing implementation for gathering user input and rendering UIs for the given platform. Isolating platform-specific functionality allows the developer to make platform-independent use of the rest of the framework.
-
When running in a web browser, BrowserModule is imported from the platform-browser package, and supports services that simplify security and event processing, and allows applications to access browser-specific features, such as interpreting keyboard input and controlling the title of the document being displayed. All applications running in the browser use the same platform service.
-
When server-side rendering (SSR) is used, the platform-server package provides web server implementations of the DOM, XMLHttpRequest, and other low-level features that do not rely on a browser.
-
- An npm package that plugs gaps in the JavaScript implementation of a browser. See Browser Support for polyfills that support particular functionality for particular platforms.
-
In the Angular CLI, a standalone application or library that can be created or modified by an Angular CLI command.
-
A project, as generated by the ng new, contains the set of source files, resources, and configuration files that you need to develop and test the application using the Angular CLI. Projects can also be created with the ng generate application and ng generate library commands.
-
To learn more, see Project File Structure.
-
The angular.json file configures all projects in a workspace.
-
An object that implements one of the Provider interfaces. A provider object defines how to obtain an injectable dependency associated with a DI token. An injector uses the provider to create a new instance of a dependency for a class that requires it.
-
Angular registers its own providers with every injector, for services that Angular defines. You can register your own providers for services that your application needs.
-
See also service. See also dependency injection.
-
Learn more in Dependency Injection.
-
A framework for building Angular forms through code in a component. The alternative is a template-driven form.
-
When using reactive forms:
- The "source of truth", the form model, is defined in the component class.
- Validation is set up through validation functions rather than validation directives.
- Each control is explicitly created in the component class by creating a FormControl instance manually or with FormBuilder.
- The template input elements do not use ngModel.
- The associated Angular directives are prefixed with form, such as formControl, formGroup, and formControlName.
-
The alternative is a template-driven form. For an introduction and comparison of both forms approaches, see Introduction to Angular Forms.
-
A class that implements the Resolve interface that you use to produce or retrieve data that is needed before navigation to a requested route can be completed. You may use a function with the same signature as the resolve() method in place of the Resolve interface. Resolvers run after all route guards for a route tree have been executed and have succeeded.
-
See an example of using a resolve guard to retrieve dynamic data.
-
A method that controls navigation to a requested route in a routing application. Guards determine whether a route can be activated or deactivated, and whether a lazy-loaded module can be loaded.
-
Learn more in the Routing and Navigation guide.
-
A tool that configures and implements navigation among states and views within an Angular application.
-
The Router module is an NgModule that provides the necessary service providers and directives for navigating through application views. A routing component is one that imports the Router module and whose template contains a RouterOutlet element where it can display views produced by the router.
-
The router defines navigation among views on a single page, as opposed to navigation among pages. It interprets URL-like links to determine which views to create or destroy, and which components to load or unload. It allows you to take advantage of lazy loading in your Angular applications.
-
To learn more, see Routing and Navigation.
- A directive that acts as a placeholder in the template of a routing component. Angular dynamically renders the template based on the current router state.
-
An Angular component with a RouterOutlet directive in its template that displays views based on router navigations.
-
To learn more, see Routing and Navigation.
- In schematics, a function that operates on a file tree to create, delete, or modify files in a specific manner.
-
A scaffolding library that defines how to generate or transform a programming project by creating, modifying, refactoring, or moving files and code. A schematic defines rules that operate on a virtual file system referenced as a tree.
-
The Angular CLI uses schematics to generate and modify Angular projects and parts of projects.
-
Angular provides a set of schematics for use with the Angular CLI. See the Angular CLI command reference. The ng add Angular CLI command runs schematics as part of adding a library to your project. The ng generate Angular CLI command runs schematics to create applications, libraries, and Angular code constructs.
-
Library developers can create schematics that enable the Angular CLI to add and update their published libraries, and to generate artifacts the library defines. Add these schematics to the npm package that you use to publish and share your library.
-
-
To learn more, see Schematics. To learn more, see also Integrating Libraries with the CLI.
-
Schematics come with their own command-line tool. Use Node 6.9 or above to install the Schematics CLI globally.
npm install -g @angular-devkit/schematics-cli
-
This installs the
schematics
executable, which you can use to create a new schematicscollection
with an initial named schematic. The collection directory is a workspace forschematics
. You can also use the schematics command to add a new schematic to an existing collection, or extend an existing schematic.
-
A way to group related npm packages. NgModules are delivered within scoped packages whose names begin with the Angular scope name @angular. For example, @angular/core, @angular/common, @angular/forms, and @angular/router.
-
Import a scoped package in the same way that you import a normal package.
- architecture/src/app/app.component.ts (import)
import { Component } from '@angular/core';
- architecture/src/app/app.component.ts (import)
-
A technique that generates static application pages on the server, and can generate and serve those pages in response to requests from browsers. It can also pre-generate pages as HTML files that you serve later.
-
This technique can improve performance on mobile and low-powered devices and improve the user experience by showing a static first page quickly while the client-side application is loading. The static version can also make your application more visible to web crawlers.
-
You can easily prepare an application for server-side rendering by using the
Angular CLI
to run theAngular Universal
tool, using the@nguniversal/express-engine
schematic.
-
In Angular, a class with the @Injectable() decorator that encapsulates non-UI logic and code that can be reused across an application. Angular distinguishes components from services to increase modularity and reusability.
-
The @Injectable() metadata allows the service class to be used with the dependency injection mechanism. The injectable class is instantiated by a provider. Injectors maintain lists of providers and use them to provide service instances when they are required by components or other services.
-
To learn more, see Introduction to Services and Dependency Injection.
-
A configuration of components, directives, and pipes to indicate that this class can be imported directly without declaring it in any NgModule.
-
Standalone components, directives, and pipes differ from non-standalone ones by:
-
having the
standalone
field of their decorator set totrue
. -
allowing their direct importing without the need to pass through NgModules.
-
specifying their dependencies directly in their decorator.
-
-
To learn more, see the Standalone components guide.
-
A category of directive that is responsible for shaping HTML layout by modifying the DOM. Modification of the DOM includes, adding, removing, or manipulating elements and the associated children.
-
To learn more, see Structural Directives.
-
A function that defines how to obtain or generate values or messages to be published. This function is executed when a consumer runs the
subscribe()
method of an observable. -
The act of subscribing to an observable triggers its execution, associates callbacks with it, and creates a
Subscription
object that lets you unsubscribe. -
The
subscribe()
method takes an observer JavaScript object with up to three callbacks, one for each type of notification that an observable can deliver.-
The
next
notification sends a value such as a number, a string, or an object. -
The
error
notification sends a JavaScript Error or exception. -
The
complete
notification does not send a value, but the handler is run when the method completes. Scheduled values can continue to be returned after the method completes.
-
-
A buildable or runnable subset of a project, configured as an object in the workspace configuration file, and executed by an Architect builder.
-
In the angular.json file, each project has an "architect" section that contains targets which configure builders. Some of these targets correspond to Angular CLI command, such as build, serve, test, and lint.
-
For example, the Architect builder invoked by the ng build command to compile a project uses a particular build tool, and has a default configuration with values that you can override on the command line. The build target also defines an alternate configuration for a "development" build, which you can invoke with the --configuration development flag on the build command.
-
The Architect tool provides a set of builders. The ng new Angular CLI command provides a set of targets for the initial application project. The ng generate application and ng generate library Angular CLI commands provide a set of targets for each new project. These targets, their options and configurations, can be customized to meet the needs of your project. For example, you may want to add a "staging" or "testing" configuration to the "build" target of a project.
-
You can also define a custom builder, and add a target to the project configuration that uses your custom builder. You can then run the target using the ng run Angular CLI command.
-
Code that defines how to render the view of a component.
-
A template combines straight HTML with Angular data-binding syntax, directives, and template expressions (logical constructs). The Angular elements insert or calculate values that modify the HTML elements before the page is displayed. Learn more about Angular template language in the Template Syntax guide.
-
A template is associated with a component class through the @Component() decorator. The template code can be provided inline, as the value of the template property, or in a separate HTML file linked through the templateUrl property.
-
Additional templates, represented by TemplateRef objects, can define alternative or embedded views, which can be referenced from multiple components.
-
template-driven forms A format for building Angular forms using HTML forms and input elements in the view. The alternative format uses the reactive forms framework.
-
When using template-driven forms:
-
The "source of truth" is the template. The validation is defined using attributes on the individual input elements.
-
Two-way binding with ngModel keeps the component model synchronized with the user's entry into the input elements.
-
Behind the scenes, Angular creates a new control for each input element, provided you have set up a
name
attribute and two-way binding for each input. -
The associated Angular directives are prefixed with
ng
such asngForm
,ngModel
, andngModelGroup
.
-
-
The alternative is a reactive form. For an introduction and comparison of both forms approaches, see Introduction to Angular Forms.
- A TypeScript-like syntax that Angular evaluates within a data binding.
-
A variable defined in a template that references an instance associated with an element, such as a directive instance, component instance, template as in TemplateRef, or DOM element. After declaring a template reference variable on an element in a template, you can access values from that variable elsewhere within the same template. The following example defines a template reference variable named #phone.
- src/app/app.component.html
<input #phone placeholder="phone number" />
- src/app/app.component.html
-
To learn more, see Template reference variable.
-
A template input variable is a variable you can reference within a single instance of the template. You declare a template input variable using the
let
keyword as inlet customer
.<tr *ngFor="let customer of customers;"> <td>{{customer.customerNo}}</td> <td>{{customer.name}}</td> <td>{{customer.address}}</td> <td>{{customer.city}}</td> <td>{{customer.state}}</td> <button (click)="selectedCustomer=customer">Select</button> </tr>
-
Read and learn more about template input variables.
- An opaque identifier used for efficient table lookup. In Angular, a
DI token
is used to findproviders
of dependencies in thedependency injection
system.
- The translation process that transforms one version of JavaScript to another version; for example, down-leveling ES2015 to the older ES5 version.
- In schematics, a virtual file system represented by the Tree class. Schematic rules take a tree object as input, operate on them, and return a new tree object.
-
A programming language based on JavaScript that is notable for its optional typing system. TypeScript provides compile-time type checking and strong tooling support The type checking and tooling support include code completion, refactoring, inline documentation, and intelligent search. Many code editors and IDEs support TypeScript either natively or with plug-ins.
-
TypeScript is the preferred language for Angular development. To learn more about TypeScript, see typescriptlang.org.
- A file specifies the root files and the compiler options required to compile a TypeScript project. To learn more, see TypeScript configuration.
-
A data flow model where the component tree is always checked for changes in one direction from parent to child, which prevents cycles in the change detection graph.
-
In practice, this means that data in Angular flows downward during change detection. A parent component can easily change values in its child components because the parent is checked first. A failure could occur, however, if a child component tries to change a value in its parent during change detection (inverting the expected data flow), because the parent component has already been rendered. In development mode, Angular throws the ExpressionChangedAfterItHasBeenCheckedError error if your application attempts to do this, rather than silently failing to render the new value.
-
To avoid this error, a lifecycle hook method that seeks to make such a change should trigger a new change detection run. The new run follows the same direction as before, but succeeds in picking up the new value.
- A tool for implementing server-side rendering of an Angular application. When integrated with an app, Universal generates and serves static pages on the server in response to requests from browsers. The initial static page serves as a fast-loading placeholder while the full application is being prepared for normal execution in the browser. To learn more, see Angular Universal: server-side rendering.
-
The smallest grouping of display elements that can be created and destroyed together. Angular renders a view under the control of one or more
directives
. -
A
component
class and its associatedtemplate
define a view. A view is specifically represented by aViewRef
instance associated with a component. A view that belongs immediately to a component is referenced as a host view. Views are typically collected intoview hierarchies
. -
Properties of elements in a view can change dynamically, in response to user actions; the structure (number and order) of elements in a view cannot. You can change the structure of elements by inserting, moving, or removing nested views within their view containers.
-
View hierarchies can be loaded and unloaded dynamically as the user navigates through the application, typically under the control of a
router
.
- A previous compilation and rendering pipeline used by Angular. It has since been replaced by
Ivy
and is no longer in use. View Engine was deprecated in version 9 and removed in version 13.
-
A tree of related views that can be acted on as a unit. The root view referenced as the host view of a component. A host view is the root of a tree of embedded views, collected in a
ViewContainerRef
view container attached to an anchor element in the hosting component. The view hierarchy is a key part of Angular change detection. -
The view hierarchy does not imply a component hierarchy. Views that are embedded in the context of a particular hierarchy can be host views of other components. Those components can be in the same NgModule as the hosting component, or belong to other NgModules.
- See custom element.
-
A collection of Angular projects (that is, applications and libraries) powered by the Angular CLI that are typically co-located in a single source-control repository (such as git).
-
The ng new Angular CLI command creates a file system directory (the "workspace root"). In the workspace root, it also creates the workspace configuration file (angular.json) and, by default, an initial application project with the same name.
-
Commands that create or operate on applications and libraries (such as add and generate) must be executed from within a workspace directory. To learn more, see Workspace Configuration.
-
A file named angular.json at the root level of an Angular workspace provides workspace-wide and project-specific configuration defaults for build and development tools that are provided by or integrated with the Angular CLI. To learn more, see Workspace Configuration.
-
Additional project-specific configuration files are used by tools, such as package.json for the npm package manager, tsconfig.json for TypeScript transpilation, and tslint.json for TSLint. To learn more, see Workspace and Project File Structure.
-
An execution context for a set of asynchronous tasks. Useful for debugging, profiling, and testing applications that include asynchronous operations such as event processing, promises, and runs to remote servers.
-
An Angular application runs in a zone where it can respond to asynchronous events by checking for data changes and updating the information it displays by resolving data bindings.
-
A zone client can take action before and after an async operation completes.
-
Learn more about zones in this Brian Ford video.
-
Bootstraping
-
ex:
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; platformBrowserDynamic().bootstrapModule(AppModule);
- Import platformBrowserDynamic from @angular/platform-browser-dynamic.
- Bootstraps the application, using the root component from the specified NgModule.
-
-
NgModules
-
ex:
import { NgModule } from '@angular/core';
- Import NgModule from @angular/core.
-
ex:
@NgModule({ declarations: …, imports: …, exports: …, providers: …, bootstrap: … }) class MyModule {}
- Defines a module that contains components, directives, pipes, and providers.
-
ex:
declarations: [ MyRedComponent, MyBlueComponent, MyDatePipe ]
- List of components, directives, and pipes that belong to this module.
-
ex:
imports: [ BrowserModule, SomeOtherModule ]
- List of modules to import into this module. Everything from the imported modules is available to declarations of this module.
-
ex:
exports: [ MyRedComponent, MyDatePipe ]
- List of components, directives, and pipes visible to modules that import this module.
-
ex:
providers: [ MyService, { provide: … } ]
- List of dependency injection providers visible both to the contents of this module and to importers of this module.
-
ex:
bootstrap: [MyAppComponent]
- List of components to bootstrap when this module is bootstrapped.
-
-
Template syntax
-
ex:
<input [value]="firstName">
- Binds property
value
to the result of expressionfirstName
.
- Binds property
-
ex:
<div [attr.role]="myAriaRole">
- Binds attribute
role
to the result of expressionmyAriaRole
.
- Binds attribute
-
ex:
<div [class.extra-sparkle]="isDelightful">
- Binds the presence of the CSS class
extra-sparkle
on the element to the truthiness of the expressionisDelightful
.
- Binds the presence of the CSS class
-
ex:
<div [style.width.px]="mySize">
- Binds style property
width
to the result of expressionmySize
in pixels. Units are optional.
- Binds style property
-
ex:
<button (click)="readRainbow($event)">
- Calls method
readRainbow
when a click event is triggered on this button element (or its children) and passes in the event object.
- Calls method
-
ex:
<div title="Hello {{ponyName}}">
- Binds a property to an interpolated string, for example, "Hello Seabiscuit". Equivalent to:
<div [title]="'Hello ' + ponyName">
- Binds a property to an interpolated string, for example, "Hello Seabiscuit". Equivalent to:
-
ex:
<p> Hello {{ponyName}} </p>
- Binds text content to an interpolated string, for example, "Hello Seabiscuit".
-
ex:
<my-cmp [(title)]="name">
- Sets up two-way data binding. Equivalent to:
<my-cmp [title]="name" (titleChange)="name=$event">
- Sets up two-way data binding. Equivalent to:
-
ex:
<video #movieplayer …></video> <button (click)="movieplayer.play()"> Play </button>
- Creates a local variable
movieplayer
that provides access to thevideo
element instance in data-binding and event-binding expressions in the current template.
- Creates a local variable
-
ex:
<p *myUnless="myExpression"> … </p>
- The asterisk (
*
) character turns the current element into an embedded template. Equivalent to:<ng-template [myUnless]="myExpression"> <p> … </p> </ng-template>
- The asterisk (
-
ex:
<p> Card No.: {{cardNumber | myCardNumberFormatter}} </p>
- Transforms the current value of expression
cardNumber
using the pipe calledmyCardNumberFormatter
.
- Transforms the current value of expression
-
ex:
<p> Employer: {{employer?.companyName}} </p>
- The safe navigation operator (
?
) means that the employer field is optional and ifundefined
, the rest of the expression should be ignored.
- The safe navigation operator (
-
ex:
<svg:rect x="0" y="0" width="100" height="100"/>
- An SVG snippet template needs an
svg:
prefix on its root element to disambiguate the SVG element from an HTML component.
- An SVG snippet template needs an
-
ex:
<svg> <rect x="0" y="0" width="100" height="100"/> </svg>
- An
<svg>
root element is detected as an SVG element automatically, without the prefix.
- An
-
-
BUILT-IN DIRECTIVES
-
ex:
import { CommonModule } from '@angular/common';
- Import CommonModule from @angular/common.
-
ex:
<section *ngIf="showSection">
- Removes or recreates a portion of the DOM tree based on the
showSection
expression.
- Removes or recreates a portion of the DOM tree based on the
-
ex:
<li *ngFor="let item of list">
- Turns the
li
element and its contents into a template, and uses that to instantiate a view for each item in list.
- Turns the
-
ex:
<div [ngSwitch]="conditionExpression"> <ng-template [ngSwitchCase]="case1Exp"> … </ng-template> <ng-template ngSwitchCase="case2LiteralString"> … </ng-template> <ng-template ngSwitchDefault> … </ng-template> </div>
- Conditionally swaps the contents of the
div
by selecting one of the embedded templates based on the current value ofconditionExpression
.
- Conditionally swaps the contents of the
-
ex:
<div [ngClass]="{'active': isActive, 'disabled': isDisabled}">
- Binds the presence of CSS classes on the element to the truthiness of the associated map values. The right-hand expression should return
{class-name: true/false}
map.
- Binds the presence of CSS classes on the element to the truthiness of the associated map values. The right-hand expression should return
-
ex:
<div [ngStyle]="{'property': 'value'}"> <div [ngStyle]="dynamicStyles()">
- Allows you to assign styles to an HTML element using CSS. You can use CSS directly, as in the first example, or you can call a method from the component.
-
-
FORMS
-
ex:
import { FormsModule } from '@angular/forms';
- Import FormsModule from @angular/forms.
-
ex:
<input [(ngModel)]="userName">
- Provides two-way data-binding, parsing, and validation for form controls.
-
-
CLASS DECORATORS
-
ex:
import { Directive, … } from '@angular/core';
- Import
Directive, …
from @angular/core';.
- Import
-
ex:
@Component({…}) class MyComponent() {}
- Declares that a class is a component and provides metadata about the component.
-
ex:
@Directive({…}) class MyDirective() {}
- Declares that a class is a directive and provides metadata about the directive.
-
ex:
@Pipe({…}) class MyPipe() {}
- Declares that a class is a pipe and provides metadata about the pipe.
-
ex:
@Injectable() class MyService() {}
- Declares that a class can be provided and injected by other classes. Without this decorator, the compiler won't generate enough metadata to allow the class to be created properly when it's injected somewhere.
-
-
DIRECTIVE CONFIGURATION
-
ex:
@Directive({ property1: value1, … })
- Add
property1
property withvalue1
value to Directive.
- Add
-
ex:
selector: '.cool-button:not(a)'
- Specifies a CSS selector that identifies this directive within a template. Supported selectors include
element
,[attribute]
,.class
, and:not()
. - Does not support parent-child relationship selectors.
- Specifies a CSS selector that identifies this directive within a template. Supported selectors include
-
ex:
providers: [ MyService, { provide: … } ]
- List of dependency injection providers for this directive and its children.
-
-
COMPONENT CONFIGURATION
-
@COMPONENT EXTENDS @DIRECTIVE, SO THE @DIRECTIVE CONFIGURATION APPLIES TO COMPONENTS AS WELL
-
ex:
moduleId: module.id
- If set, the
templateUrl
andstyleUrl
are resolved relative to the component.
- If set, the
-
ex:
viewProviders: [MyService, { provide: … }]
- List of dependency injection providers scoped to this component's view.
-
ex:
template: 'Hello {{name}}' templateUrl: 'my-component.html'
- Inline template or external template URL of the component's view.
-
ex:
styles: ['.primary {color: red}'] styleUrls: ['my-component.css']
-
-
CLASS FIELD DECORATORS FOR DIRECTIVES AND COMPONENTS
-
ex:
import { Input, … } from '@angular/core';
- Import
Input, ...
from@angular/core
.
- Import
-
ex:
@Input() myProperty;
- Declares an input property that you can update using property binding (example:
<my-cmp [myProperty]="someExpression">
).
- Declares an input property that you can update using property binding (example:
-
ex:
@Output() myEvent = new EventEmitter();
- Declares an output property that fires events that you can subscribe to with an event binding (example:
<my-cmp (myEvent)="doSomething()">
).
- Declares an output property that fires events that you can subscribe to with an event binding (example:
-
ex:
@HostBinding('class.valid') isValid;
- Binds a host element property (here, the CSS class
valid
) to a directive/component property (isValid
).
- Binds a host element property (here, the CSS class
-
ex:
@HostListener('click', ['$event']) onClick(e) {…}
- Subscribes to a host element event (
click
) with a directive/component method (onClick
), optionally passing an argument ($event
).
- Subscribes to a host element event (
-
ex:
@ContentChild(myPredicate) myChildComponent;
- Binds the first result of the component content query (
myPredicate
) to a property (myChildComponent
) of the class.
- Binds the first result of the component content query (
-
ex:
@ContentChildren(myPredicate) myChildComponents;
- Binds the results of the component content query (
myPredicate
) to a property (myChildComponents
) of the class.
- Binds the results of the component content query (
-
ex:
@ViewChild(myPredicate) myChildComponent;
- Binds the first result of the component view query (
myPredicate
) to a property (myChildComponent
) of the class. Not available for directives.
- Binds the first result of the component view query (
-
ex:
@ViewChildren(myPredicate) myChildComponents;
- Binds the results of the component view query (
myPredicate
) to a property (myChildComponents
) of the class. Not available for directives.
- Binds the results of the component view query (
-
-
DIRECTIVE AND COMPONENT CHANGE DETECTION AND LIFECYCLE HOOKS (IMPLEMENTED AS CLASS METHODS)
-
ex:
constructor(myService: MyService, …) { … }
- Called before any other lifecycle hook. Use it to inject dependencies, but avoid any serious work here.
-
ex:
ngOnChanges(changeRecord) { … }
- Called after every change to input properties and before processing content or child views.
-
ex:
ngOnInit() { … }
- Called after the constructor, initializing input properties, and the first call to
ngOnChanges
.
- Called after the constructor, initializing input properties, and the first call to
-
ex:
ngDoCheck() { … }
- Called every time that the input properties of a component or a directive are checked. Use it to extend change detection by performing a custom check.
-
ex:
ngAfterContentInit() { … }
- Called after
ngOnInit
when the component's or directive's content has been initialized.
- Called after
-
ex:
ngAfterContentChecked() { … }
- Called after every check of the component's or directive's content.
-
ex:
ngAfterViewInit() { … }
- Called after
ngAfterContentInit
when the component's views and child views / the view that a directive is in has been initialized.
- Called after
-
ex:
ngAfterViewChecked() { … }
- Called after every check of the component's views and child views / the view that a directive is in.
-
ex:
ngOnDestroy() { … }
- Called once, before the instance is destroyed.
-
-
DEPENDENCY INJECTION CONFIGURATION
-
ex:
{ provide: MyService, useClass: MyMockService }
- Sets or overrides the provider for
MyService
to theMyMockService
class.
- Sets or overrides the provider for
-
ex:
{ provide: MyService, useFactory: myFactory }
- Sets or overrides the provider for
MyService
to themyFactory
factory function.
- Sets or overrides the provider for
-
ex:
{ provide: MyValue, useValue: 41 }
- Sets or overrides the provider for
MyValue
to the value41
.
- Sets or overrides the provider for
-
-
ROUTING AND NAVIGATION
-
ex:
import { Routes, RouterModule, … } from '@angular/router';
- Import
Routes, RouterModule, ...
from@angular/router
.
- Import
-
ex:
const routes: Routes = [ { path: '', component: HomeComponent }, { path: 'path/:routeParam', component: MyComponent }, { path: 'staticPath', component: … }, { path: '**', component: … }, { path: 'oldPath', redirectTo: '/staticPath' }, { path: …, component: …, data: { message: 'Custom' } } ]); const routing = RouterModule.forRoot(routes);
- Configures routes for the application. Supports static, parameterized, redirect, and wildcard routes. Also supports custom route data and resolve.
-
ex:
<router-outlet></router-outlet> <router-outlet name="aux"></router-outlet>
- Marks the location to load the component of the active route.
-
ex:
<a routerLink="/path"> <a [routerLink]="[ '/path', routeParam ]"> <a [routerLink]="[ '/path', { matrixParam: 'value' } ]"> <a [routerLink]="[ '/path' ]" [queryParams]="{ page: 1 }"> <a [routerLink]="[ '/path' ]" fragment="anchor">
- Creates a link to a different view based on a route instruction consisting of a route path, required and optional parameters, query parameters, and a fragment. To navigate to a root route, use the
/
prefix; for a child route, use the./
prefix; for a sibling or parent, use the ../ prefix.
- Creates a link to a different view based on a route instruction consisting of a route path, required and optional parameters, query parameters, and a fragment. To navigate to a root route, use the
-
ex:
<a [routerLink]="[ '/path' ]" routerLinkActive="active">
- The provided classes are added to the element when the
routerLink
becomes the current active route.
- The provided classes are added to the element when the
-
ex:
<a [routerLink]="[ '/path' ]" routerLinkActive="active" ariaCurrentWhenActive="page">
- The provided classes and
aria-current
attribute are added to the element when the routerLink becomes the current active route.
- The provided classes and
-
ex:
class CanActivateGuard implements CanActivate { canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable<boolean|UrlTree>|Promise<boolean|UrlTree>|boolean|UrlTree { … } } { path: …, canActivate: [CanActivateGuard] }
- An interface for defining a class that the router should call first to determine if it should activate this component. Should return a
boolean|UrlTree
or an Observable/Promise that resolves to aboolean|UrlTree
.
- An interface for defining a class that the router should call first to determine if it should activate this component. Should return a
-
ex:
class CanDeactivateGuard implements CanDeactivate<T> { canDeactivate( component: T, route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable<boolean|UrlTree>|Promise<boolean|UrlTree>|boolean|UrlTree { … } } { path: …, canDeactivate: [CanDeactivateGuard] }
- An interface for defining a class that the router should call first to determine if it should deactivate this component after a navigation. Should return a
boolean|UrlTree
or an Observable/Promise that resolves to aboolean|UrlTree
.
- An interface for defining a class that the router should call first to determine if it should deactivate this component after a navigation. Should return a
-
ex:
class CanActivateChildGuard implements CanActivateChild { canActivateChild( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable<boolean|UrlTree>|Promise<boolean|UrlTree>|boolean|UrlTree { … } } { path: …, canActivateChild: [CanActivateGuard], children: … }
- An interface for defining a class that the router should call first to determine if it should activate the child route. Should return a
boolean|UrlTree
or an Observable/Promise that resolves to aboolean|UrlTree
.
- An interface for defining a class that the router should call first to determine if it should activate the child route. Should return a
-
ex:
class ResolveGuard implements Resolve<T> { resolve( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable<any>
Promise<any>
-
ex:
class CanLoadGuard implements CanLoad { canLoad( route: Route ): Observable<boolean|UrlTree>|Promise<boolean|UrlTree>|boolean|UrlTree { … } } { path: …, canLoad: [CanLoadGuard], loadChildren: … }
- An interface for defining a class that the router should call first to check if the lazy loaded module should be loaded. Should return a
boolean|UrlTree
or an Observable/Promise that resolves to aboolean|UrlTree
.
- An interface for defining a class that the router should call first to check if the lazy loaded module should be loaded. Should return a
-
- Looking for an opinionated guide to Angular syntax, conventions, and application structure? Step right in. This style guide presents preferred conventions and, as importantly, explains why.
-
Each guideline describes either a good or bad practice, and all have a consistent presentation.
-
The wording of each guideline indicates how strong the recommendation is.
-
Do is one that should always be followed. Always might be a bit too strong of a word. Guidelines that literally should always be followed are extremely rare. On the other hand, you need a really unusual case for breaking a Do guideline.
-
Consider guidelines should generally be followed. If you fully understand the meaning behind the guideline and have a good reason to deviate, then do so. Aim to be consistent.
-
Avoid indicates something you should almost never do. Code examples to avoid have an unmistakable red header.
-
Why?
- Gives reasons for following the previous recommendations.
-
Some code examples display a file that has one or more similarly named companion files. For example,
hero.component.ts
andhero.component.html
. -
The guideline uses the shortcut
hero.component.ts|html|css|spec
to represent those various files. Using this shortcut makes this guide's file structures easier to read and more terse.
- Apply the single responsibility principle (SRP) to all components, services, and other symbols. This helps make the application cleaner, easier to read and maintain, and more testable.
-
Do define one thing, such as a service or component, per file.
-
Consider limiting files to 400 lines of code.
-
Why?
- One component per file makes it far easier to read, maintain, and avoid collisions with teams in source control.
-
Why?
- One component per file avoids hidden bugs that often arise when combining components in a file where they may share variables, create unwanted closures, or unwanted coupling with dependencies.
-
Why?
-
A single component can be the default export for its file which facilitates lazy loading with the router.
-
The key is to make the code more reusable, easier to read, and less mistake-prone.
-
-
The following negative example defines the
AppComponent
, bootstraps the app, defines theHero
model object, and loads heroes from the server all in the same file. Don't do this.-
app/heroes/hero.component.ts
/* avoid */ import { Component, NgModule, OnInit } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; interface Hero { id: number; name: string; } @Component({ selector: 'app-root', template: ` <h1>{{title}}</h1> <pre>{{heroes | json}}</pre> `, styleUrls: ['app/app.component.css'] }) class AppComponent implements OnInit { title = 'Tour of Heroes'; heroes: Hero[] = []; ngOnInit() { getHeroes().then(heroes => (this.heroes = heroes)); } } @NgModule({ imports: [BrowserModule], declarations: [AppComponent], exports: [AppComponent], bootstrap: [AppComponent] }) export class AppModule {} platformBrowserDynamic().bootstrapModule(AppModule); const HEROES: Hero[] = [ { id: 1, name: 'Bombasto' }, { id: 2, name: 'Tornado' }, { id: 3, name: 'Magneta' } ]; function getHeroes(): Promise<Hero[]> { return Promise.resolve(HEROES); // TODO: get hero data from the server; }
-
-
It is a better practice to redistribute the component and its supporting classes into their own, dedicated files.
-
main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; platformBrowserDynamic().bootstrapModule(AppModule);
-
app/app.module.ts
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { RouterModule } from '@angular/router'; import { AppComponent } from './app.component'; import { HeroesComponent } from './heroes/heroes.component'; @NgModule({ imports: [ BrowserModule, ], declarations: [ AppComponent, HeroesComponent ], exports: [ AppComponent ], bootstrap: [ AppComponent ] }) export class AppModule { }
-
app/app.component.ts
import { Component } from '@angular/core'; import { HeroService } from './heroes'; @Component({ selector: 'toh-app', template: ` <toh-heroes></toh-heroes> `, styleUrls: ['./app.component.css'], providers: [HeroService] }) export class AppComponent {}
-
app/heroes/heroes.component.ts
import { Component, OnInit } from '@angular/core'; import { Hero, HeroService } from './shared'; @Component({ selector: 'toh-heroes', template: ` <pre>{{heroes | json}}</pre> ` }) export class HeroesComponent implements OnInit { heroes: Hero[] = []; constructor(private heroService: HeroService) {} ngOnInit() { this.heroService.getHeroes() .then(heroes => this.heroes = heroes); } }
-
app/heroes/shared/hero.service.ts
import { Injectable } from '@angular/core'; import { HEROES } from './mock-heroes'; @Injectable() export class HeroService { getHeroes() { return Promise.resolve(HEROES); } }
-
app/heroes/shared/hero.model.ts
export interface Hero { id: number; name: string; }
-
app/heroes/shared/mock-heroes.ts
import { Hero } from './hero.model'; export const HEROES: Hero[] = [ { id: 1, name: 'Bombasto' }, { id: 2, name: 'Tornado' }, { id: 3, name: 'Magneta' } ];
-
-
As the application grows, this rule becomes even more important.
-
Do define small functions
-
Consider limiting to no more than 75 lines.
-
Why?
- Small functions are easier to test, especially when they do one thing and serve one purpose.
-
Why?
- Small functions promote reuse.
-
Why?
- Small functions are easier to read.
-
Why?
- Small functions are easier to maintain.
-
Why?
- Small functions help avoid hidden bugs that come with large functions that share variables with external scope, create unwanted closures, or unwanted coupling with dependencies.
Naming conventions are hugely important to maintainability and readability. This guide recommends naming conventions for the file name and the symbol name.
-
Do use consistent names for all symbols.
-
Do follow a pattern that describes the symbol's feature then its type. The recommended pattern is
feature.type.ts
. -
Why?
- Naming conventions help provide a consistent way to find content at a glance. Consistency within the project is vital. Consistency with a team is important. Consistency across a company provides tremendous efficiency.
-
Why?
- The naming conventions should help find desired code faster and make it easier to understand.
-
Why?
- Names of folders and files should clearly convey their intent. For example,
app/heroes/hero-list.component.ts
may contain a component that manages a list of heroes.
- Names of folders and files should clearly convey their intent. For example,
-
Do use dashes to separate words in the descriptive name.
-
Do use dots to separate the descriptive name from the type.
-
Do use consistent type names for all components following a pattern that describes the component's feature then its type. A recommended pattern is
feature.type.ts
. -
Do use conventional type names including
.service
,.component
,.pipe
,.module
, and.directive
. Invent additional type names if you must but take care not to create too many. -
Why?
- Type names provide a consistent way to quickly identify what is in the file.
-
Why?
- Type names make it easy to find a specific file type using an editor or IDE's fuzzy search techniques.
-
Why?
- Unabbreviated type names such as
.service
are descriptive and unambiguous. Abbreviations such as.srv
,.svc
, and.serv
can be confusing.
- Unabbreviated type names such as
-
Why?
- Type names provide pattern matching for any automated tasks.
-
Do use consistent names for all assets named after what they represent.
-
Do use upper camel case for class names.
-
Do match the name of the symbol to the name of the file.
-
Do append the symbol name with the conventional suffix (such as
Component
,Directive
,Module
,Pipe
, orService
) for a thing of that type. -
Do give the filename the conventional suffix (such as
.component.ts
,.directive.ts
,.module.ts
,.pipe.ts
, or.service.ts
) for a file of that type. -
Why?
- Consistent conventions make it easy to quickly identify and reference assets of different types.
-
SYMBOL NAME | FILE NAME
-
ex:
@Component({ … }) export class AppComponent { }
- app.component.ts
-
ex:
@Component({ … }) export class HeroesComponent { }
- heroes.component.ts
-
ex:
@Component({ … }) export class HeroListComponent { }
- hero-list.component.ts
-
ex:
@Component({ … }) export class HeroDetailComponent { }
- hero-detail.component.ts
-
ex:
@Directive({ … }) export class ValidationDirective { }
- validation.directive.ts
-
ex:
@NgModule({ … }) export class AppModule
- app.module.ts
-
ex:
@Pipe({ name: 'initCaps' }) export class InitCapsPipe implements PipeTransform { }
- init-caps.pipe.ts
-
ex:
@Injectable() export class UserProfileService { }
- user-profile.service.ts
-
-
Do use consistent names for all services named after their feature.
-
Do suffix a service class name with
Service
. For example, something that gets data or heroes should be called aDataService
or aHeroService
. -
A few terms are unambiguously services. They typically indicate agency by ending in "-er". You may prefer to name a service that logs messages
Logger
rather thanLoggerService
. Decide if this exception is agreeable in your project. As always, strive for consistency. -
Why?
- Provides a consistent way to quickly identify and reference services.
-
Why?
- Clear service names such as
Logger
do not require a suffix.
- Clear service names such as
-
Why?
- Service names such as
Credit
are nouns and require a suffix and should be named with a suffix when it is not obvious if it is a service or something else.
- Service names such as
-
SYMBOL NAME | FILE NAME
- ex:
@Injectable() export class HeroDataService { }
- hero-data.service.ts
- ex:
@Injectable() export class CreditService { }
- credit.service.ts
- ex:
@Injectable() export class Logger { }
- logger.service.ts
- ex:
-
Do put bootstrapping and platform logic for the application in a file named
main.ts
. -
Do include error handling in the bootstrapping logic.
-
Avoid putting application logic in
main.ts
. Instead, consider placing it in a component or service. -
Why?
- Follows a consistent convention for the startup logic of an app.
-
Why?
- Follows a familiar convention from other technology platforms.
-
main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; platformBrowserDynamic().bootstrapModule(AppModule) .then(success => console.log(`Bootstrap success`)) .catch(err => console.error(err));
-
Do use dashed-case or kebab-case for naming the element selectors of components.
-
Why?
- Keeps the element names consistent with the specification for Custom Elements
-
app/heroes/shared/hero-button/hero-button.component.ts
/* avoid */ @Component({ selector: 'tohHeroButton', templateUrl: './hero-button.component.html' }) export class HeroButtonComponent {}
-
app/heroes/shared/hero-button/hero-button.component.ts
@Component({ selector: 'toh-hero-button', templateUrl: './hero-button.component.html' }) export class HeroButtonComponent {}
-
app/app.component.html
<toh-hero-button></toh-hero-button>
-
Do use a hyphenated, lowercase element selector value; for example,
admin-users
. -
Do use a custom prefix for a component selector. For example, the prefix
toh
represents Tour of Heroes and the prefixadmin
represents an admin feature area. -
Do use a prefix that identifies the feature area or the application itself.
-
Why?
- Prevents element name collisions with components in other applications and with native HTML elements.
-
Why?
- Makes it easier to promote and share the component in other applications.
-
Why?
- Components are easy to identify in the DOM.
-
app/heroes/hero.component.ts
/* avoid */ // HeroComponent is in the Tour of Heroes feature @Component({ selector: 'hero' }) export class HeroComponent {}
-
app/users/users.component.ts
/* avoid */ // UsersComponent is in an Admin feature @Component({ selector: 'users' }) export class UsersComponent {}
-
app/heroes/hero.component.ts
@Component({ selector: 'toh-hero' }) export class HeroComponent {}
-
app/users/users.component.ts
@Component({ selector: 'admin-users' }) export class UsersComponent {}
-
Do Use lower camel case for naming the selectors of directives.
-
Why?
- Keeps the names of the properties defined in the directives that are bound to the view consistent with the attribute names.
-
Why?
- The Angular HTML parser is case-sensitive and recognizes lower camel case.
-
Do use a custom prefix for the selector of directives (for example, the prefix
toh
from Tour of Heroes). -
Do spell non-element selectors in lower camel case unless the selector is meant to match a native HTML attribute.
-
Don't prefix a directive name with ng because that prefix is reserved for Angular and using it could cause bugs that are difficult to diagnose.
-
Why?
- Prevents name collisions.
-
Why?
- Directives are easily identified.
-
app/shared/validate.directive.ts
/* avoid */ @Directive({ selector: '[validate]' }) export class ValidateDirective {}
-
app/shared/validate.directive.ts
@Directive({ selector: '[tohValidate]' }) export class ValidateDirective {}
-
Do use consistent names for all pipes, named after their feature. The pipe class name should use
UpperCamelCase
(the general convention for class names), and the correspondingname
string should use lowerCamelCase. Thename
string cannot use hyphens ("dash-case" or "kebab-case"). -
Why?
- Provides a consistent way to quickly identify and reference pipes.
-
SYMBOL NAME | FILE NAME
-
ex:
@Pipe({ name: 'ellipsis' }) export class EllipsisPipe implements PipeTransform { }
- ellipsis.pipe.ts
-
ex:
@Pipe({ name: 'initCaps' }) export class InitCapsPipe implements PipeTransform { }
- init-caps.pipe.ts
-
-
Do name test specification files the same as the component they test.
-
Do name test specification files with a suffix of
.spec
. -
Why?
- Provides a consistent way to quickly identify tests.
-
Why?
- Provides pattern matching for
karma
or other test runners.
- Provides pattern matching for
-
TEST TYPE | FILE NAMES
- Components
- heroes.component.spec.ts
- hero-list.component.spec.ts
- hero-detail.component.spec.ts
- Services
- logger.service.spec.ts
- hero.service.spec.ts
- filter-text.service.spec.ts
- Pipes
- ellipsis.pipe.spec.ts
- init-caps.pipe.spec.ts
- Components
-
Do name end-to-end test specification files after the feature they test with a suffix of
.e2e-spec
. -
Why?
- Provides a consistent way to quickly identify end-to-end tests.
-
Why?
- Provides pattern matching for test runners and build automation.
-
TEST TYPE | FILE NAMES
- End-to-End Tests
- app.e2e-spec.ts
- heroes.e2e-spec.ts
- End-to-End Tests
-
Do append the symbol name with the suffix
Module
. -
Do give the file name the
.module.ts
extension. -
Do name the module after the feature and folder it resides in.
-
Why?
- Provides a consistent way to quickly identify and reference modules.
-
Why?
- Upper camel case is conventional for identifying objects that can be instantiated using a constructor.
-
Why?
- Easily identifies the module as the root of the same named feature.
-
Do suffix a
RoutingModule
class name withRoutingModule
. -
Do end the filename of a
RoutingModule
with-routing.module.ts
. -
Why?
- A
RoutingModule
is a module dedicated exclusively to configuring the Angular router. A consistent class and file name convention make these modules easy to spot and verify.
- A
-
SYMBOL NAME | FILE NAME
-
ex:
@NgModule({ … }) export class AppModule { }
- app.module.ts
-
ex:
@NgModule({ … }) export class HeroesModule { }
- heroes.module.ts
-
ex:
@NgModule({ … }) export class VillainsModule { }
- villains.module.ts
-
ex:
@NgModule({ … }) export class AppRoutingModule { }
- app-routing.module.ts
-
ex:
@NgModule({ … }) export class HeroesRoutingModule { }
- heroes-routing.module.ts
-
-
Have a near-term view of implementation and a long-term vision. Start small but keep in mind where the application is heading.
-
All of the application's code goes in a folder named
src
. All feature areas are in their own folder, with their own NgModule. -
All content is one asset per file. Each component, service, and pipe is in its own file. All third party vendor scripts are stored in another folder and not in the
src
folder. You didn't write them and you don't want them clutteringsrc
. Use the naming conventions for files in this guide.
-
Do structure the application such that you can Locate code quickly, Identify the code at a glance, keep the Flattest structure you can, and Try to be DRY.
-
Do define the structure to follow these four basic guidelines, listed in order of importance.
-
Why?
- LIFT provides a consistent structure that scales well, is modular, and makes it easier to increase developer efficiency by finding code quickly. To confirm your intuition about a particular structure, ask: Can I quickly open and start work in all of the related files for this feature?
-
Do make locating code intuitive and fast.
-
Why?
- To work efficiently you must be able to find files quickly, especially when you do not know (or do not remember) the file names. Keeping related files near each other in an intuitive location saves time. A descriptive folder structure makes a world of difference to you and the people who come after you.
-
Do name the file such that you instantly know what it contains and represents.
-
Do be descriptive with file names and keep the contents of the file to exactly one component.
-
Avoid files with multiple components, multiple services, or a mixture.
-
Why?
- Spend less time hunting and pecking for code, and become more efficient. Longer file names are far better than short-but-obscure abbreviated names.
-
It may be advantageous to deviate from the one-thing-per-file rule when you have a set of small, closely-related features that are better discovered and understood in a single file than as multiple files. Be wary of this loophole.
-
Do keep a flat folder structure as long as possible.
-
Consider creating sub-folders when a folder reaches seven or more files.
-
Consider configuring the IDE to hide distracting, irrelevant files such as generated
.js
and.js.map
files. -
Why?
- No one wants to search for a file through seven levels of folders. A flat structure is easy to scan.
-
On the other hand, psychologists believe that humans start to struggle when the number of adjacent interesting things exceeds nine. So when a folder has ten or more files, it may be time to create subfolders.
-
Base your decision on your comfort level. Use a flatter structure until there is an obvious value to creating a new folder.
-
Do be DRY (Don't Repeat Yourself).
-
Avoid being so DRY that you sacrifice readability.
-
Why?
- Being DRY is important, but not crucial if it sacrifices the other elements of LIFT. That's why it's called T-DRY. For example, it's redundant to name a template
hero-view.component.html
because with the.html
extension, it is obviously a view. But if something is not obvious or departs from a convention, then spell it out.
- Being DRY is important, but not crucial if it sacrifices the other elements of LIFT. That's why it's called T-DRY. For example, it's redundant to name a template
-
Do start small but keep in mind where the application is heading down the road.
-
Do have a near term view of implementation and a long term vision.
-
Do put all of the application's code in a folder named
src
. -
Consider creating a folder for a component when it has multiple accompanying files (
.ts
,.html
,.css
, and.spec
). -
Why?
- Helps keep the application structure small and easy to maintain in the early stages, while being easy to evolve as the application grows.
-
Why?
- Components often have four files (for example,
*.html
,*.css
,*.ts
, and*.spec.ts
) and can clutter a folder quickly.
- Components often have four files (for example,
-
Here is a compliant folder and file structure:
<project root> src app core exception.service.ts|spec.ts user-profile.service.ts|spec.ts heroes hero hero.component.ts|html|css|spec.ts hero-list hero-list.component.ts|html|css|spec.ts shared hero-button.component.ts|html|css|spec.ts hero.model.ts hero.service.ts|spec.ts heroes.component.ts|html|css|spec.ts heroes.module.ts heroes-routing.module.ts shared shared.module.ts init-caps.pipe.ts|spec.ts filter-text.component.ts|spec.ts filter-text.service.ts|spec.ts villains villain … villain-list … shared … villains.component.ts|html|css|spec.ts villains.module.ts villains-routing.module.ts app.component.ts|html|css|spec.ts app.module.ts app-routing.module.ts main.ts index.html … node_modules/… …
-
While components in dedicated folders are widely preferred, another option for small applications is to keep components flat (not in a dedicated folder). This adds up to four files to the existing folder, but also reduces the folder nesting. Whatever you choose, be consistent.
-
Do create folders named for the feature area they represent.
-
Why?
- A developer can locate the code and identify what each file represents at a glance. The structure is as flat as it can be and there are no repetitive or redundant names.
-
Why?
- The LIFT guidelines are all covered.
-
Why?
- Helps reduce the application from becoming cluttered through organizing the content and keeping them aligned with the LIFT guidelines.
-
Why?
- When there are a lot of files, for example 10+, locating them is easier with a consistent folder structure and more difficult in a flat structure.
-
Do create an NgModule for each feature area.
-
Why?
- NgModules make it easy to lazy load routable features.
-
Why?
- NgModules make it easier to isolate, test, and reuse features.
-
For more information, refer to this folder and file structure example.
-
Do create an NgModule in the application's root folder, for example, in
/src/app
. -
Why?
- Every application requires at least one root NgModule.
-
Consider naming the root module
app.module.ts
. -
Why?
- Makes it easier to locate and identify the root module.
-
app/app.module.ts
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; import { HeroesComponent } from './heroes/heroes.component'; @NgModule({ imports: [ BrowserModule, ], declarations: [ AppComponent, HeroesComponent ], exports: [ AppComponent ] }) export class AppModule {}
-
Do create an NgModule for all distinct features in an application; for example, a
Heroes
feature. -
Do place the feature module in the same named folder as the feature area; for example, in
app/heroes
. -
Do name the feature module file reflecting the name of the feature area and folder; for example,
app/heroes/heroes.module.ts
. -
Do name the feature module symbol reflecting the name of the feature area, folder, and file; for example,
app/heroes/heroes.module.ts
definesHeroesModule
. -
Why?
- A feature module can expose or hide its implementation from other modules.
-
Why?
- A feature module identifies distinct sets of related components that comprise the feature area.
-
Why? A feature module can easily be routed to both eagerly and lazily.
-
Why?
- A feature module defines clear boundaries between specific functionality and other application features.
-
Why?
- A feature module helps clarify and make it easier to assign development responsibilities to different teams.
-
Why?
- A feature module can easily be isolated for testing.
-
Do create a feature module named
SharedModule
in ashared
folder; for example,app/shared/shared.module.ts
definesSharedModule
. -
Do declare components, directives, and pipes in a shared module when those items will be re-used and referenced by the components declared in other feature modules.
-
Consider using the name
SharedModule
when the contents of a shared module are referenced across the entire application. -
Consider not providing services in shared modules. Services are usually singletons that are provided once for the entire application or in a particular feature module. There are exceptions, however. For example, in the sample code that follows, notice that the
SharedModule
providesFilterTextService
. This is acceptable here because the service is stateless;that is, the consumers of the service aren't impacted by new instances. -
Do import all modules required by the assets in the
SharedModule
; for example,CommonModule
andFormsModule
. -
Why?
SharedModule
will contain components, directives, and pipes that may need features from another common module; for example,ngFor
inCommonModule
.
-
Do declare all components, directives, and pipes in the
SharedModule
. -
Do export all symbols from the
SharedModule
that other feature modules need to use. -
Why?
SharedModule
exists to make commonly used components, directives, and pipes available for use in the templates of components in many other modules.
-
Avoid specifying app-wide singleton providers in a SharedModule. Intentional singletons are OK. Take care.
-
Why?
- A lazy loaded feature module that imports that shared module will make its own copy of the service and likely have undesirable results.
-
Why?
- You don't want each module to have its own separate instance of singleton services. Yet there is a real danger of that happening if the
SharedModule
provides a service.
src app shared shared.module.ts init-caps.pipe.ts|spec.ts filter-text.component.ts|spec.ts filter-text.service.ts|spec.ts app.component.ts|html|css|spec.ts app.module.ts app-routing.module.ts main.ts index.html …
- You don't want each module to have its own separate instance of singleton services. Yet there is a real danger of that happening if the
-
app/shared/shared.module.ts
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { FilterTextComponent } from './filter-text/filter-text.component'; import { FilterTextService } from './filter-text/filter-text.service'; import { InitCapsPipe } from './init-caps.pipe'; @NgModule({ imports: [CommonModule, FormsModule], declarations: [ FilterTextComponent, InitCapsPipe ], providers: [FilterTextService], exports: [ CommonModule, FormsModule, FilterTextComponent, InitCapsPipe ] }) export class SharedModule { }
-
app/shared/init-caps.pipe.ts
import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'initCaps' }) export class InitCapsPipe implements PipeTransform { transform = (value: string) => value; }
-
app/shared/filter-text/filter-text.component.ts
import { Component, EventEmitter, Output } from '@angular/core'; @Component({ selector: 'toh-filter-text', template: '<input type="text" id="filterText" [(ngModel)]="filter" (keyup)="filterChanged($event)" />' }) export class FilterTextComponent { @Output() changed: EventEmitter<string>; filter = ''; constructor() { this.changed = new EventEmitter<string>(); } clear() { this.filter = ''; } filterChanged(event: any) { event.preventDefault(); console.log(`Filter Changed: ${this.filter}`); this.changed.emit(this.filter); } }
-
app/shared/filter-text/filter-text.service.ts
import { Injectable } from '@angular/core'; @Injectable() export class FilterTextService { constructor() { console.log('Created an instance of FilterTextService'); } filter(data: string, props: Array<string>, originalList: Array<any>) { let filteredList: any[]; if (data && props && originalList) { data = data.toLowerCase(); const filtered = originalList.filter(item => { let match = false; for (const prop of props) { if (item[prop].toString().toLowerCase().indexOf(data) > -1) { match = true; break; } } return match; }); filteredList = filtered; } else { filteredList = originalList; } return filteredList; } }
-
app/heroes/heroes.component.ts
import { Component } from '@angular/core'; import { FilterTextService } from '../shared/filter-text/filter-text.service'; @Component({ selector: 'toh-heroes', templateUrl: './heroes.component.html' }) export class HeroesComponent { heroes = [ { id: 1, name: 'Windstorm' }, { id: 2, name: 'Bombasto' }, { id: 3, name: 'Magneta' }, { id: 4, name: 'Tornado' } ]; filteredHeroes = this.heroes; constructor(private filterService: FilterTextService) { } filterChanged(searchText: string) { this.filteredHeroes = this.filterService.filter(searchText, ['id', 'name'], this.heroes); } }
-
app/heroes/heroes.component.html
<div>This is heroes component</div> <ul> <li *ngFor="let hero of filteredHeroes"> {{hero.name}} </li> </ul> <toh-filter-text (changed)="filterChanged($event)"></toh-filter-text>
-
A distinct application feature or workflow may be lazy loaded or loaded on demand rather than when the application starts.
-
Do put the contents of lazy loaded features in a lazy loaded folder. A typical lazy loaded folder contains a routing component, its child components, and their related assets and modules.
-
Why?
- The folder makes it easy to identify and isolate the feature content.
-
Avoid allowing modules in sibling and parent folders to directly import a module in a lazy loaded feature.
-
Why?
- Directly importing and using a module will load it immediately when the intention is to load it on demand.
-
Avoid adding filtering or sorting logic into custom pipes.
-
Do pre-compute the filtering and sorting logic in components or services before binding the model in templates.
-
Why?
- Filtering and especially sorting are expensive operations. As Angular can call pipe methods many times per second, sorting and filtering operations can degrade the user experience severely for even moderately-sized lists.
-
Consider giving components an element selector, as opposed to attribute or class selectors.
-
Why?
- Components have templates containing HTML and optional Angular template syntax. They display content. Developers place components on the page as they would native HTML elements and web components.
-
Why?
- It is easier to recognize that a symbol is a component by looking at the template's html.
-
There are a few cases where you give a component an attribute, such as when you want to augment a built-in element. For example,
Material Design
uses this technique with<button mat-button>
. However, you wouldn't use this technique on a custom element.-
app/heroes/hero-button/hero-button.component.ts
/* avoid */ @Component({ selector: '[tohHeroButton]', templateUrl: './hero-button.component.html' }) export class HeroButtonComponent {}
-
app/app.component.html
<!-- avoid --> <div tohHeroButton></div>
-
app/heroes/shared/hero-button/hero-button.component.ts
@Component({ selector: 'toh-hero-button', templateUrl: './hero-button.component.html' }) export class HeroButtonComponent {}
-
app/app.component.html
<toh-hero-button></toh-hero-button>
-
-
Do extract templates and styles into a separate file, when more than 3 lines.
-
Do name the template file
[component-name].component.html
, where [component-name] is the component name. -
Do name the style file
[component-name].component.css
, where [component-name] is the component name. -
Do specify component-relative URLs, prefixed with
./
. -
Why?
- Large, inline templates and styles obscure the component's purpose and implementation, reducing readability and maintainability.
-
Why?
- In most editors, syntax hints and code snippets aren't available when developing inline templates and styles. The Angular TypeScript Language Service (forthcoming) promises to overcome this deficiency for HTML templates in those editors that support it; it won't help with CSS styles.
-
Why?
-
A component relative URL requires no change when you move the component files, as long as the files stay together.
-
Why?
- The
./
prefix is standard syntax for relative URLs; don't depend on Angular's current ability to do without that prefix.
- The
-
app/heroes/heroes.component.ts
/* avoid */ @Component({ selector: 'toh-heroes', template: ` <div> <h2>My Heroes</h2> <ul class="heroes"> <li *ngFor="let hero of heroes | async" (click)="selectedHero=hero"> <span class="badge">{{hero.id}}</span> {{hero.name}} </li> </ul> <div *ngIf="selectedHero"> <h2>{{selectedHero.name | uppercase}} is my hero</h2> </div> </div> `, styles: [` .heroes { margin: 0 0 2em 0; list-style-type: none; padding: 0; width: 15em; } .heroes li { cursor: pointer; position: relative; left: 0; background-color: #EEE; margin: .5em; padding: .3em 0; height: 1.6em; border-radius: 4px; } .heroes .badge { display: inline-block; font-size: small; color: white; padding: 0.8em 0.7em 0 0.7em; background-color: #607D8B; line-height: 1em; position: relative; left: -1px; top: -4px; height: 1.8em; margin-right: .8em; border-radius: 4px 0 0 4px; } `] }) export class HeroesComponent { heroes: Observable<Hero[]>; selectedHero!: Hero; constructor(private heroService: HeroService) { this.heroes = this.heroService.getHeroes(); } }
-
Solution:
-
app/heroes/heroes.component.ts
@Component({ selector: 'toh-heroes', templateUrl: './heroes.component.html', styleUrls: ['./heroes.component.css'] }) export class HeroesComponent { heroes: Observable<Hero[]>; selectedHero!: Hero; constructor(private heroService: HeroService) { this.heroes = this.heroService.getHeroes(); } }
-
app/heroes/heroes.component.html
<div> <h2>My Heroes</h2> <ul class="heroes"> <li *ngFor="let hero of heroes | async"> <button type="button" (click)="selectedHero=hero"> <span class="badge">{{hero.id}}</span> <span class="name">{{hero.name}}</span> </button> </li> </ul> <div *ngIf="selectedHero"> <h2>{{selectedHero.name | uppercase}} is my hero</h2> </div> </div>
-
app/heroes/heroes.component.css
.heroes { margin: 0 0 2em 0; list-style-type: none; padding: 0; width: 15em; } .heroes li { display: flex; } .heroes button { flex: 1; cursor: pointer; position: relative; left: 0; background-color: #EEE; margin: .5em; padding: 0; border-radius: 4px; display: flex; align-items: stretch; height: 1.8em; } .heroes button:hover { color: #2c3a41; background-color: #e6e6e6; left: .1em; } .heroes button:active { background-color: #525252; color: #fafafa; } .heroes button.selected { background-color: black; color: white; } .heroes button.selected:hover { background-color: #505050; color: white; } .heroes button.selected:active { background-color: black; color: white; } .heroes .badge { display: inline-block; font-size: small; color: white; padding: 0.8em 0.7em 0 0.7em; background-color: #405061; line-height: 1em; margin-right: .8em; border-radius: 4px 0 0 4px; } .heroes .name { align-self: center; }
-
-
Do use the
@Input()
and@Output()
class decorators instead of the inputs and outputs properties of the@Directive
and@Component
metadata: -
Consider placing
@Input()
or@Output()
on the same line as the property it decorates. -
Why?
- It is easier and more readable to identify which properties in a class are inputs or outputs.
-
Why?
- If you ever need to rename the property or event name associated with @Input() or @Output(), you can modify it in a single place.
-
Why?
- The metadata declaration attached to the directive is shorter and thus more readable.
-
Why?
- Placing the decorator on the same line usually makes for shorter code and still easily identifies the property as an input or output. Put it on the line above when doing so is clearly more readable.
-
app/heroes/shared/hero-button/hero-button.component.ts
/* avoid */ @Component({ selector: 'toh-hero-button', template: `<button type="button"></button>`, inputs: [ 'label' ], outputs: [ 'heroChange' ] }) export class HeroButtonComponent { heroChange = new EventEmitter<any>(); label: string; }
-
Solution:
- app/heroes/shared/hero-button/hero-button.component.ts
@Component({ selector: 'toh-hero-button', template: `<button type="button">{{label}}</button>` }) export class HeroButtonComponent { @Output() heroChange = new EventEmitter<any>(); @Input() label = ''; }
- app/heroes/shared/hero-button/hero-button.component.ts
-
Avoid input and output aliases except when it serves an important purpose.
-
Why?
-
Two names for the same property (one private, one public) is inherently confusing.
-
Why?
-
You should use an alias when the directive name is also an input property, and the directive name doesn't describe the property.
-
app/heroes/shared/hero-button/hero-button.component.ts
/* avoid pointless aliasing */ @Component({ selector: 'toh-hero-button', template: `<button type="button">{{label}}</button>` }) export class HeroButtonComponent { // Pointless aliases @Output('heroChangeEvent') heroChange = new EventEmitter<any>(); @Input('labelAttribute') label: string; }
-
app/app.component.html
<!-- avoid --> <toh-hero-button labelAttribute="OK" (changeEvent)="doSomething()"> </toh-hero-button>
-
Solution:
-
app/heroes/shared/hero-button/hero-button.component.ts
@Component({ selector: 'toh-hero-button', template: `<button type="button" >{{label}}</button>` }) export class HeroButtonComponent { // No aliases @Output() heroChange = new EventEmitter<any>(); @Input() label = ''; }
-
app/heroes/shared/hero-button/hero-highlight.directive.ts
import { Directive, ElementRef, Input, OnChanges } from '@angular/core'; @Directive({ selector: '[heroHighlight]' }) export class HeroHighlightDirective implements OnChanges { // Aliased because `color` is a better property name than `heroHighlight` @Input('heroHighlight') color = ''; constructor(private el: ElementRef) {} ngOnChanges() { this.el.nativeElement.style.backgroundColor = this.color || 'yellow'; } }
-
app/app.component.html
<toh-hero-button label="OK" (change)="doSomething()"> </toh-hero-button> <!-- `heroHighlight` is both the directive name and the data-bound aliased property name --> <h3 heroHighlight="skyblue">The Great Bombasto</h3>
-
-
Do place properties up top followed by methods.
-
Do place private members after public members, alphabetized.
-
Why?
- Placing members in a consistent sequence makes it easy to read and helps instantly identify which members of the component serve which purpose.
-
app/shared/toast/toast.component.ts
/* avoid */ export class ToastComponent implements OnInit { private defaults = { title: '', message: 'May the Force be with you' }; message: string; title: string; private toastElement: any; ngOnInit() { this.toastElement = document.getElementById('toh-toast'); } // private methods private hide() { this.toastElement.style.opacity = 0; window.setTimeout(() => this.toastElement.style.zIndex = 0, 400); } activate(message = this.defaults.message, title = this.defaults.title) { this.title = title; this.message = message; this.show(); } private show() { console.log(this.message); this.toastElement.style.opacity = 1; this.toastElement.style.zIndex = 9999; window.setTimeout(() => this.hide(), 2500); } }
-
Solution:
-
app/shared/toast/toast.component.ts
export class ToastComponent implements OnInit { // public properties message = ''; title = ''; // private fields private defaults = { title: '', message: 'May the Force be with you' }; private toastElement: any; // public methods activate(message = this.defaults.message, title = this.defaults.title) { this.title = title; this.message = message; this.show(); } ngOnInit() { this.toastElement = document.getElementById('toh-toast'); } // private methods private hide() { this.toastElement.style.opacity = 0; window.setTimeout(() => this.toastElement.style.zIndex = 0, 400); } private show() { console.log(this.message); this.toastElement.style.opacity = 1; this.toastElement.style.zIndex = 9999; window.setTimeout(() => this.hide(), 2500); } }
-
-
Do limit logic in a component to only that required for the view. All other logic should be delegated to services.
-
Do move reusable logic to services and keep components simple and focused on their intended purpose.
-
Why?
- Logic may be reused by multiple components when placed within a service and exposed as a function.
-
Why?
- Logic in a service can more easily be isolated in a unit test, while the calling logic in the component can be easily mocked.
-
Why?
- Removes dependencies and hides implementation details from the component.
-
Why?
- Keeps the component slim, trim, and focused.
-
app/heroes/hero-list/hero-list.component.ts
/* avoid */ import { OnInit } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { catchError, finalize } from 'rxjs/operators'; import { Hero } from '../shared/hero.model'; const heroesUrl = 'http://angular.io'; export class HeroListComponent implements OnInit { heroes: Hero[]; constructor(private http: HttpClient) {} getHeroes() { this.heroes = []; this.http.get(heroesUrl).pipe( catchError(this.catchBadResponse), finalize(() => this.hideSpinner()) ).subscribe((heroes: Hero[]) => this.heroes = heroes); } ngOnInit() { this.getHeroes(); } private catchBadResponse(err: any, source: Observable<any>) { // log and handle the exception return new Observable(); } private hideSpinner() { // hide the spinner } }
-
Solution:
-
app/heroes/hero-list/hero-list.component.ts
import { Component, OnInit } from '@angular/core'; import { Hero, HeroService } from '../shared'; @Component({ selector: 'toh-hero-list', template: `...` }) export class HeroListComponent implements OnInit { heroes: Hero[] = []; constructor(private heroService: HeroService) {} getHeroes() { this.heroes = []; this.heroService.getHeroes() .subscribe(heroes => this.heroes = heroes); } ngOnInit() { this.getHeroes(); } }
-
-
Do name events without the prefix
on
. -
Do name event handler methods with the prefix
on
followed by the event name. -
Why?
- This is consistent with built-in events such as button clicks.
-
Why?
- Angular allows for an alternative syntax
on-*
. If the event itself was prefixed withon
this would result in an on-onEvent binding expression.
- Angular allows for an alternative syntax
-
app/heroes/hero.component.ts
/* avoid */ @Component({ selector: 'toh-hero', template: `...` }) export class HeroComponent { @Output() onSavedTheDay = new EventEmitter<boolean>(); }
-
app/app.component.html
<!-- avoid --> <toh-hero (onSavedTheDay)="onSavedTheDay($event)"></toh-hero>
-
Solution:
-
app/heroes/hero.component.ts
export class HeroComponent { @Output() savedTheDay = new EventEmitter<boolean>(); }
-
app/app.component.html
<toh-hero (savedTheDay)="onSavedTheDay($event)"></toh-hero>
-
-
Do put presentation logic in the component class, and not in the template.
-
Why?
- Logic will be contained in one place (the component class) instead of being spread in two places.
-
Why?
- Keeping the component's presentation logic in the class instead of the template improves testability, maintainability, and reusability.
-
app/heroes/hero-list/hero-list.component.ts
/* avoid */ @Component({ selector: 'toh-hero-list', template: ` <section> Our list of heroes: <toh-hero *ngFor="let hero of heroes" [hero]="hero"> </toh-hero> Total powers: {{totalPowers}}<br> Average power: {{totalPowers / heroes.length}} </section> ` }) export class HeroListComponent { heroes: Hero[]; totalPowers: number; }
-
Solutions:
-
app/heroes/hero-list/hero-list.component.ts
@Component({ selector: 'toh-hero-list', template: ` <section> Our list of heroes: <toh-hero *ngFor="let hero of heroes" [hero]="hero"> </toh-hero> Total powers: {{totalPowers}}<br> Average power: {{avgPower}} </section> ` }) export class HeroListComponent { heroes: Hero[]; totalPowers = 0; get avgPower() { return this.totalPowers / this.heroes.length; } }
-
-
TypeScript's
--strictPropertyInitialization
compiler option ensures that a class initializes its properties during construction. When enabled, this option causes the TypeScript compiler to report an error if the class does not set a value to any property that is not explicitly marked as optional. -
By design, Angular treats all
@Input
properties as optional. When possible, you should satisfy--strictPropertyInitialization
by providing a default value. -
app/heroes/hero/hero.component.ts
@Component({ selector: 'toh-hero', template: `...` }) export class HeroComponent { @Input() id = 'default_id'; }
-
If the property is hard to construct a default value for, use
?
to explicitly mark the property as optional.-
app/heroes/hero/hero.component.ts
@Component({ selector: 'toh-hero', template: `...` }) export class HeroComponent { @Input() id?: string; process() { if (this.id) { // ... } } }
-
-
You may want to have a required
@Input
field, meaning all your component users are required to pass that attribute. In such cases, use a default value. Just suppressing the TypeScript error with!
is insufficient and should be avoided because it will prevent the type checker ensure the input value is provided.- app/heroes/hero/hero.component.ts
@Component({ selector: 'toh-hero', template: `...` }) export class HeroComponent { // The exclamation mark suppresses errors that a property is // not initialized. // Ignoring this enforcement can prevent the type checker // from finding potential issues. @Input() id!: string; }
- app/heroes/hero/hero.component.ts
-
Do use attribute directives when you have presentation logic without a template.
-
Why?
- Attribute directives don't have an associated template.
-
Why?
- An element may have more than one attribute directive applied.
-
app/shared/highlight.directive.ts
@Directive({ selector: '[tohHighlight]' }) export class HighlightDirective { @HostListener('mouseover') onMouseEnter() { // do highlight work } }
-
app/app.component.html
<div tohHighlight>Bombasta</div>
-
Consider preferring the
@HostListener
and@HostBinding
to thehost
property of the@Directive
and@Component
decorators. -
Do be consistent in your choice.
-
Why?
- The property associated with
@HostBinding
or the method associated with@HostListener
can be modified only in a single place —in the directive's class. If you use the host metadata property, you must modify both the property/method declaration in the directive's class and the metadata in the decorator associated with the directive.
- The property associated with
-
app/shared/validator.directive.ts
import { Directive, HostBinding, HostListener } from '@angular/core'; @Directive({ selector: '[tohValidator]' }) export class ValidatorDirective { @HostBinding('attr.role') role = 'button'; @HostListener('mouseenter') onMouseEnter() { // do work } }
-
Compare with the less preferred
host
metadata alternative. -
Why?
- The host metadata is only one term to remember and doesn't require extra ES imports.
-
app/shared/validator2.directive.ts
import { Directive } from '@angular/core'; @Directive({ selector: '[tohValidator2]', host: { '[attr.role]': 'role', '(mouseenter)': 'onMouseEnter()' } }) export class Validator2Directive { role = 'button'; onMouseEnter() { // do work } }
-
Do use services as singletons within the same injector. Use them for sharing data and functionality.
-
Why?
- Services are ideal for sharing methods across a feature area or an app.
-
Why?
- Services are ideal for sharing stateful in-memory data.
-
app/heroes/shared/hero.service.ts
export class HeroService { constructor(private http: HttpClient) { } getHeroes() { return this.http.get<Hero[]>('api/heroes'); } }
-
Do create services with a single responsibility that is encapsulated by its context.
-
Do create a new service once the service begins to exceed that singular purpose.
-
Why?
- When a service has multiple responsibilities, it becomes difficult to test.
-
Why? When a service has multiple responsibilities, every component or service that injects it now carries the weight of them all.
-
Do provide a service with the application root injector in the
@Injectable
decorator of the service. -
Why?
- The Angular injector is hierarchical.
-
Why?
- When you provide the service to a root injector, that instance of the service is shared and available in every class that needs the service. This is ideal when a service is sharing methods or state.
-
Why?
- When you register a service in the
@Injectable
decorator of the service, optimization tools such as those used by the Angular CLI's production builds can perform tree shaking and remove services that aren't used by your app.
- When you register a service in the
-
Why?
- This is not ideal when two different components need different instances of a service. In this scenario it would be better to provide the service at the component level that needs the new and separate instance.
-
src/app/treeshaking/service.ts
@Injectable({ providedIn: 'root', }) export class Service { }
-
Do use the
@Injectable()
class decorator instead of the@Inject
parameter decorator when using types as tokens for the dependencies of a service. -
Why?
- The Angular Dependency Injection (DI) mechanism resolves a service's own dependencies based on the declared types of that service's constructor parameters.
-
Why?
- When a service accepts only dependencies associated with type tokens, the
@Injectable()
syntax is much less verbose compared to using@Inject()
on each individual constructor parameter.
- When a service accepts only dependencies associated with type tokens, the
-
app/heroes/shared/hero-arena.service.ts
/* avoid */ export class HeroArena { constructor( @Inject(HeroService) private heroService: HeroService, @Inject(HttpClient) private http: HttpClient) {} }
-
Solutions:
- app/heroes/shared/hero-arena.service.ts
@Injectable() export class HeroArena { constructor( private heroService: HeroService, private http: HttpClient) {} }
- app/heroes/shared/hero-arena.service.ts
-
Do refactor logic for making data operations and interacting with data to a service.
-
Do make data services responsible for XHR calls, local storage, stashing in memory, or any other data operations.
-
Why? The component's responsibility is for the presentation and gathering of information for the view. It should not care how it gets the data, just that it knows who to ask for it. Separating the data services moves the logic on how to get it to the data service, and lets the component be simpler and more focused on the view.
-
Why?
- This makes it easier to test (mock or real) the data calls when testing a component that uses a data service.
-
Why?
- The details of data management, such as headers, HTTP methods, caching, error handling, and retry logic, are irrelevant to components and other data consumers.
-
A data service encapsulates these details. It's easier to evolve these details inside the service without affecting its consumers. And it's easier to test the consumers with mock service implementations.
- Use Lifecycle hooks to tap into important events exposed by Angular.
-
Do implement the lifecycle hook interfaces.
-
Why?
- Lifecycle interfaces prescribe typed method signatures. Use those signatures to flag spelling and syntax mistakes.
-
app/heroes/shared/hero-button/hero-button.component.ts
/* avoid */ @Component({ selector: 'toh-hero-button', template: `<button type="button">OK</button>` }) export class HeroButtonComponent { onInit() { // misspelled console.log('The component is initialized'); } }
-
Solution:
- app/heroes/shared/hero-button/hero-button.component.ts
@Component({ selector: 'toh-hero-button', template: `<button type="button">OK</button>` }) export class HeroButtonComponent implements OnInit { ngOnInit() { console.log('The component is initialized'); } }
- app/heroes/shared/hero-button/hero-button.component.ts
- Useful tools and tips for Angular.
-
Do use file templates or snippets to help follow consistent styles and patterns. Here are templates and/or snippets for some of the web development editors and IDEs.
-
Consider using snippets for Visual Studio Code that follow these styles and guidelines.
-
Consider using [snippets] for Atom that follow these styles and guidelines.
-
Consider using [snippets] for Sublime Text that follow these styles and guidelines.
-
Consider using [snippets] for Vim that follow these styles and guidelines.