Post

Angular

Table of Contents

Inbox

Angular

Installation, initialization

  1. To create Angular app, install Angular CLI and execute
    • ng new my-app --no-strict --standalone false --routing false
      • --no-strict at a beginning will help learning by not using strict mode
      • --standalone false as standalone is some other way of working with Angular
      • --routing false as for the beginning routing is not needed
  2. In angular.json you can specify style sources
    • Ordering matters, so latter ones will override former ones
    • Set it in projects.first-app.architect.build.options.styles property
      • e.g. "styles": [ "node_modules/bootstrap/dist/css/bootstrap.min.css", "src/styles.css"]
  3. You can use Bootstrap for out-of-the-box useful styles
    • npm install --save bootstrap@3
    • Then reach your CSS located in node_modules/bootstrap/dist/css/bootstrap.css
    • You can use minified file, which is smaller: bootstrap.min.css
    • You add the style to angular.json in a way mentioned above
  4. 🛠️ npm install problems
    • When getting errors with npm install, a solution may be to run npm install --legacy-peer-deps instead of npm install.
  5. Create Component from CLI
    • ng generate component my-component
      • or in short: ng g c my-component
    • You can exclude creating tests with --skip-tests

Selectors

  1. Selector
    • It’s the way Angular selects an element
  2. Available Selectors
    1. Element Selector
      • Used for Components
      • my-component -> <my-component></my-component>
        • e.g. @Component({ selector: 'app-recipes', (...) }}
    2. Attribute Selector
      • [my-component] -> e.g. <div my-component></div>
        • e.g. @Directive({ selector: '[my-component]', (...) })
    3. Class Selector
      • .my-component -> e.g. <div class="my-component"></div>
    4. ID Selectors, PseudoSelectors (like hover)
      • Not supported
  3. Data Binding
    • It can be described as communication between:
      • Business Logic Layer (TypeScript)
      • Presentation Layer (HTML)
  4. Data Binding possibilities
    1. Output Data (from TS to HTML)
      1. String Interpolation ``
      2. Property Binding - input: [disabled]="userIsGuest", a: [url]="recipe.url"
    2. Input (React to Events)
      • Event Binding (event)="someExpression"
    3. Output & Input (Two-Way) Data Binding
      • [(ngModel)]="data"
      • Example: <input type="text" [(ngModel)]="myVariable">
        • Listen to everything that is inputted and save it in myVariable
        • At the same time listen to this variable and update <input> text accordingly
  5. $event
    • Syntax for data emitted by a given event
    • Like keystroke in input
      • <input type="text" (input)="onKeystroke($event)">

Directives

  1. Directives
    • Instructions in the DOM
    • Structural Directives
      • It means it changes the structure of the DOM
      • It doesn’t hide/unhide, it really adds/removes elements
      • It can be only one Structural Directive on an element
      • Structural Directives start with a star *
      • Example: *ngIf, *ngFor
    • Attribute Directives
      • They allow to manipulate attributes
      • They change only the element they sit on
      • Example: ngStyle
  2. Create *ngIf with else
    • Create *ngIf element
      • <p *ngIf="myPredicate"; else myReference>Predicate is satisfied</p>
    • Create a local reference in an “else” element
      • <ng-template #myReference><p>Predicate not satisfied</p></ng-template>
  3. *ngFor
    • Get index of current iteration
      • *ngFor="let item of items; let i = index"
  4. Custom Directives

    1. Example
      1. Write directive.ts code
        • 1
          2
          3
          4
          5
          6
          7
          8
          9
          
          @Directive({
              selector: '[appHighlight]'
          })
          export class HighlightDirective implements OnInit {
              constructor(private elementRef: ElementRef, private renderer: Renderer2) { }
              ngOnInit() {
                      this.renderer.setStyle(this.elementRef.nativeElement, 'background-color', 'yellow');
              }
          }
          
      2. Add Directive to @NgModule declarations:
        • 1
          2
          3
          4
          5
          6
          
          @NgModule({
              declarations: [
                  AppComponent,
                  HighlightDirective
              ]
          })
          
    2. Generating Directives from CLI

      • ng generate directive improved-highlight
      • ng g d improved-highlight
    3. Directives allow to listen to events:

      • 1
        2
        3
        
        @HostListener('mouseenter') mouseenter(event: Event) {
            this.renderer.setStyle(this.elementRef.nativeElement, 'background-color', 'blue')
        }
        
    4. Structural Directives behind the scenes
      • Something like this
        • 1
          2
          3
          
          <div *ngIf="x > 0">
              <p>x is greater than 0</p>
          </div>
          
      • Gets translated into:
        • 1
          2
          3
          4
          5
          
          <ng-template [ngIf]="x > 0">
              <div>
                  <p>x is greater than 0</p>
              </div>
          </ng-template>
          
    5. @HostBinding
      • Use it to bind with a certain property, e.g.
        • @HostBinding('style.backgroundColor') backgroundColor = 'green'
        • @HostBinding('class.open') isOpen = true
  5. [ngSwitch] example:
    • 1
      2
      3
      4
      5
      
      <div [ngSwitch]="myValue">
          <p *ngSwitchCase="Yes">Yes, agreed.</p>
          <p *ngSwitchCase="No">No, disagreed.</p>
          <p *ngSwitchDefault>Can't say.</p>
      </div>
      

