Security in Angular Applications

Author: Bibhas Bhattacharya

This tutorial is adapted from the Web Age course Advanced Angular 12 Programming Training.

1.1 Overview of Authentication and Authorization

Authentication is the process of reliably identifying a user. 

Authorization is the process of giving a user access to only the resources she is allowed to access.

Common programming tasks:

  • Provide a login page where user enters her credentials.
  • Upon a successful login the server issues a token identifying the user. The browser side saves that token in memory or local storage.
  • Limit access to application functions based on user privilege. This is usually done using role based security.
  • With every HTTP call to the backend include the user identity token. Depending on the technology used this is usually done as a cookie or HTTP header.
  • If the user accesses a protected resource (such as an Angular route) and the identity token does not exist or has expired then redirect to the login page.
  • If a HTTP call returns 403 or similar then redirect to the login page.

1.2 About Identity Token

The two most common ways to create an identity token are SAML2 and JWT.

In the end, they both achieve the same goals:

  • An identity token issued by the server contains user’s unique ID, name, e-mail address, various security roles, and any other kind of business-specific payload.
  • The token is signed by the server. It is very difficult to generate a fake token by a third party. When a client sends a token with an HTTP request the server can reliably ensure that the token was issued by it and has not been tampered with.
  • A token is however vulnerable to stealing via man in a middle attack. The server can not discern if a token was stolen. To prevent that always transfer tokens over HTTPS and set a token to expire every few hours.

SAML2 is widely used in the enterprise. But JWT is a more modern standard and much easier to use. In this chapter, we will use JWT in the examples but the basic approach will be the same for SAML2.

1.3 Exchanging the Identity Token

Upon successful login, the server will issue the identity token. The client needs to send this back in the future with every HTTP request. Exactly how the server and client do this depends on various things. Here are two common scenarios:

  • Cookie-based – The server expects the token to be in a cookie. In which case the server sets the cookie and the browser automatically sends it back with every request.
  • Header based – In JWT the client usually sends the token back in the “Authorization” request header. In that case, the server issues the token in the response body of the login request. The client needs to write code to save that token and set the Authorization header for all future requests.
//Example response from server containing a JWT token
{
  token: "abc-123"
}

//Example Authorization request header
Authorization: Bearer abc-123

1.4 Example Login Code in Angular Service

Saves identity token in session storage

@Injectable(...)
export class AuthenticationService {
  constructor(private http:HttpClient) { }

  login(credentials:Object) : Observable<Object> {
    return this.http.post<Object>("/login", credentials)
      .pipe(tap(response => 
          window.sessionStorage.setItem("auth-token", 
          response["token"])))
  }
  getAuthToken() : string {
    return window.sessionStorage.getItem("auth-token")
  }
}

Notes

The example above assumes that the identity token is available from the response of the “/login” call as follows:

{
  token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.0gyt4I1ZFGzzDZBrK9jwOIH4NSWvs0k4snp-ZcwKLVU"
}

1.5 Options for Saving the Identity Token

You can save the token in a service class member variable. Because Angular apps are single page the service instance is preserved as the user navigates between views.

In session storage: This is per server domain and per tab. The storage is cleared when the tab is closed.

In local storage. This is per domain and survives a browser restart.

1.6 Sending the Token to Server

You can develop an interceptor to set the Authorization header for every backend call.

@Injectable()
export class AuthInterceptorService implements HttpInterceptor {
  constructor(private authSvc: AuthenticationService) { }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (this.authSvc.getAuthToken() === null) {
      return next.handle(req)
    }

    req = req.clone({
      setHeaders: { "Authorization": `Bearer ${this.authSvc.getAuthToken()}` }
    })

    return next.handle(req)
  }
}

1.7 Obtaining User Information

JWT allows the server to encode user information such as full name, profile photo URL and user roles in the identity token. This information is called payload of the token.

You can decode the token to obtain the payload. Use the jwt-decode package to do this.

npm install jwt-decode

npm install @types/jwt-decode

Example code.

import jwt_decode from "jwt-decode";

export class UserProfile {
  photoURL!:string
  name!:string
  roles!:string[]
}

let jwtToken = "ABC1728...2xcv"
let userProfile = jwt_decode<UserProfile>(jwtToken)

1.8 Cross Site Scripting (XSS) Attack

This attack is mounted by a user by somehow embedding a <script> tag in a page. This can be done for example by submitting a comment or writing a user profile that contains the <script> tag. 

When another user visits the infected page, the script can then collect private information about that user from the DOM such as cookie, names, and emails. This information then can be passed to a third-party site using a mechanism that bypasses the same-origin policy. Such as setting the src attribute of an image or adding a <script> tag with a source from another site.

