Angular笔记

字数 3512 · 2019-04-01

#javascript

Component

The component and template define a view.

A component is a special type of directive. The @Component() decorator extends the @Directive() decorator with template-oriented features.

A view that belongs to a component is called a host view.

You can change the structure of elements by inserting, moving, or removing nested views within their view containers.

The documentation generally refers to elements (ElementRef instances), as distinct from DOM elements (which can be accessed directly if necessary).

Lifecycle Hooks

After creating a component/directive by calling its constructor, Angular calls the lifecycle hook methods in the following sequence at specific moments:

  • ngOnChanges()
  • ngOnInit()
  • ngDoCheck()
  • ngAfterContentInit()
  • ngAfterContentChecked()
  • ngAfterViewInit()
  • ngAfterViewChecked()
  • ngOnDestroy()

Directive

There are three kinds of directives in Angular:

  • Components — directives with a template.
  • Structural directives — change the DOM layout by adding and removing DOM elements.
  • Attribute directives — change the appearance or behavior of an element, component, or another directive.

Structural Directives

Structural directives are responsible for HTML layout. They shape or reshape the DOM’s structure, typically by adding, removing, or manipulating elements.

1
<div *ngIf="hero" class="name"></div>

Internally, Angular translates the *ngIf attribute into a <ng-template> element, wrapped around the host element:

1
2
3
 <ng-template [ngIf]="hero">
  <div class="name"></div>
</ng-template>

<ng-template>
The <ng-template> is an Angular element for rendering HTML. It is never displayed directly.

@Directive

Option Description
selector The CSS selector that identifies this directive in a template and triggers instantiation of the directive.
inputs Enumerates the set of data-bound input properties for a directive
outputs Enumerates the set of event-bound output properties.
providers Configures the injector of this directive or component with a token that maps to a provider of a dependency.
exportAs Defines the name that can be used in the template to assign this directive to a variable.
queries Configures the queries that will be injected into the directive.
host Maps class properties to host element bindings for properties, attributes, and events, using a set of key-value pairs.

inputs

1
2
3
4
inputs: ['bankName', 'id: account-id'],
// ...
bankName: string;
id: string;

Equivalent to:

1
2
@Input() bankName: string;
@Input('account-id') id: string;

host

  • Host Listeners
  • Host Property Bindings
  • Attributes
1
2
3
4
5
host: {
  '(click)': 'onClick($event.target)',
  '[class.valid]': 'valid',
}
// ...

Equivalent to:

1
2
3
4
5
@HostListener('click', ['$event.target'])
onClick(btn) {
  console.log('button', btn, 'number of clicks:', this.numberOfClicks++);
}
@HostBinding('class.valid') get valid() { return this.control.valid; }

providers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Greeter {
  greet(name:string) {
    return 'Hello ' + name + '!';
  }
}

@Directive({
  selector: 'greet',
  providers: [
    Greeter
  ]
})
class HelloWorld {
  greeter:Greeter;

  constructor(greeter:Greeter) {
    this.greeter = greeter;
  }
}

exportAs

1
2
3
4
5
6
7
8
9
10
11
12
13
@Directive({
  selector: 'child-dir',
  exportAs: 'child'
})
class ChildDir {
}

@Component({
  selector: 'main',
  template: `<child-dir #c="child"></child-dir>`
})
class MainComponent {
}

queries

@Component

viewProviders
Defines the set of injectable objects that are visible to its view DOM children.

Different with providers when use ng-content.

1
2
3
4
<h1>
  <app-title></app-title>
  <ng-content></ng-content>
</h1>

Dependency Injection

The injector is responsible for creating service instances and injecting them into classes.

A provider tells an injector how to create the service.

The @Injectable() is an essential ingredient in every Angular service definition.

You can configure injectors with providers at different levels of your app, by setting a metadata value in one of three places:

  • In the @Injectable() decorator for the service itself.
  • In the @NgModule() decorator for an NgModule.
  • In the @Component() decorator for a component.

The @Injectable() decorator has the providedIn metadata option, where you can specify the provider of the decorated service class with the root injector, or with the injector for a specific NgModule.

The @NgModule() and @Component() decorators have the providers metadata option, where you can configure providers for NgModule-level or component-level injectors.

Services are singletons within the scope of an injector.

Inject DOM element

1
2
3
4
5
6
7
8
9
10
import { ElementRef } from '@angular/core';

export class HighlightDirective {
  
  private el: HTMLElement;