Binding

  1. Allow external components to “send” data
    • HTML -> TS
    • @Input()
    • Or binding through alias - @Input('propertyName')
  2. Local reference
    • <input type="text" #myHTMLReference>
    • Used to pass data HTML -> TS
    • Used for passing data locally
    • It passes the HTML Element itself (real reference)
    • Can be placed on any HTML element
    • Visible within the whole HTML Template
  3. @ViewChild()
    • Used to pass data HTML -> TS
    • Used for local binding
    • It passes ElementRef
      • Use .nativeElement to reference “real” element
    • You shouldn’t change element using this way, only get values
      • This is discouraged way of modifying DOM
    • If you want to use it in ngOnInit() add { static: true }
      • @ViewChild('localReference', { static: true })
    • You can reference the first occurrence of a given type
      • @ViewChild(MyComponent)
  4. <ng-content></ng-content>
    • Directive used to include all HTML found within this tag
      • By default all HTML within Component HTML tags is lost
  5. @ContentChild()
    • Used to pass data HTML -> child Component’s TS
    • It passes local reference from content into the child component
    • @ContentChild('contentReference')
      • If you want to use it in ngAfterContentInit() add { static: true }
        • @ContentChild('contentReference', { static: true })
    • E.g.:
      • main.component.html
        • 1
          2
          3
          
           <app-widget-component>
               <p #contentParagraph>Some Content to be passed to WidgetComponent</p>
           </app-widget-component>
          
      • widget.component.ts
        • 1
          
          @ContentChild('contentParagraph', { static: true }) paragraph: ElementRef
          
  6. Lifecycle Hooks
    • ngOnChanges() - after on bound input property change (@Inputs)
      • It receives a SimpleChanges object
    • ngOnInit() - Component is initialized (it is not not yet added to DOM)
      • Runs after constructor
    • ngDoCheck() - runs whenever Angular change detection runs, e.g. button clicked
    • ngAfterContentInit() - whenever <ng-content> was projected into the View
    • ngAfterContentChecked() - every time projected content was checked
    • ngAfterViewInit() - after Component’s View and child Views was initialized (rendered)
    • ngAfterViewCheck() - every time Component’s View and child Views was checked
    • ngOnDestroy() - right before Component will be destroyed

Services

  1. To make Service injected, add it into providers property of @Component directive (to tell Angular how to handle this service), and add it into constructor:
    • 1
      2
      3
      4
      5
      6
      7
      
      @Component({
        // ...
        providers: [MyService]
      })
      export class MyComponent {
        constructor(private myService: MyService) {}
      }
      
  2. Hierarchical Injector
    • It provides the same instance of a Service for a given Component and its all children.
    • It’s important to note that therefore it’s not a pure singleton from Spring, but something like hierarchy singleton, therefore multiple instances of a given service/dependency may exist.
    • In short, it goes down, not up
    • Scopes:
      1. The whole Application
        1. Add @Injectable({providedIn: 'root'}) Decorator
        2. Or before Angular 6:
          • Add in AppModule
      2. All Components
        1. Placing instance in AppComponent
          • makes it available in all Components
          • (but not for e.g. other Services)
      3. Component (and its children)
        • Place it on Component
        • ⚠️ this way it can override dependency provided on a higher level (like AppModule or AppComponent)
          • 💡 So if you don’t want to duplicate dependency, don’t add it into providers array - place it only on constructor
  3. To make Service injected into another Service:
    • it must be in the Application-wide scope
    • you have to add @Injectable() decorator for the class
      • (@Injectable() marks class as one into which we want to inject something)
      • However (since some version) it’s recommended to add this annotation also on classes we want to be injected