XSS is one of the most common forms of a security breach on the web.

To prevent XSS reject any HTML content from untrusted sources. For example, you can severely limit or completely eliminate any HTML in user comments. But allow company staff to allow entering HTML in product catalog content.

1.9 Angular and XSS

By default, Angular considers values stored in any component variable as untrusted. But any HTML directly entered in the template is trusted.

In this example, the <div> and <b> tags in the template are trusted. But the <h1> tag in the comment variable is not trusted. Angular will sanitize variables (escape the HTML) before setting DOM element properties.

@Component({...
  template: `<div><b>Comment:</b> {{comment}}</div>`
})
export class AppComponent {
  comment = '<h1>Hello World</h1>'
}

//Generates this DOM element
<div><b>Comment:</b> &lt;h1&gt;Hello World&lt;/h1&gt;</div>

1.10 Sanitizing Content

Angular uses different strategies to sanitize the value in a variable depending on where in the template the variable is used:

  • HTML – When the variable is used to set the innerHTML of an element. We see this in the example in the previous slide.
  • Style – When you bind the variable to the “style” property of an element.
  • URL – When you bind the variable to the “href” property.
  • Resource URL – When you bind the variable to the “src” attribute of an image or script.

A different strategy is used in these cases because an XSS attack is mounted differently in each of these situations.

1.11 Trusting HTML Content

To bypass Angular’s sanitization for trusted content take these steps:

  • Convert a string to SafeHtml by calling the bypassSecurityTrustHtml() method of DomSanitizer service.
  • Bind the SafeHtml object to the innerHTML property of an element.

The same example as before converted to bypass sanitization.

import { DomSanitizer, SafeHtml } from '@angular/platform-browser';

@Component({...,
  template: `<div><b>Comment:</b> <span  [innerHTML]="getSafeComment()"></span></div>`
})
export class AppComponent {
  comment = '<h1>Hello World</h1>'
  constructor(private sanitizer: DomSanitizer){}
  getSafeComment() : SafeHtml {
    return this
      .sanitizer
      .bypassSecurityTrustHtml(this.comment)
  }
}

1.12 Cross-Site Request Forgery (XSRF)

In this attack, a page from a malicious site (evil.com) somehow sends a request to a legitimate site (good.com). Such a request needs to bypass the same-origin policy which is quite easy. For example, request an image from good.com or submit a form using JavaScript.

If the user is currently logged into the legitimate site then such a request can cause all kinds of unintended consequence. The damage level increases with the level of privilege the user has with the legitimate site.

XSRF attack is technically very easy to mount. The only challenge is in how to lure users into the malicious site. This is usually done through phishing attack.

Details of XSRF and strategies to mitigate it are beyond the scope of this class. Read about it in www.owasp.org.

1.13 Preventing XSRF Using a Token

The server issues a difficult-to-guess unique token. The Angular app sends the token back with each request in a custom request header. The server verifies the token. Currently there is no way for a malicious web page to set custom headers prior to requesting an image or submitting a form to the legitimate web site.

Angular’s HttpClient service has some support for this. To use this, take these steps:

  • The server must first issue a unique token using the XSRF-TOKEN cookie. set-cookie: XSRF-TOKEN=example-xsrf-token;Path=/
  • For all future POST, PUT, DELETE requests HttpClient will set the X-XSRF-TOKEN custom header. X-XSRF-TOKEN: example-xsrf-token
  • From the server-side verify that X-XSRF-TOKEN custom header is set. Frequently issue a new token to further strengthen security.
  • In addition, take all other mitigation steps outlined in OWASP web site. Like verify the Referer request header to make sure request is coming from a page served by the legitimate web site.

1.14 Summary

In this tutorial, we covered:

  • Upon successful authentication, the server should issue an identity token. Angular app should save the token in memory, local or session storage depending on the business requirement.
  • To make any secure backend call the application needs to send that token back. Server validates the token to make sure that it was not made up by a third party or has not expired.
  • You can obtain user information such as name and email by decoding the token.
  • Angular provides ways to mitigate Cross Site Scripting and Cross Site Request Forgery attacks.

Advanced Components Concepts in Angular

Author: Bibhas Bhattacharya

This tutorial is adapted by Web Age course Advanced Angular 12 Programming Training.

1.1 Detecting Change to @Input Binding

When a parent component updates a property of a child bound using @Input the child may like to get notified of the change. This is necessary when a change in a component’s state requires further processing. Example: When you change the sorting order property of a product list component it needs to sort the products differently.

There are two ways a child can intercept changes to an @Input property:

  • Using a custom setter
  • Using ngOnChanges

1.2 Example Child and Parent Components