  constructor(el: ElementRef) {
    this.el = el.nativeElement;
  }
}

NgModule

NgModules configure the injector and the compiler and help organize related things together.

An NgModule is a class marked by the @NgModule decorator.

@NgModule

  • imports - array tells Angular what other NgModules the current module needs
  • declarations - array tells Angular which components belong to that module
  • exports - The set of components, directives, and pipes declared in this NgModule that can be used in the template of any component that is part of an NgModule that imports this NgModule
  • entryComponents - The set of components to compile when this NgModule is defined, so that they can be dynamically loaded into the view
  • providers - The set of injectable objects that are available in the injector of this module.

The declarations array only takes declarables. Declarables are components, directives and pipes.

DOM manipulation

You get a reference to Angular DOM abstractions by using ViewChild query along with template variable references. The simplest wrapper around a DOM element is ElementRef. For templates you have TemplateRef that allows you to create an embedded view. Host views can be accessed on componentRef created using ComponentFactoryResolver. The views can be manipulated with ViewContainerRef. There are two directives that make the manual process automatic: ngTemplateOutlet — for embedded views and ngComponentOutlet for host views (dynamic components).

@ViewChild

1
<span #tref>I am span</span>
1
@ViewChild("tref", {read: ElementRef}) tref: ElementRef;

ViewContainerRef cannot be inferred and have to be asked for specifically in read parameter. Others, like ViewRef cannot be returned from the DOM and have to be constructed manually.

ElementRef

Useful for accessing native DOM element.

1
2
3
4
class ElementRef<T> {
  constructor(nativeElement: T)
  nativeElement: T
}

obtain an instance of ElementRef associated with their host element through DI mechanism:

1
2
3
4
export class SampleComponent{
  constructor(private hostElement: ElementRef) {
    console.log(this.hostElement.nativeElement.outerHTML);
  }

TemplateRef

1
2
3
<ng-template #tpl>
    <span>I am span in template</span>
</ng-template>
1
2
3
4
5
6
7
8
export class SampleComponent implements AfterViewInit {
  @ViewChild("tpl") tpl: TemplateRef<any>;