How it works

  1. Angular actually replaces Component tags like <app-root></app-root> with HTML defined in corresponding .html files
  2. Angular is for creating SPAs - Single Page Applications
    • Even if we have Routing, etc. it’s still an SPA
    • It is because there’s only one index.html
  3. Angular CLI injects into <script> tag code that starts Angular
  4. The first code that gets executed is main.ts file
  5. By default, directories aren’t scanned, so you have to specify Components in app.module.ts in @NgModule in declarations property
  6. TL;DR is that Angular changes DOM (HTML) at runtime
  7. How Angular handles CSSes
    • It adds a certain unique property to all HTML elements in the given component and applies styles only on elements with matching property, e.g.
      • <p _ngcontent-ejo-1>This text should be red</p>
      • p[_ngcontent-ejo-1] { font-color: red; }
  8. View Encapsulation
    • There are 3 types:
      • None - don’t encapsulate CSS, so it will be applies globally (se even in parent Components)
      • Native (ShadowDOM) - encapsulates CSS natively, but only in browsers which support it
      • Emulated - like real ShadowDOM, but it’s emulated, so all browsers will handle it properly
    • You can set it in:
      • @Component({ encapsulation: ViewEncapsulation.None })

Definitions

  1. Decorator
    • @Something <- this is Decorator
    • Java Annotations are TypeScript Decorators

Various

Various

  1. Inline HTML/CSS for Component
    • You can define HTML template in @Component Decorator inline
      • instead of templateUrl property, use just template
      • instead of stylesUrl, use styles
  2. innerText ~ ``
    • Both these expressions are equivalent:
      • <p [innerText]="myValue"></p>
      • <p></p>
  3. Shortcut for defining class fields
    • Put them into constructor like
      • constructor(public field: string, public anotherField: int)

Emmet

  1. Emmet
    • It’s IDE plugin, which helps to work with HTML, CSS, JSX by expanding abbreviations
    • e.g. app-component, Tab becomes <app-component></app-component>
  2. Create div with given class
    • creates div with given class
    • .container -> <div class="container"></div>
  3. Create nested HTML
    • .row>.col-xs-12 -> <div class="row"><div class="col-xs-12"></div></div>

The rest

Bootstrap

  1. Create button group
    • <div class="btn-group"></div>

Testing

Running tests on WSL2 requires to handle Chrome executable.
Therefore working with them is more straightforward on host system.

  1. Run tests
    • Build app in watch mode, launch Karma test runner
    • ng test
  2. Customize Karma test runner
    • Create karma.conf.js, update angular.json
    • ng generate config karma
  3. Run tests in CI/CD
    • ng test --no-watch --no-progress --browsers=ChromeHeadless
  4. Generate Coverage Report
    • ng test --no-watch --code-coverage
  5. Some ways of injecting dependencies
    1. Just use real production service
      • new SubjectUnderTest(new ProdService())
    2. Use fake service
      • new SubjectUnderTest(new FakeService())
    3. Use fake object
      • new SubjectUnderTest({ someFunction: () => 'fake value' } as ProdService)
    4. Use Spy
      • 1
        2
        3
        4
        
        const serviceSpy = jasmine.createSpyObj("ProdService", [ 'someFunction' ]);
        const stubValue = "stub value";
        serviceSpy.someFunction.and.returnValue(stubValue);
        new SubjectUnderTest(serviceSpy);
        