We will use these components to illustrate both approaches. The parent provides a GUI to increment a counter. The child simply displays the counter value.

@Component({
  selector: 'app-child',
  template: `<p>{{counter}}</p>`
})
export class ChildComponent {
  @Input() counter = 0
}

@Component({
  selector: 'app-parent',
  template: `<app-child [counter]="p_counter"></app-child>
  <button (click)="update()">Update</button>`
})
export class ParentComponent {
  p_counter = 0

  update() { this.p_counter += 1 }
}

1.3 Using a Custom Setter

@Component({
  selector: 'app-child',
  template: `<p>{{counter}}</p>`
})
export class ChildComponent {
  _counter = 0

  @Input() 
  set counter(newValue:number) {
    console.log("Intercepted update:", newValue)
    this._counter = newValue
  }

  get counter() {
    return this._counter
  }
}

Notes

In the example above “counter” is now a computed property with a custom getter and setter. It internally uses the _counter member variable to store state.

Code for the parent component doesn’t change. We still bind to the counter property.

1.4 Using ngOnChanges

import { Component, Input, OnChanges, SimpleChange } from '@angular/core';

@Component({...})
export class ChildComponent implements OnChanges {
  @Input() counter = 0

  ngOnChanges(changes: {[key: string]: SimpleChange}) {
    let c = changes["counter"]
    console.log(c.previousValue, c.currentValue, c.firstChange)
  }
}
//Prints
undefined 0 true
0 1 false
1 2 false

Notes

ngOnChanges receives a dictionary where the key is a string with the same name as the property that was modified. The value is a SimpleChange object. That object has these properties:

  • 1. firstChange – If the property is being set for the first time.
  • 2. previousValue – The value before the change. It is normally undefined when firstChange is true.
  • 3. currentValue – The value after the update.

1.5 Advanced Inter Component Communication

Data and event binding using @Input and @Outrput work very well for immediate parent-child components. But this becomes cumbersome for deeply nested components or for communication between siblings.

Angular provides a couple of ways to deal with these situations:

  • A component can directly access an instance of a child component.
  • A component can directly access an instance of an ancestor.
  • For truly complex situations use the Subject API discussed later in this chapter.

Notes

For example, these situations become difficult to implement using @Input and @Output:

◦ A parent wants to set a property of a grand child or a child further down the descendants hierarchy. Every component in the chain has to set up @Input binding for the property.

◦ A component needs to raise an event for a grand parent or a component further higher in the hierarchy. Every component in the chain needs to setup an @Output binding.

◦ A component needs to communicate with a sibling.

◦ A component needs to invoke a method of another component.

1.6 Direct Access to Child

You can access an immediate child directive or component in a few ways:

  • Using a template local variable for the child component instance.
  • Using the @ViewChild decorator. This is strongly typed and recommended over template local variables.
  • Use the @ViewChildren decorator to get a collection of children and monitor changes in that collection.

Note: these give you access to immediate children and not any arbitrary descendants down the hierarchy.

1.7 Using a Template Local Variable

@Component({
  selector: 'app-parent',
  template: `
  <app-child #ch></app-child>
  <button (click)="ch.counter = ch.counter + 1" >
    Update</button>
  `
})
export class ParentComponent {
}

Notes

In the example above we declare a local variable called “ch” for an instance of ChildComponent. From the button click handler, we access the counter variable of the class. You could also call a method of ChildComponent from the template this way.

1.8 Using the @ViewChild Decorator

import { Component, ViewChild } from '@angular/core';
import { ChildComponent } from '../child/child.component';

@Component({
  selector: 'app-parent',
  template: `<app-child></app-child>
  <button (click)="update()">Update</button>`
})
export class ParentComponent {
  @ViewChild(ChildComponent, {static: false}) 
  child:ChildComponent

  update() { this.child.counter += 1 }
}
  • Access to the ‘child’ property depends the type of the childComponent as well as the timing.
  • In the case above the variable is available after the view initializes (from ngAfterViewInit()) but is not yet available when ngOnInit() is called.
  • If the DOM changes and children are added or removed the variable is kept up to date.

Notes

The @ViewChild decorator locates the child component by its class. This works well when there is only one instance of the child component inside the parent. If there are multiple instances then @ViewChild returns the first one. To get a specific instance you need to declare a template local variable for the child and refer to the variable name from @ViewChild. In the example below we update the counter of the second child.

@Component({
  selector: 'app-parent',
  template: `
  <app-child #ch1></app-child>
  <app-child #ch2></app-child>
  <button (click)="update()">Update</button>
  `
})
export class ParentComponent {
  @ViewChild("ch2", {static: true}) 
  child:ChildComponent

  update() {
    this.child.counter += 1
  }
}

