Templates - Advanced

Structural Directives

short info about the asterisk (*)

  • a * is the signal for a structural Directive
  • the asterisk replaces a more complicated syntax with a short version
  • it is used for *ngIf, *ngFor and *ngSwitch
  • An example will show the differences

*ngIf

to show and hide content

removes the host element from the DOM

                    
                         
                         <div *ngIf="search.length > 1">
                            
                         </div>
                    
                
                    
                         search: string = "";
                    
                

pretty useful as a guard for null checks

                    
                         <div *ngIf="person">
                            

{{person.firstName}}

</div>
Important: There is a huge difference between hiding an element with css and removing it with *ngIf from the DOM. When hidden with css, the element still uses resources such as memory. *ngIf is most of the time the better approach.

Task 7.1 - *ngIf

  • Use a radio button with the values "simple" and "advanced"
  • when advanced is selected, show an Input field
  • if the Input contains more than 2 characters, display the input text under the field
  • Use a bal-radio
  • Use a bal-input
  • Hint: a bal-radio-group can be created, when the bal-radio elements have the same value for the name-property

Task 7.1 - Possible Solution

                     
                        <bal-radio-group [value]="setting" (balChange)="setting=$event.detail">
                            <bal-radio name="myRadio" value="simple">Simple</bal-radio>
                            <bal-radio name="myRadio" value="advanced">Advanced</bal-radio>
                        </bal-radio-group>
                          
                        <div *ngIf="setting === 'advanced'">
                            <bal-input
                              placeholder="advanced"
                              [value]="advancedValue"
                              (balInput)="advancedValue=$event.detail!.toString()"
                            ></bal-input>
                            <p *ngIf="advancedValue.length > 2">
                              Value: {{advancedValue}}
                            </p>
                        </div>
                     
                    
                         setting: string = "simple";
                         advancedValue: string = "";
                    
                 

*ngIf - short vs. long syntax

                    
                         
                         <div *ngIf="search.length > 1">
                            
                         </div>
                    
                    
                        
                        <template [ngIf]="search.length > 1">
                            
</template>

*ngFor

to iterate over collections

                    
                        
    <li *ngFor="let customer of customers"> {{ customer.name}} </li>
<div *ngFor="let item of items; let idx = index;"> </div>

*ngFor - Syntax

  • custom Angular Syntax
  • the block with *ngFor is repeated
  • takes every element of the array and assigns it to item. A li element is created for every iteration.
  • the so called template input variable is only valid in the scope of the element and its children

Task 7.2 - *ngFor

  • create an array of todo's
  • Hint: todos = [{title: 'A'}, {title: 'B'}];
  • every todo should have at least 2 properties (title, description)
  • render every single todo with a h2 - a heading for the title and a paragraph for other properties

Task 7.2 - Possible Solution

                     
                        <div *ngFor="let todo of todos">
                            <h2>{{todo.title}}</h2>
                            <p>{{todo.desc}}</p>
                        </div>
                     
                    
                            todos: any[]= [{
                                title: "*ngFor",
                                desc: "find solution"
                            }, {
                                title: "*ngSwitch",
                                desc: "take a nap"
                            }];
                    
                 

*ngFor Advanced - helper variables

helper boolean variables for odd, even, first and last and the index variable

                    
                        <div *ngFor="let todo of todos; let idx = index;
                            let first = first; let last = last;
                            let even = even; let odd = odd;">
                            {{idx}} // 0
                            {{first}} // true
                            {{last}} // true
                            {{even}} // true
                            {{odd}} // false
                        </div>
                    
                

*ngFor Advanced - custom trackBy

Object identity is used to make changes in collections available in the DOM. The behaviour can be controlled with a custom trackBy function. Default is object identity ===

                    
                        <div *ngFor="let todo of todos; trackBy: myTrackFunc">
                        </div>
                    
                    
                        myTrackFunc(index, todo) {
                            return todo.id;
                        }
                    
                

*ngFor - short vs. long syntax

                    
                        <div *ngFor="let item of items; let idx = index;">
                            
                        </div>
                    
                    
                        <template ngFor let--item let-idx="index"
                            [ngForOf]="items">
                            <div>
                                
                            </div>
                        </template>
                    
                