TestBed

  1. TestBed creates a dynamically-constructed Angular test module that emulates an Angular @NgModule.
  2. TestBed.configureTestingModule() method takes a metadata object that can have most of the properties of an @NgModule.
  3. When you want to test a Service, use providers metadata property and specify there other Services you want to mock.
    • TestBed.configureTestingModule({ providers: [ MyService, { provide: OtherService, useValue: spy } ] });
    • Or if using real class, instead of useValue, use: useClass: TestOtherService
  4. Inject it then using const service = TestBed.inject(MyService)
  5. Example:

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      
      let myService: MyService;
      let otherServiceSpy: jasmine.SpyObj<OtherService>;
      
      beforeEach(() => (
        const spy = jasmine.createSpyObj('OtherService', ['someFunction']);
        TestBed.configureTestingModule({ providers: [ MyService, { provide: OtherService, useValue: spy } ]});
      ));
      
      myService = TestBed.inject(MyService);
      otherServiceSpy = TestBed.inject(OtherService) as jasmine SpyObj<OtherService>;
      

Testing HTTP Services

  1. ❗ Always provide both next and error callbacks for Observables.
    Otherwise you may encounter other tests failing in random places!
    • It is because without specifying error, you end up with an asynchronous uncaught observable error.
  2. Example of HTTP test:

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      
      let httpClientSpy: jasmine.SpyObj<HttpClient>;
      let myService: MyService;
      
      beforeEach(() => {
        httpClientSpy = jasmine.createObjSpy('HttpClient', ['get']);
        myService = new MyService(httpClientSpy);
      });
      
      it('should return expected response', (done: DoneFn) => {
        const expectedValue = 'correct value';
      
        httpClientSpy.get.and.returnValue(asyncData(expectedValue));
      
        myService.someFunction().subscribe({
            next: (returnedValue) => {
                expect(returnedValue).toEqual(expectedValue);
                done();
            },
            error: done.fail,
        });
        expect(httpClientSpy.get.calls.count()).toBe(1);
      });
      
  3. HttpClientTestingModule
    • Used for complex interactions
    • It’s covered in HttpGuide

Testing Components

  1. Remember to manually call lifecycle hook methods like ngOnInit (just as Angular would do)

DOM Testing

  1. Component is more than just a class. It interacts with DOM and other Components.
    • Therefore tests covering only a class won’t tell if Component renders, interacts with user or interact with other (e.g. parent or child) Components properly.
    • You need to examine DOM and simulate user interactions to be able to confirm Component works as intended.
  2. When function call is asynchronous, you can use waitForAsync()
    • 1
      2
      3
      
      beforeEach(waitForAsync(() => {
        TestBed.configureTestingModule({ imports: [ BannerComponent ]}).compileComponents();
      }));
      
  3. Basic Component test assertion
    • Component can be created
    • expect(component).toBeDefined();
  4. createComponent()
    • TestBed.createComponent(MyComponent) creates an instance of MyComponent, adds it to the DOM and returns a ComponentFixture
  5. ComponentFixture
    • It’s a harness for interacting with the created Component and its corresponding element
    • 1
      2
      3
      
      const componentFixture = TestBed.createComponent(MyComponent);
      const component = componentFixture.componentInstance;
      expect(component).toBeDefined();
      
  6. ❗ Don’t reconfigure TestBed after calling createComponent()
    • createComponent() freezes the current TestBed definition, which makes it closed to further modifications
  7. NativeElement
    • It’s any type, as at a compile time it’s unknown what type it is or even if it’s an HTMLElement
      • Tests may be run on a non-browser platform such as WebWorker or any other that doesn’t have DOM or where DOM-emulation doesn’t support full HTMLElement API
    • In standard browser environment nativeElement will always be an HTMLElement (or one of it’s derived classes)
    • You can use standard HTML API then, e.g. querySelector()
      • 1
        2
        3
        
        const bannerElement: HTMLElement = componentFixture.nativeElement;
        const paragraph = bannerElement.querySelector('p')!;
        expect(p.textContext).toEqual('Hello World');
        
    • Under the hood it’s actually fixture.debugElement.nativeElement
  8. DebugElement
    • An abstraction to work safely across all platforms
    • Angular creates a DebugElement tree which wraps NativeElements for the running platform
    • DebugElement contains methods and properties useful in testing
  9. Example of testing

Legend

❗ - highly important information

⚠️ - important information

💡 - tip, good practice

🛠️ - troubleshooting


💠✔️💭💬🗨️