  ngAfterViewInit() {
    let elementRef = this.tpl.elementRef;
    console.log(elementRef.nativeElement.textContent);
  }
}

When you define a template you can have input parameters specified through let-paramname:

1
2
3
<template [paForOf]="getProducts()" let-item='item'>
     <span></span>
</template>
1
this.container.createEmbeddedView(this.template, {item: {name: 'John'}}`

1
this.container.createEmbeddedView(this.template, { $implicit: this.dataSource[i] });

If the input property is specified like this let-item without second part =something, the embedded view treats it as let-item=$implicit so you have to pass a context object with $implicit property.

https://stackoverflow.com/questions/46910752/what-is-createembeddedview-context-parameter-in-angular

ViewRef

Angular supports two types of views:

  • Embedded Views which are linked to a Template
  • Host Views which are linked to a Component

Creating embedded view

With TemplateRef:

1
let view = this.tpl.createEmbeddedView(null);

With ViewContainerRef:

1
this.vc.createEmbeddedView(this.tpl);

Creating host view

1
2
3
4
5
6
constructor(private injector: Injector,
            private r: ComponentFactoryResolver) {
    let factory = this.r.resolveComponentFactory(ColorComponent);
    let componentRef = factory.create(injector);
    let view = componentRef.hostView;
}

Don’t forget that components that are instantiated dynamically must be added to EntryComponents of a module or hosting component.

ViewContainerRef

Represents a container where one or more views can be attached.

Usually, a good candidate to mark a place where a ViewContainer should be created is ng-container element.

1
@ViewChild('dynamicComponentContainer', { read: ViewContainerRef }) dynamicComponentContainer: ViewContainerRef;

ViewContainer provides a convenient API for manipulating the views:

1
2
3
4
5
6
7
8
9
10
class ViewContainerRef {
    ...
    clear() : void
    insert(viewRef: ViewRef, index?: number) : ViewRef
    get(index: number) : ViewRef
    indexOf(viewRef: ViewRef) : number
    detach(index?: number) : ViewRef
    remove(index?: number): void
    move(viewRef: ViewRef, currentIndex: number) : ViewRef
}

While the remove method destroys the view so it can’t be re-attached later, the detach method preserves it to be re-used in the future which is important for optimization.

ViewContainer also provides API to create a view automatically:

1
2
3
4
5
6
7
8
class ViewContainerRef {
    element: ElementRef
    length: number

    createComponent(componentFactory...): ComponentRef<C>
    createEmbeddedView(templateRef...): EmbeddedViewRef<C>
    ...
}

ngTemplateOutlet

1
2
3
4
5
6
<span>I am first span</span>
<ng-container [ngTemplateOutlet]="tpl"></ng-container>
<span>I am last span</span>
<ng-template #tpl>
    <span>I am span in template</span>
</ng-template>

ngComponentOutlet

1
<ng-container *ngComponentOutlet="ColorComponent"></ng-container>

https://hackernoon.com/angular-pro-tip-how-to-dynamically-create-components-in-body-ba200cc289e6

Exploring Angular DOM manipulation techniques using ViewContainerRef

@ContentChild

1
<ng-content></ng-content>
1
2
3
4
5
6
7
8
9
10
11
12
<div class="card">
    <div class="card-header">
        
    </div>

    <!-- single slot transclusion here -->
    <ng-content></ng-content>

    <div class="card-footer">
        
    </div>
</div>
1
2
3
4
5
6
7
8
9
<card header="my header" footer="my footer">
    <!-- put your dynamic content here -->
    <div class="card-block">
      <h4 class="card-title">You can put any content here</h4>
      <p class="card-text">For example this line of text and</p>
      <a href="#" class="btn btn-primary">This button</a>
    </div>
    <!-- end dynamic content -->
<card>

https://scotch.io/tutorials/angular-2-transclusion-using-ng-content

Form

Angular provides two different approaches to handling user input through forms: reactive and template-driven. Both capture user input events from the view, validate the user input, create a form model and data model to update, and provide a way to track changes.

Both reactive and template-driven forms share underlying building blocks.

  • FormControl tracks the value and validation status of an individual form control.

  • FormGroup tracks the same values and status for a collection of form controls.

  • FormArray tracks the same values and status for an array of form controls.

  • ControlValueAccessor creates a bridge between Angular FormControl instances and native DOM elements.

NgForm

Angular automatically creates and attaches an NgForm directive to the <form> tag.

  • Each input element has an id property that is used by the label element’s for attribute to match the label to its input control.
  • Each input element has a name property that is required by Angular forms to register the control with the form.

ControlValueAccessor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import { Component, OnInit, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

@Component({
  selector: 'app-date-picker',
  templateUrl: './date-picker.component.html',
  styleUrls: ['./date-picker.component.css'],
  providers: [     
  {       
    provide: NG_VALUE_ACCESSOR, 
    useExisting: forwardRef(() => DatePickerComponent),
    multi: true     
  }   
  ]
})
export class DatePickerComponent implements OnInit, ControlValueAccessor {

  val: string = '';

  onChange = Function.prototype;
  onTouched = Function.prototype;
  
  get value() {
    return this.val
  }

  set value(val){
    this.val = val
    this.onChange(val)
  }

  constructor() { }

  ngOnInit() { }

  writeValue(value: string): void {
    this.value = value
  }

  registerOnChange(fn: () => {}): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: () => {}): void {
    this.onTouched = fn;
  }
}

Subject

1
2
3
export class MyService {
  public mysubject : Subject = new Subject();
}
1
myservice.mysubject.next("new value");
1
2
3
this.myservice.mysubject.subscribe( (value) => {
  //insert your code here
});

Routing

In summary, you want to delay rendering the routed component until all necessary data have been fetched.

You need a resolver.

i18n

Angular simplifies the following aspects of internationalization:

  • Displaying dates, number, percentages, and currencies in a local format.
  • Preparing text in component templates for translation.
  • Handling plural forms of words.
  • Handling alternative text.

When internationalizing your app, you need to do thorough testing to make sure UI components behave as expected even when their contents vary greatly in content size.

You need to build and deploy a separate version of the app for each supported language.

Pluralzation

1
<span i18n>Updated {minutes, plural, =0 {just now} =1 {one minute ago} other {{{minutes}} minutes ago}}</span>
  • The first parameter is the key. It is bound to the component property (minutes), which determines the number of minutes.
  • The second parameter identifies this as a plural translation type.
  • The third parameter defines a pluralization pattern consisting of pluralization categories and their matching values.

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 provided by the Angular CLI.

Path values given in the configuration are relative to the root workspace folder.

  • projects : Contains a subsection for each project (library or application) in the workspace, with the per-project configuration options.

Testing

CheatSheet

Glossary

Debug

1
ng.probe($0).componentInstance