angular_docs_understanding_angular
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revision | |||
| angular_docs_understanding_angular [2023/10/06 12:10] – [Component styles] kalaschnikow | angular_docs_understanding_angular [2023/10/06 12:11] (current) – kalaschnikow | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| + | ====== Understanding Angular ====== | ||
| + | |||
| + | https:// | ||
| + | |||
| + | |||
| + | |||
| + | ===== Components ===== | ||
| + | |||
| + | ==== Overview ==== | ||
| + | |||
| + | Components are the main building blocks for Angular applications. Each component consists of: | ||
| + | * An HTML template that declares what renders on the page | ||
| + | * A TypeScript class that defines behavior | ||
| + | * A CSS selector that defines how the component is used in a template | ||
| + | * Optionally, CSS styles applied to the template | ||
| + | |||
| + | |||
| + | === Creating a component === | ||
| + | * Creating a component using Angular CLI | ||
| + | * '' | ||
| + | * By default this command generates: | ||
| + | * directory named after the component | ||
| + | * component file, ''< | ||
| + | * template file, ''< | ||
| + | * CSS file, ''< | ||
| + | * testing specification file, ''< | ||
| + | |||
| + | * Creating a component manually | ||
| + | * ... | ||
| + | |||
| + | === Specifying a component' | ||
| + | |||
| + | Every component // | ||
| + | |||
| + | < | ||
| + | @Component({ | ||
| + | selector: ' | ||
| + | }) | ||
| + | </ | ||
| + | |||
| + | === Defining a component' | ||
| + | |||
| + | * inline | ||
| + | * '' | ||
| + | * If you want your template to span multiple lines, use backticks (`) | ||
| + | * with file path URL | ||
| + | * '' | ||
| + | |||
| + | |||
| + | === Declaring a component' | ||
| + | |||
| + | * inline | ||
| + | * '' | ||
| + | * with file path URL | ||
| + | * '' | ||
| + | |||
| + | |||
| + | ==== Component Lifecycle ==== | ||
| + | |||
| + | https:// | ||
| + | |||
| + | * Lifecycle: | ||
| + | * start | ||
| + | * instantiate component class | ||
| + | * render component view | ||
| + | * change detection | ||
| + | * detection of data-bound properties => update component and view | ||
| + | * end | ||
| + | * destroy component instance | ||
| + | * remove rendered template from the DOM | ||
| + | |||
| + | * Lifecycle hook methods | ||
| + | * can be used to tap into key events in the lifecycle | ||
| + | |||
| + | === Responding to lifecycle events === | ||
| + | Responding to lifecycle event is possible by implementing interface of a lifecycle hook. Every such interface contains only one method. The interface is named usually like '' | ||
| + | < | ||
| + | @Directive({selector: | ||
| + | export class PeekABooDirective implements OnInit { | ||
| + | constructor(private logger: LoggerService) { } | ||
| + | |||
| + | // implement OnInit' | ||
| + | ngOnInit() { | ||
| + | this.logIt(' | ||
| + | } | ||
| + | |||
| + | logIt(msg: string) { | ||
| + | this.logger.log(`# | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | == Lifecycle event sequence == | ||
| + | * '' | ||
| + | * Respond when Angular sets or resets data-bound input properties. | ||
| + | * called before (!) '' | ||
| + | * This happens frequently, so any operation you perform here impacts performance significantly. | ||
| + | * If your component has no inputs or you use it without providing any inputs, the framework will not call '' | ||
| + | * '' | ||
| + | * Initialize the directive or component after Angular first displays the data-bound properties and sets the directive or component' | ||
| + | * Called once, after the first ngOnChanges(). ngOnInit() is still called even when ngOnChanges() is not (which is the case when there are no template-bound inputs). | ||
| + | * '' | ||
| + | * Detect and act upon changes that Angular can't or won't detect on its own. | ||
| + | * Called immediately after '' | ||
| + | * '' | ||
| + | * Respond after Angular projects external content into the component' | ||
| + | * Called //once// after the first '' | ||
| + | * '' | ||
| + | * Respond after Angular checks the content projected into the directive or component. | ||
| + | * Called after '' | ||
| + | * '' | ||
| + | * Respond after Angular initializes the component' | ||
| + | * Called //once// after the first '' | ||
| + | * '' | ||
| + | * Called once after the first '' | ||
| + | * Called after the '' | ||
| + | * '' | ||
| + | * Cleanup just before Angular destroys the directive or component. Unsubscribe Observables and detach event handlers to avoid memory leaks. | ||
| + | * Called immediately before Angular destroys the directive or component. | ||
| + | |||
| + | === Initializing a component or directive === | ||
| + | Use the ngOnInit() method to perform the following initialization tasks: | ||
| + | * Perform complex initializations outside of the constructor | ||
| + | * Components should be cheap and safe to construct. You should not, for example, fetch data in a component constructor. You shouldn' | ||
| + | * Set up the component after Angular sets the input properties | ||
| + | * Components should be cheap and safe to construct. You should not, for example, fetch data in a component constructor. You shouldn' | ||
| + | An ngOnInit() is a good place for a component to fetch its initial data. | ||
| + | |||
| + | === Cleaning up on instance destruction === | ||
| + | Angular provides several ways to clean up when an instance is destroyed. | ||
| + | * '' | ||
| + | * This is the place to free resources that won't be garbage-collected automatically. | ||
| + | * Unsubscribe from Observables and DOM events | ||
| + | * Stop interval timers | ||
| + | * Unregister all callbacks that the directive registered with global or application services | ||
| + | * notify another part of the application that the component is going away. | ||
| + | * DestroyRef | ||
| + | * In addition to '' | ||
| + | * takeUntilDestroyed | ||
| + | * (actually available only for developer preview) | ||
| + | * When using RxJS Observables in components or directives, you may want to complete any observables when the component or directive is destroyed. Angular' | ||
| + | |||
| + | |||
| + | === Reading and writing the DOM === | ||
| + | Sometimes it is necessary to use browser-only APIs to read or write the DOM. It is not possible with lifecycle events because they run during server-side rendering and pre-rendering. For this purpose Angular provides two functions (both are currently available only for developer preview): | ||
| + | * afterRender | ||
| + | * Synchronize state with the DOM. | ||
| + | * Runs after every change detection cycle that follows. | ||
| + | * afterNextRender | ||
| + | * When using RxJS Observables in components or directives, you may want to complete any observables when the component or directive is destroyed. Angular' | ||
| + | * Runs //once// after the next change detection cycle. | ||
| + | |||
| + | These functions can be used for: | ||
| + | * One-time initialization | ||
| + | * see [[https:// | ||
| + | * synchronization every time Angular finishes mutating data | ||
| + | * see [[https:// | ||
| + | |||
| + | === General examples === | ||
| + | |||
| + | * Sequence and frequency of all lifecycle events | ||
| + | * PeekABooComponent example | ||
| + | * The hook calling order: | ||
| + | * 1 OnChanges | ||
| + | * 2 OnInit | ||
| + | * 3 DoCheck | ||
| + | * 4 AfterContentInit | ||
| + | * 5 AfterContentChecked | ||
| + | * 6 AfterViewInit | ||
| + | * 7 AfterViewChecked | ||
| + | * 8 DoCheck | ||
| + | * 9 AfterContentChecked | ||
| + | * 10 AfterViewChecked | ||
| + | * 11 OnDestroy | ||
| + | * Use directives to watch the DOM | ||
| + | * '' | ||
| + | * The SpyDirective implements two hooks, ngOnInit() and ngOnDestroy(), | ||
| + | * This template applies the SpyDirective to a <div> in the ngFor hero repeater managed by the parent SpyComponent. | ||
| + | * Use component and directive hooks together | ||
| + | * ... | ||
| + | |||
| + | === Using change detection hooks === | ||
| + | Angular calls the ngOnChanges() method of a component or directive whenever it detects changes to the //input properties// | ||
| + | |||
| + | ... | ||
| + | |||
| + | === Defining custom change detection === | ||
| + | ... | ||
| + | While the '' | ||
| + | |||
| + | If you use this hook, your implementation must be extremely lightweight or the user experience suffers. | ||
| + | |||
| + | ==== View encapsulation ==== | ||
| + | In Angular, a component' | ||
| + | |||
| + | Following modes are available: | ||
| + | * ViewEncapsulation.ShadowDom | ||
| + | * based on Shadow DOM API | ||
| + | * ViewEncapsulation.Emulated | ||
| + | * emulating Shadow DOM | ||
| + | * ViewEncapsulation.None | ||
| + | * no encapsulation | ||
| + | |||
| + | ... | ||
| + | ==== Component interaction ==== | ||
| + | |||
| + | https:// | ||
| + | |||
| + | === Pass data from parent to child with input binding === | ||
| + | Example: | ||
| + | |||
| + | |||
| + | HeroChildComponent has two input properties, typically adorned with @Input() decorator: | ||
| + | < | ||
| + | import { Component, Input } from ' | ||
| + | import { Hero } from ' | ||
| + | |||
| + | @Component({ | ||
| + | selector: ' | ||
| + | template: ` | ||
| + | < | ||
| + | <p>I, {{hero.name}}, | ||
| + | ` | ||
| + | }) | ||
| + | export class HeroChildComponent { | ||
| + | @Input() hero!: Hero; | ||
| + | @Input(' | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Using HerChildComponent in HeroParentComponent: | ||
| + | < | ||
| + | import { Component } from ' | ||
| + | |||
| + | import { HEROES } from ' | ||
| + | |||
| + | @Component({ | ||
| + | selector: ' | ||
| + | template: ` | ||
| + | < | ||
| + | |||
| + | < | ||
| + | *ngFor=" | ||
| + | [hero]=" | ||
| + | [master]=" | ||
| + | </ | ||
| + | ` | ||
| + | }) | ||
| + | export class HeroParentComponent { | ||
| + | heroes = HEROES; | ||
| + | master = ' | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | === Intercept input property changes with a setter === | ||
| + | |||
| + | Use an input property setter to intercept and act upon a value from the parent. | ||
| + | |||
| + | The setter of the name input property in the child NameChildComponent trims the whitespace from a name and replaces an empty value with default text. | ||
| + | < | ||
| + | import { Component, Input } from ' | ||
| + | |||
| + | @Component({ | ||
| + | selector: ' | ||
| + | template: '< | ||
| + | }) | ||
| + | export class NameChildComponent { | ||
| + | @Input() | ||
| + | get name(): string { return this._name; } | ||
| + | set name(name: string) { | ||
| + | this._name = (name && name.trim()) || '< | ||
| + | } | ||
| + | private _name = ''; | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Using NameChildComponent: | ||
| + | < | ||
| + | import { Component } from ' | ||
| + | |||
| + | @Component({ | ||
| + | selector: ' | ||
| + | template: ` | ||
| + | < | ||
| + | |||
| + | < | ||
| + | ` | ||
| + | }) | ||
| + | export class NameParentComponent { | ||
| + | // Displays 'Dr. IQ', '< | ||
| + | names = ['Dr. IQ', ' | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | === Intercept input property changes with ngOnChanges() === | ||
| + | |||
| + | Detect and act upon changes to input property values with the '' | ||
| + | |||
| + | You might prefer this approach to the property setter when watching multiple, interacting input properties. | ||
| + | |||
| + | < | ||
| + | import { Component, Input, OnChanges, SimpleChanges } from ' | ||
| + | |||
| + | @Component({ | ||
| + | selector: ' | ||
| + | template: ` | ||
| + | < | ||
| + | < | ||
| + | <ul> | ||
| + | <li *ngFor=" | ||
| + | </ul> | ||
| + | ` | ||
| + | }) | ||
| + | export class VersionChildComponent implements OnChanges { | ||
| + | @Input() major = 0; | ||
| + | @Input() minor = 0; | ||
| + | changeLog: string[] = []; | ||
| + | |||
| + | ngOnChanges(changes: | ||
| + | const log: string[] = []; | ||
| + | for (const propName in changes) { | ||
| + | const changedProp = changes[propName]; | ||
| + | const to = JSON.stringify(changedProp.currentValue); | ||
| + | if (changedProp.isFirstChange()) { | ||
| + | log.push(`Initial value of ${propName} set to ${to}`); | ||
| + | } else { | ||
| + | const from = JSON.stringify(changedProp.previousValue); | ||
| + | log.push(`${propName} changed from ${from} to ${to}`); | ||
| + | } | ||
| + | } | ||
| + | this.changeLog.push(log.join(', | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | === Test it for Intercept input property changes with ngOnChanges() === | ||
| + | |||
| + | Test that **// | ||
| + | ... | ||
| + | |||
| + | === Parent listens for child event === | ||
| + | |||
| + | The child component exposes an EventEmitter property with which it emits events when something happens. The parent binds to that event property and reacts to those events. | ||
| + | |||
| + | The child' | ||
| + | < | ||
| + | import { Component, EventEmitter, | ||
| + | |||
| + | @Component({ | ||
| + | selector: ' | ||
| + | template: ` | ||
| + | < | ||
| + | <button type=" | ||
| + | <button type=" | ||
| + | ` | ||
| + | }) | ||
| + | export class VoterComponent { | ||
| + | @Input() | ||
| + | @Output() voted = new EventEmitter< | ||
| + | didVote = false; | ||
| + | |||
| + | vote(agreed: | ||
| + | this.voted.emit(agreed); | ||
| + | this.didVote = true; | ||
| + | } | ||
| + | }</ | ||
| + | |||
| + | The parent VoteTakerComponent binds an event handler called onVoted() that responds to the child event payload $event and updates a counter. | ||
| + | < | ||
| + | import { Component } from ' | ||
| + | |||
| + | @Component({ | ||
| + | selector: ' | ||
| + | template: ` | ||
| + | < | ||
| + | < | ||
| + | |||
| + | < | ||
| + | *ngFor=" | ||
| + | [name]=" | ||
| + | (voted)=" | ||
| + | </ | ||
| + | ` | ||
| + | }) | ||
| + | export class VoteTakerComponent { | ||
| + | agreed = 0; | ||
| + | disagreed = 0; | ||
| + | voters = ['Dr. IQ', ' | ||
| + | |||
| + | onVoted(agreed: | ||
| + | if (agreed) { | ||
| + | this.agreed++; | ||
| + | } else { | ||
| + | this.disagreed++; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | === Parent interacts with child using local variable === | ||
| + | A parent component cannot use data binding to read child properties or invoke child methods. Do both by creating a template reference variable for the child element and then reference that variable within the parent template as seen in the following example. | ||
| + | |||
| + | The local variable ''# | ||
| + | < | ||
| + | import { Component } from ' | ||
| + | import { CountdownTimerComponent } from ' | ||
| + | |||
| + | @Component({ | ||
| + | selector: ' | ||
| + | template: ` | ||
| + | < | ||
| + | <button type=" | ||
| + | <button type=" | ||
| + | <div class=" | ||
| + | < | ||
| + | `, | ||
| + | styleUrls: [' | ||
| + | }) | ||
| + | export class CountdownLocalVarParentComponent { } | ||
| + | </ | ||
| + | |||
| + | === Parent calls an @ViewChild() === | ||
| + | The local variable approach is straightforward. But it is limited because the parent-child wiring must be done entirely //within the parent template//. The parent component itself (the parent class!) has no access to the child. | ||
| + | |||
| + | When the parent component class requires that kind of access, inject the child component into the parent as a ViewChild. | ||
| + | |||
| + | < | ||
| + | import { AfterViewInit, | ||
| + | import { Component } from ' | ||
| + | import { CountdownTimerComponent } from ' | ||
| + | |||
| + | @Component({ | ||
| + | selector: ' | ||
| + | template: ` | ||
| + | < | ||
| + | <button type=" | ||
| + | <button type=" | ||
| + | <div class=" | ||
| + | < | ||
| + | `, | ||
| + | styleUrls: [' | ||
| + | }) | ||
| + | export class CountdownViewChildParentComponent implements AfterViewInit { | ||
| + | |||
| + | @ViewChild(CountdownTimerComponent) | ||
| + | private timerComponent!: | ||
| + | |||
| + | seconds() { return 0; } | ||
| + | |||
| + | ngAfterViewInit() { | ||
| + | // Redefine `seconds()` to get from the `CountdownTimerComponent.seconds` ... | ||
| + | // but wait a tick first to avoid one-time devMode | ||
| + | // unidirectional-data-flow-violation error | ||
| + | setTimeout(() => this.seconds = () => this.timerComponent.seconds, | ||
| + | } | ||
| + | |||
| + | start() { this.timerComponent.start(); | ||
| + | stop() { this.timerComponent.stop(); | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | === Parent and children communicate using a service === | ||
| + | A parent component and its children share a service whose interface enables bidirectional communication within the family. | ||
| + | |||
| + | The scope of the service instance is the parent component and its children. Components outside this component subtree have no access to the service or their communications. | ||
| + | |||
| + | < | ||
| + | ... | ||
| + | </ | ||
| + | ... | ||
| + | |||
| + | |||
| + | ==== Component styles ==== | ||
| + | |||
| + | === Using component styles === | ||
| + | For every Angular component you write, you can define not only an HTML template, but also the CSS styles that go with that template, specifying any selectors, rules, and media queries that you need. | ||
| + | |||
| + | One way to do this is to set the styles property in the component metadata. The styles property takes an array of strings that contain CSS code. | ||
| + | |||
| + | < | ||
| + | @Component({ | ||
| + | selector: ' | ||
| + | template: ` | ||
| + | < | ||
| + | < | ||
| + | `, | ||
| + | styles: ['h1 { font-weight: | ||
| + | }) | ||
| + | export class HeroAppComponent { | ||
| + | /* . . . */ | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | === Component styling best practices === | ||
| + | |||
| + | You should consider the styles of a component to be private implementation details for that component. | ||
| + | |||
| + | == Authoring a component to support customization == | ||
| + | As component author, you can explicitly design a component to accept customization in one of four different ways. | ||
| + | * 1. Use CSS Custom Properties (recommended) | ||
| + | * You can define a supported customization API for your component by defining its styles with CSS Custom Properties, alternatively known as CSS Variables. Anyone using your component can consume this API by defining values for these properties, customizing the final appearance of the component on the rendered page. | ||
| + | * While this requires defining a custom property for each customization point, it creates a clear API contract that works in all style encapsulation modes. | ||
| + | * 2. Declare global CSS with @mixin | ||
| + | * While Angular' | ||
| + | * 3. Customize with CSS ::part | ||
| + | * If your component uses Shadow DOM, you can apply the part attribute to specify elements in your component' | ||
| + | * 4. Provide a TypeScript API (not recommended) | ||
| + | * You can define a TypeScript API for customizing styles, using template bindings to update CSS classes and styles. | ||
| + | * This is not recommended because the additional JavaScript cost of this style API incurs far more performance cost than CSS. | ||
| + | |||
| + | === Special selectors === | ||
| + | |||
| + | == :host == | ||
| + | |||
| + | ... | ||
| + | |||
| + | == : | ||
| + | |||
| + | ... | ||
| + | |||
| + | == (deprecated) /deep/, >>>, | ||
| + | |||
| + | ... | ||
| + | |||
| + | ... | ||
| + | === Loading component styles === | ||
| + | |||
| + | ... | ||
| + | == Styles in component metadata == | ||
| + | |||
| + | ... | ||
| + | |||
| + | == Style files in component metadata == | ||
| + | |||
| + | ... | ||
| + | == Template inline styles == | ||
| + | |||
| + | ... | ||
| + | |||
| + | == Template link tags == | ||
| + | ... | ||
| + | |||
| + | |||
| + | == CSS @imports == | ||
| + | ... | ||
| + | |||
| + | == External and global style files == | ||
| + | ... | ||
| + | |||
| + | == Non-CSS style files == | ||
| + | ... | ||
| + | |||
| + | ==== Sharing data between child and parent directives and components ==== | ||
| + | ... | ||
| + | ==== Content projection ==== | ||
| + | ... | ||
| + | ==== Dynamic component loader ==== | ||
| + | ... | ||
| + | ==== Angular elements overview ==== | ||
| + | ... | ||
| + | |||
| + | |||
| + | ===== Templates ===== | ||
| + | ... | ||
| + | |||
| + | ===== Directives ===== | ||
| + | ... | ||
| + | |||
| + | ===== Dependency injection ===== | ||
| + | ... | ||