1.9 Static and Dynamic Children with @ViewChild

Children referenced by @ViewChild have two types:

  • Dynamic {static:false} – If they are wrapped by an ngFor or ngInit and are thus subject to change.
  • Static {static:true} – If they are not dynamic

The child’s static state needs to be declared in the second parameter (options) of the @ViewChild annotation.

@ViewChild('ch1', {static:true} ) prop1: StaticChildComponent;
@ViewChild('ch2', {static:false} ) prop2: DynamicChildComponent;

As a rule properties based on Dynamic view children should be used only after the view initializes (for example from within ngAfterViewInit()) and not before.

1.10 More About @ViewChild

There may be several objects associated with the same element in the template. For example, an element can have many attribute directives. You can specify the object you need by supplying its type in the options object parameter of @ViewChild.

@Component({
  selector: 'app-parent',
  template: `<div #d></div>`
})
export class ParentComponent implements OnInit {
  @ViewChild("d", 
   {static: false, read: ViewContainerRef}) childContainer
  @ViewChild("d", 
   {static: false, read: ElementRef}) childElement

  ngOnInit() {
    console.log(this.childContainer)
    console.log(this.childElement)
  }
}

Notes

You specify the type of object instance you are querying using the “read” property. If you do not supply the read property Angular behaves this way:

  • If the child is a component tag then the component instance is returned.
  • Otherwise, the ElementRef instance for the tag is returned.

1.11 The @ViewChildren Decorator

Similar to @ViewChild but gives you access to a collection of all matching children.

import { Component, AfterViewInit, ViewChildren, QueryList } from '@angular/core';

@Component({
  selector: 'app-parent',
  template: `<app-child [counter]="1"></app-child>
  <app-child [counter]="2"></app-child>`
})
export class ParentComponent implements AfterViewInit {

  @ViewChildren(ChildComponent) childList: QueryList<ChildComponent>

  ngAfterViewInit() {
    this.childList.forEach(child => 
        console.log(child.counter))
  }
}

Note: the @ViewChildren decorator does not require the static state {static:boolean} option parameter like we saw with @ViewChild

1.12 Live Monitoring of Children

You can monitor any dynamic addition or removal of children.

@Component({
  selector: 'app-parent',
  template: `<app-child [counter]="1"></app-child>
  <app-child *ngIf="showTwo" [counter]="2"></app-child>`
})
export class ParentComponent implements AfterViewInit {
  showTwo = false
  @ViewChildren(ChildComponent) childList: QueryList<ChildComponent>

  ngAfterViewInit() {
    this.childList.changes.subscribe(queryList => 
      queryList.forEach(child => console.log(child.counter)))

    window.setTimeout(_ => this.showTwo = true, 1000)
  }
}

Notes

In the example above we add the second child after one second. This change is picked up by our subscription callback. The callback receives a new QueryList which you can iterate over.

1.13 Direct Access to the Parent Component

You can inject an instance of an ancestor component.

export class ChildComponent implements OnInit {
  constructor(private parent:ParentComponent, private grandParent:AppComponent) { }
}

Since @ViewChild and @ViewChildren do not give access to deeply nested children it is better for these children to lookup an ancestor and initiate communication.

To restrict the search to the immediate parent use @Host.

  constructor(@Host() private parent:ParentComponent) { }

You can make a component more flexible by not requiring it to have a specific parent. Use @Optional for that.

  constructor(@Optional() @Host() private parent:ParentComponent) { }

Notes

@ViewChild and @ViewChildren limit access to the immediate children only. They are not very suitable when a parent needs to communicate with a deeply nested descendent. In a situation like that it will be much easier for the descendants to lookup an ancestor component and then make method calls to initiate communication.

1.14 Communication Using Subject API

When deeply nested components need to notify events to each other data and event binding may become complex.

In a situation like this RxJS Subject API can considerably simplify the implementation. The basic steps are as follows:

  • Use a singleton service to create a subject (or topic of conversation).
  • To notify an event a component will post a message to the subject.
  • Any other component can subscribe to the subject and be notified.

1.15 Creating a Subject