*ngSwitch

  • [ngSwitch]="expression" is the directive with the switch value
  • *ngSwitchCase="value" is the directive with the case value, if it's equal to the switch value, the element will be attached to the DOM
  • *ngSwitchDefault attaches the element to the DOM if none of the cases matches

example

                    
                        <div [ngSwitch]="selectedFruit">
                            <apple-view *ngSwitchCase="Fruit.APPLE">
                                </apple-view>

                            <orange-view *ngSwitchCase="Fruit.ORANGE">
                                </orange-view>

                            <no-fruit-selected *ngSwitchDefault>
                                </no-fruit-selected>
                        </div>
                    
                

ngClass

for dynamic CSS classes

for single classes - use a class binding

                
                    

for multiple classes: use ngClass directive

                
                    <div [ngClass]="{
                      'active': customer.active,
                      'inactive': !customer.active}">
                    </div>
                
            

ngStyle

for dynamic inline styles

for single styles:use a style binding

                    
                        <span
                            [style.font-weight]="pers.isVip ? 'bold' : 'normal'">
                        </span>
                    
                

for multiple styles: ngStyle directive

                    
                        <span [ngStyle]="{
                          'font-size': pers.isVip ? 'bold' : 'normal'
                          'color': pers.isVip ? 'red' : 'blue'}">
                        </span>
                    
                

Inline styles vs. classes

  • I NEVER use inline styles
  • In my opinion, all styles belong into the corresponding stylesheets
  • inline styles are not reusable
  • inline styles reduce maintainability - they are not effected by a redesign when css files are being replaced or color variables change
  • css classes are way more performant than inline styles

Pipes

for the transformation of values

  • they start with the UNIX pipe sign |, followed by the name of the operation e.g. | uppercase
  • many built in pipes, e.g. lowercase, uppercase, date, currency and many more
  • pipes can have params {{birthday | date:"MM/dd/yy"}}
  • Custom pipes: implement PipeTransform interface and use the @Pipe({name: 'name'}) annotation on the typescript class

Examples

                    
                        {{birthday | date:'MM/dd/yy'}}

                        
                        {{customer | json}}

                        
                        {{balance | currency:'CHF':false}}

                        
                        {{title | lowercase | uppercase}}

                        
                        {{invoice.nr | invoiceNr}}
                    
                

Custom Pipe

                    
                        import { Pipe, PipeTransform } from '@angular/core';

                        @Pipe({name: 'ellipsis'})
                        export class EllipsisPipe implements PipeTransform {
                            transform(word: string, count: number): string {
                                if(!count) {
                                    return word;
                                }
                                if(word.length <= count) {
                                    return word
                                }
                                return word.substring(0, count) + '...';
                            }
                        }
                    
                    
                        {{'Lorem ipsum dolor' | ellipsis:10}}
                        
                    
                

Task 7.3 - Pipes

  • create a custom pipe phonenumber
  • format the phonenumber 0564410808 (string)
  • it should be formatted as follows
  • Default: 056 441 08 08
  • with param 41: +41 56 441 08 08

Task 7.3 - Possible Solution

                    
                        import {Pipe, PipeTransform} from '@angular/core';
                        @Pipe({name: 'phonenumber'})
                        export class PhoneNumberPipe implements PipeTransform {
                          transform(phone: string, countryCode: string): string {
                            let pts = phone.match(/^(\d{3})(\d{3})(\d{2})(\d{2})$/);
                            if (!pts) return phone;
                            let format = `${pts[1]} ${pts[2]} ${pts[3]} ${pts[4]}`;

                            if (countryCode) {
                              format = `+${countryCode} ${format.substring(1)}`;
                            }

                            return format;
                          }
                        }
                    
                 

Safe Navigation Operator

to avoid NullPointers ;-)

  • used with ?.
  • the value is only rendered when all properties can be accessed
  • lightweight alternative to chained *ngIf or chained null checks with &&
                
                    

Name: {{comp?.ceo?.name}}

Name: {{comp && comp.ceo && comp.ceo.name}}

Any questions?