import { Subject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class TopicManagerService {
  public cartTopic = new Subject<CartItem>()
}

A subject is strongly typed. You can specify the data type of messages posted in the subject when creating the subject.

1.16 Publishing a Message in a Subject

In this example, C1 component is publishing a message.

@Component({
  selector: 'app-c1',
  template: `<button (click)="publish()">Go</button>`
})
export class C1Component {
  constructor(private tm:TopicManagerService) { }

  publish() {
    this.tm.cartTopic.next({itemId: 1001, qty: 2})
  }
}

1.17 Subscribing to the Subject

In this example C2 is subscribing. Code will be same for C3.

export class C2Component implements OnInit {
  constructor(private tm:TopicManagerService) { }

  ngOnInit() {
    this.tm.cartTopic.subscribe(item => console.log(item))
  }
}

1.18 Problem With Ordering

If you subscribe after a message is posted you will not receive the message. This can be a problem if components are posting and subscribing from ngOnInt since the order of initialization can be hard to control.

To receive the last posted message even if you have subscribed late, use the BehaviorSubject class instead of a plain Subject. The constructor needs a starter message which can be undefined.

public cartTopic = new BehaviorSubject<CartItem>(undefined)

To receive more than one last posted messages use ReplaySubject. The constructor takes the number of past messages.

public cartTopic = new ReplaySubject<CartItem>(5)

1.19 Content Projection

Content projection lets a parent component (i.e., the user of the component) provide parts of the template of a child component.

This has many useful benefits:

  • The parent can customize how the component should render data.
  • UI toolkit components can let users decide what content gets displayed. For example, a card component can simply focus on showing a shaded border and a title but the parent component can decide what gets displayed in the card.

1.20 Setup Projection Using ng-content

The ng-content directive lets you specify a placeholder. A user of the component can supply the actual template for that area. Below is a card component that has two placeholders.

@Component({
  selector: 'app-card',
  template: `<div class="card">
    <ng-content select=".title"></ng-content><hr/>
    <ng-content select=".body"></ng-content>    
  </div>`
})
export class ChildComponent {}

ng-content takes a CSS selector which helps the parent supply different templates for different placeholders. We see that next.

1.21 Supplying Template for ng-content

Here a user of the card component supplies its templates using matching CSS selectors.

@Component({
  selector: 'app-parent',
  template: `<app-card>
    <div class="title">First Card</div>
    <div class="body">First card body here.</div>
  </app-card>
  <app-card>
    <div class="title">{{title2}}</div>
    <div class="body"><button (click)="greet()">Hello</button></div>
  </app-card>`
})
export class ParentComponent {
  title2 = "Second Title"
  greet(){}
}

Notes

The template supplied by the parent has full access to its own properties and methods. We see that above in the second card. This works just fine even though the template will actually be executed inside the card component. Without this feature it will be very difficult to create a generic card component that can host any content. Content projection makes it all very easy.

1.22 The Host Element

Angular creates a DOM element for every component on the page. This is called the host element. The host contains as child the elements generated by the component’s template.

In the example of the previous slide the element created for the <app-card> tag will be the host element for the card component.

Sometimes you need to access the host element. For example

  • A card component needs to show a border around it.
  • A popup menu component needs to position the host where the mouse was clicked.

1.23 Static Styling of the Host Element

Use the :host pseudo selector from the component’s CSS.

:host {
    display: block;
    border: 1px solid black;
    width: 200px;
    position: absolute;
}

1.24 Setting DOM Properties of the Host

Use the @HostBinding decorator to one-way bind a component property to a DOM property of the host element.

The following example shows the host element and sets its position.

export class PopupMenuComponent {
  @HostBinding("style.top") y = "0px"
  @HostBinding("style.left") x = "0px"
  @HostBinding("style.visibility") visibility = "hidden"

  openAt(x:number, y:number) {
    this.x = `${x}px`
    this.y = `${y}px`

    this.visibility = "visible"
  }
}

1.25 Dynamically Loading a Component

Some applications may need to dynamically decide what component to show without directly referring to its tag in a template. For example, different products may need to be displayed using different components.

This can be done by a parent component following these steps:

  • Create a ComponentFactory for the component class that you need to display. This is done using the ComponentFactoryResolver service.
  • In the parent’s template add a placeholder element where the child component will be dynamically inserted.
  • Using the ViewContainerRef object for the placeholder element create an instance of the child component. This will add the child to the component tree and make it visible.
  • Add all the child components that are dynamically displayed in the entryComponents list of the application module. Without this the tree shaker will exclude the child components from the build process (since they are never directly used in any template).

1.26 Dynamic Loading Example

In this example the parent component – HostComponent – dynamically loads either BasicViewComponent or FullViewComponent and adds it to the component tree.

User can use a checkbox to toggle between the two components.

The template of the parent component:

@Component({
  selector: 'app-host',
  template: `
  <div>
    <input type="checkbox" (change)="changeView()" [(ngModel)]="isBasic"> Basic view
    <div #compHost></div> <!-- Placeholder for child-->
  </div>
  `
})
export class HostComponent {...}

1.27 HostComponent Code

export class HostComponent {
  isBasic = true
  @ViewChild("compHost", 
             {static: true, read: ViewContainerRef}) 
    container:ViewContainerRef

  constructor(private resolver: ComponentFactoryResolver){}

  changeView() {
    let compClass = this.isBasic ? 
      BasicViewComponent : FullViewComponent
    let factory = 
      this.resolver.resolveComponentFactory(compClass)

    this.container.clear() //Remove old component
    let compRef = this.container.createComponent(factory)
  }
}

Notes

Two key players here are ComponentFactoryResolver and ViewContainerRef. A ViewContainerRef is used to insert a component instance into the component tree. Every element in a template has an associated ViewContainerRef. Here we look it up for the <div> tag with the local variable “compHost”.

A ComponentFactoryResolver gives us a component factory appropriate for a given component class. The createComponent() method of ViewContainerRef takes the factory as input. The method uses the factory to create a new instance of the component and adds it to the component tree.

The createComponent() method returns a ComponentRef object. You can get the actual component instance using the “instance” property of that object. The instance here will be either a BasicViewComponent or FullViewComponent. Once you have the instance you can even set its properties to set its input. There is no other way to do this since you can’t really use data binding to set input for dynamically loaded components.

1.28 Setting entryComponents

Register the dynamically loaded components to the entryComponents list of the application module.

@NgModule({
  declarations: [
    AppComponent,
    HostComponent,
    BasicViewComponent,
    FullViewComponent
  ],
  imports: [...],
  entryComponents: [
    BasicViewComponent,
    FullViewComponent
  ],
...
})
export class AppModule { }

1.29 Optimizing Change Detection

By default almost any user interaction will cause templates for all components on the page to be re-executed. For a complex application with hundreds of components on a page, this can slow down the screen update or cause flickers.

There are two ways you can take control of the situation and only re-execute a component’s template if it is truly necessary:

  • Set the change detection strategy to OnPush. This is the simplest way and should be considered first.
  • By programmatically detaching or attaching a component from the change detection process. This gives you more precise control over when a component’s template should be re-run. Not discussed here.

1.30 Example Excessive Template Execution

@Component({
  selector: 'app-a',
  template: `<input type="text" [(ngModel)]="todoItem"/> 
<button (click)="addItem()">Add</button>

<app-b [todoList]="list"></app-b>`
})
export class AComponent {
  todoItem = ""
  list = []
  addItem() {
    this.list.push(this.todoItem)
  }
}

@Component({
  selector: 'app-b',
  template: `<p *ngFor="let item of todoList">{{item}}</p>`
})
export class BComponent { @Input() todoList = [] }

Notes

As things stand right now every keystroke in the input field of AComponent will cause the template for both components to be re-executed. As you can imagine; if the list is long then rebuilding the DOM for BComponent can take some time.

1.31 Using OnPush Change Detection Strategy

Component B does not need to run its template for every key stroke in the parent component (A). It can use the OnPush strategy.

@Component({
...
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class BComponent {@Input() todoList = []}

Now the template of B will be run only if any of its bound @Input variables have changed.

Unfortunately, this will not solve our problem. Component A adds new items to the existing array. Angular only does shallow checks for changes in the object reference. It does not check if the contents of a variable (such as an array or an object) have changed. We will see next how to work around this.

1.32 Properly Changing @Input Variables

A parent needs to set an entirely new object to a bound property of a child for Angular to realize that the property has changed. In our case AComponent needs to create a new list. Now the template of B will be run only after user has clicked on the Add button.

addItem() {
  this.list = [... this.list, this.todoItem]
}

Note: Recreating a large array like this can be expensive. To solve that issue, store the array inside an object and just recreate the object. Pass the object as input to the child.

1.33 Additional Notes on OnPush

OnPush setting only takes effect if a parent changes variable bound via @Input. However, if the child itself directly changes that variable then the child’s template will be always executed. OnPush has no effect in that case. If change detection is skipped for a child due to the use of OnPush then the entire component tree from the child onwards will be skipped.

1.34 Summary

In this tutorial we covered:

  • Sometimes you need to detect changes to the variables bound by @Input to perform certain work.
  • Basic @Input and @Output based inter-component communication only works between direct parent and children. For communication between arbitrary components it is easier to use direct access or the Subject API.
  • Content projection allows a parent component to supply parts of a child’s template. This is often used by GUI framework components like modal dialog.
  • By default Angular re-executes templates of all components on a page if any change is detected in their state. This can slow down page refresh if you have many components. OnPush is an easy way to limit re-execution of a component’s template only if a @Input bound variable has changed

Deploying and Exposing Applications on AKS

Author: Faheem Javed

This tutorial is adapted from the Web Age course Kubernetes for Developers on AKS Training

1.1 Configuring AKS for Deployment

By default, AKS can only connect to the public Docker Hub registry. To allow AKS to connect to your private ACR, you are required to configure the AKS cluster.

The following command ensures the AKS cluster can access images in the custom ACR registry.

az aks update -n $AKS_NAME -g $RESOURCE_GROUP --attach-acr $ACR_NAME

To refer to ACR images, use the following convention:

{acr_login_server}/{image}:{tag}

1.2 Deploying to Kubernetes

There are various methods that can be used to deploy a workload to Kubernetes.

Method 1: Using Generators (kubectl run, kubectl create (without .yaml), kubectl expose)

If you want to check quickly whether the container is working then you might use Generators.

# creates simple pods that you cannot scale after deployment.
kubectl run nginx-deployment --image=nginx  

# creates a deployment that can be scaled after the deployment has been performed
kubectl create deployment nginx-deployment --image=nginx 

Method 2: Using Imperative way (kubectl create with .yaml)

It can work with one object configuration file at a time that can be checked into a source control repository, such as Git.

It’s a one-time operation, i.e. you cannot run the same configuration file to make changes.

kubectl create -f deployment.yaml

Method 3: Using Declarative way (kubectl apply)

It works with files, directories, and sub-directories containing object configuration YAML files.

You can modify the configuration file(s) and rerun them to update the deployment(s).

kubectl apply -f deployment.yaml
kubectl apply -f folder_name_with_a_bunch_of_yaml_files/

1.3    Kubernetes Services

Kubernetes services expose applications running on a set of Pods as network services.

Services are a solution to the following situation:

  • Instances of an application run in separate Pods
  • Each Pod gets its own IP address
  • Pods come and go based on load
  • How can we access applications when we don’t know which instances are running and what their IP addresses are?

1.4 Service Resources

Using the Generators method, you can use the following commands to deploy an application and expose the service.

kubectl create deployment nginx-deployment --image=nginx 

kubectl expose deployment {deployment-name} --type={type} --port={port} --target-port={target-port}

In production, it’s preferred to use yaml files to define Kubernetes Service Resource Objects.

Example:

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376

This spec creates the ‘my-service” service which exposes “MyApp” on the internal cluster IP address at port 9376.

1.5 Service Type

Services can have one of the following types:

  • ClusterIP (default) – IP address internal to the cluster
  • NodePort – Port on the parent node’s internal cluster IP address. Can be exposed outside the cluster.
  • LoadBalancer – Provides access through a load balancer’s IP address
  • ExternalName – Maps to the external DNS name in the externalName field

1.6 ClusterIP

ClusterIP is the default service type. Services created without a type field are assigned this type.

The following command creates a ClusterIP type service:

kubectl expose deployment hello-nginx --port=8080 --targetPort=80

Details of the created service:

kubectl get services hello-nginx
NAME          TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)    AGE
hello-nginx   ClusterIP   10.98.16.57   <none>        8080/TCP   4m59s

Service can be accessed by shelling into the cluster.

minikube ssh
curl http://10.98.16.57:8080

1.7 NodePort

With a NodePort type of service, Kubernetes exposes the service through a randomly generated port number off of the node where the application pods reside.

Node ports can also be created using the expose command:

kubectl expose deployment hello-nginx --type=NodePort --port=80

Details of the created service

kubectl get services hello-nginx
NAME          TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)       AGE
hello-nginx   NodePort   10.110.148.227   <none>      80:31164/TCP  36s

The service can be accessed through the node IP address and service’s port number:

minikube service hello-nginx --url=true
http://192.168.99.100:31164
curl http://192.168.99.100:31164

Notes

The “minikube service {service-name} –url=true” command combines the node ip address and the service’s port to create a URL that can be used to address the given service

1.8 NodePort from Service Spec

NodePort services can also be created by applying Service Resource specifications like this one:

# my-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type: NodePort
  selector:
    app: MyApp
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30007

Command creates/updates the service:

kubectl apply service -f my-service.yaml

1.9 LoadBalancer

This service type provisions an external LoadBalancer to direct traffic to the selected Pods. This type is often used when running Kubernetes on cloud providers that include their own load balancers. The IP address of the external load balancer populates the service’s status.loadBalancer.ip field

The following command can be used to create this type of service:

kubectl expose deployment hello-nginx --name hello-nginx-lb --type=LoadBalancer --port=80

Details of the created service:

kubectl get services hello-nginx
NAME        TYPE         CLUSTER-IP   EXTERNAL-IP   PORT(S)       AGE
hello-nginx LoadBalancer 10.111.8.184 192.168.122.1 80:30725/TCP  51m

 

The service can be accessed through the node IP address and service’s port number:

minikube service hello-nginx --url=true
http://192.168.122.1:31164

curl http://192.168.122.1:31164

1.10 LoadBalancer from Service Spec

LoadBalance type services can also be created from yaml file specs:

# example-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: example-service
spec:
  selector:
    app: example
  ports:
    - port: 8765
      targetPort: 9376
  type: LoadBalancer

Command to apply the spec:

kubectl apply -f example-service.yaml

Remember: the above requires an external load balancer to work

1.11 ExternalName

ExternalName type services map the service to a DNS name. Redirection to the service happens at the DNS level rather than by proxy or forwarding.          This type maps ‘my-service’ in the ‘prod’ namespace to a server identified by a name (my.database.example.com) that can be resolved by an external network DNS.

Example spec:

apiVersion: v1
kind: Service
metadata:
  name: my-service
  namespace: prod
spec:
  type: ExternalName
  externalName: my.database.example.com

 

This type might be used for example to map a service name inside the cluster to a database server outside the cluster.

Notes

More information on the external name type is available here:

https://kubernetes.io/docs/concepts/services-networking/service/#externalname

1.12 Accessing Applications

Applications can be accessed in a variety of ways:

  • Shell into the app’s container and use localhost
  • Shell into any container in the same pod as the app and again use localhost to access the app
  • Apps on the same node can access apps in different pods using the app pod’s name or IP address
  • Apps outside a cluster can access a service in a k8s cluster through a service-defined port on the cluster’s IP address.
  • The ‘kubectl port-forward’ command can be used to forward a port on the local machine where the cluster is running to a pod running inside the cluster.

1.13 Service Without a Selector

The selector in a service specification is used to identify Pods for a service to point to

spec:
  selector:
    app: MyApp

When a service is meant to point to some other type of back-end (as opposed to Pods) the selector is omitted. You might do this:

  • To access an external production database cluster
  • To point to a service in a different namespace or cluster
  • When only some of your back-ends are hosted by Kubernetes
  • When the service points to one or more Pods an Endpoint is automatically created for the service.

 

For non-Pod services the endpoint must be added manually:

apiVersion: v1
kind: Endpoints
metadata:
  name: my-service
subsets:
  - addresses:
      - ip: 192.0.2.42
    ports:
      - port: 9376

This endpoint then allows the service to point to the IP address and port of any server you choose.

Notes

For more information see:

https://kubernetes.io/docs/concepts/services-networking/service/#services-without-selectors

1.14 Ingress

An Ingress Resource is a Kubernetes object that acts as the entry point for your cluster. An Ingress sits between the external network and the services in the K8s Cluster and lets you expose multiple services under the same IP address. 

Ingresses can be used to provide:

  • Routes from network traffic outside the cluster to services within the cluster
  • Load balancing
  • SSL/TLS Termination
  • Name-based virtual hosting

1.15 Ingress Resource Example

Example:

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: example-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
  rules:
  - host: hello-world.info
    http:
      paths:
      - path: /
        backend:
          serviceName: web
          servicePort: 8080

Notes

Documentation for Ingress’ can be found here:

https://kubernetes.io/docs/concepts/services-networking/ingress/

An ingress tutorial can be found here:

https://kubernetes.io/docs/tasks/access-application-cluster/ingress-minikube/

1.16 Ingress Controller

An Ingress Controller implements the features defined in the Ingress object. Ingress objects will have NO EFFECT unless an ingress controller has been deployed. 

Two Ingress Controllers supported by the Kubernetes project are:

  • nginx ingress controller for Kubernetes
  • GCE ingress-gce

 

Several other ingress controllers are also available. 

Setting up to use the ingress-nginx ingress controller on minikube:

minikube addons enable ingress

Setting up the ingress-nginx controller on other platforms:

Notes

Documentation on Ingress Controllers can be found here:

https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/

1.17 Service Mesh

A service mesh can be thought of as an infrastructure layer for service-to-service communication.  A service mesh can implement a variety of cross-cutting features including: Load Balancing, Circuit Breaking Retry & Timeout, Seciurity(TLS), Monitoring, Traffic Metrics. A Service Mesh is typically implemented as a proxy deployed in a sidecar container next to each instance container of an application

Examples of Service Mesh include:

  • Open Service Mesh (OSM) (backed by Microsoft)
  • Istio (backed by Google & IBM)
  • Maesh
  • Kuma
  • AWS App Mesh

For more information on Service Mesh see:

https://servicemesh.es/ 

1.18 Summary

In this tutorial, we covered:

  • Kubernetes Services
  • Service Type
  • ClusterIP
  • NodePort
  • LoadBalancer
  • ExternalName
  • Accessing Applications
  • Services Without a Selector
  • Ingresses
  • Service Mesh