Section 1: Getting to know Angular-CLI Section 2: Building login Components Section 3: Building a To-do App Section 4: Evolving! Modularizing your App section 5: Multi-user versions of your To-do List Section 6: Optimizing with third-party style libraries and modules Section 7: Bringing Components to life Rx– the Hidden sword in Angular Redux Your Angular App Section 8: Redux Your Angular App The big collection of Checking the Gaps (II)

Chapter 9: The Big Collection (2)

Play Angular2 animation again

The State and the Transition

It’s my habit to experiment before theorizing, so let’s take a look at the animation skills that Angular2 offers. Let’s start with the simplest example, a very simple template:

<div class="traffic-light"></div>Copy the code

Also very simple style (actually draw a small black block) :

.traffic-light{  
  width: 100px;  
  height: 100px;  
  background-color: black;
}Copy the code

So this is what it looks like, like this, not cool at all, but let’s do it little by little, the simpler it is the easier it is to understand the concept.

Not a cool little black block

Let’s add a metadata description for animations to our component:

import { 
  Component, 
  trigger,
  state,
  style
} from '@angular/core';

@Component({
  selector: 'app-playground'.templateUrl: './playground.component.html'.styleUrls: ['./playground.component.css'].animations: [
    trigger('signal', [
      state('go', style({
        'background-color': 'green'}))])]})export class PlaygroundComponent {

  constructor() {}}Copy the code

We have noticed that animations accept an array. In this array we use a function called trigger. Trigger takes the name of the trigger as its first argument and an array as its second argument. This array is made up of a function called state and a function called transition.

So what is state? State represents a state, and when this state is activated, the style attached to state attaches to the control to which trigger is applied. What is transition? Tranistion describes a sequence of animation steps that are performed at state transition time. In the current version, we have only state and no transition. Let’s see the effect first. Of course, before we can see the effect, we need to apply this trigger to a control. That’s the div in the template in our case.

<div
    [@signal] ="'go'"
    class="traffic-light">
</div>Copy the code

If you go back to your browser, you’ll see that the little black block has turned into a little green block, as shown

The little black one turned into a little green one

What does that mean? Our state style is attached to the div. Why is that? Because [@signal]=”‘go'” defines the status of trigger to be Go. But that’s not cool, is it? Yeah, for now, but again, don’t worry. Next, we can add a state stop. When stop is activated we need to set the background color of the small cube to red, so we need to change the animations to something like the following:

animations: [
    trigger('signal', [
      state('go', style({
        'background-color': 'green' 
      })),
      state('stop', style({
          'background-color':'red'}))])]Copy the code

We also need to add two buttons Go and Stop to the template. The template now looks like this

<div
  [@signal] ="signal"
  class="traffic-light">
</div>
<button (click) ="onGo()">Go</button>
<button (click) ="onStop()">Stop</button>Copy the code

Of course, as you can see, when we click a button we need to handle the corresponding click event. Here we want the box to turn green when we hit Go and red when we hit Stop. To do this, we need a member variable called signal that changes the corresponding state in the clickable handler.

export class PlaygroundComponent {

  signal: string;

  constructor() { }

  onGo(){
    this.signal = 'go';
  }
  onStop(){
    this.signal = 'stop'; }}Copy the code

Now open up your browser and experiment, and we’ll see that clicking Go turns green, and clicking Stop turns red. But animations are still not moving. Yes, that’s because we haven’t added transition yet. We just need to rewrite animations. To make the effect more obvious, let’s specify the height for the two states.

import { 
  Component, 
  OnDestroy,
  trigger,
  state,
  style,
  transition,
  animate
} from '@angular/core';

@Component({
  selector: 'app-playground'.templateUrl: './playground.component.html'.styleUrls: ['./playground.component.css'].animations: [
    trigger('signal', [
      state('void', style({
        'transform':'translateY(-100%)'
      })),
      state('go', style({
        'background-color': 'green'.'height':'100px'
      })),
      state('stop', style({
          'background-color':'red'.'height':'50px'
      })),
      transition('void => *', animate(5000))])]})export class PlaygroundComponent {

  signal: string;

  constructor() { }

  onGo(){
    this.signal = 'go';
  }
  onStop(){
    this.signal = 'stop'; }}Copy the code

Transition (‘* => *’, animate(500)) The previous ‘* => *’ is a state transition expression. * represents any state, so this expression tells us that any state change will trigger the animation. This tells Angular to animate for 500 milliseconds, which transitions from one state to another by default. Now open your browser to experience, click Go and Stop respectively, you will find our small square from a square to a rectangle, red to green process. After you’ve experienced it, look at the following sentence: Animation is actually composed of states, and the transition defines the steps of the state transition.

There is animation of shape and color changes

So let’s talk about a void state, why do we have a void state? We actually just experienced that, but we didn’t define the void state. We do not assign signal an initial value in the component, which means that trigger’s state is void from the start. We often need this void state to implement the entry or exit animation. The void state describes the state when there is no state value.

animations: [
    trigger('signal', [
      state('void', style({
        'transform':'translateY(-100%)'
      })),
      state('go', style({
        'background-color': 'green'.'height':'100px'
      })),
      state('stop', style({
          'background-color':'red'.'height':'50px'
      })),
      transition(= > '* *', animate(500))])]Copy the code

The above code defines a void state with a -100% shift on the Y-axis. This is the initial move of the cube from outside the scene into the scene. This is an entry animation that you can try out in your browser.

The approach animation with the void state

The magic of animate functions

In our experiment above, you will notice that transition has an animate function, which you probably think of as a function that specifies the time of an animation. There’s more to it than that. Let’s dig into it. First, let’s make a small change to the animations array above and change it to something like this:

animations: [
    trigger('signal', [
      state('void', style({
        'transform':'translateY(-100%)'
      })),
      state('go', style({
        'background-color': 'green'.'height':'100px'
      })),
      state('stop', style({
          'background-color':'red'.'height':'50px'
      })),
      transition(= > '* *', animate('.5s 1s'))])]Copy the code

We changed animate(500) to animate(‘.5s 1s’). So.5s means the animation transition time is 0.5 seconds (actually the same as the 500 ms set above), and 1s means the animation plays after 1 second delay. Now let’s open up the browser and see what happens.

Or even worse, the string expression can be changed to ‘.5s 1s ease-out’, which is an ease-out function that is a way to make the animation look more realistic. In the real world, objects move at a rhythm. They don’t start out fast, and they don’t always move at a constant speed. How do you understand that? When the ball falls, it first falls faster and faster, hits the ground and bounces back before finally hitting the floor again. And the easing function can make the transition effect of the animation according to the corresponding function abstracted from such a real scene to draw. Ease-out is just one of many ease-out functions, and we can certainly specify others. Another point to note is that while ease-out is just a friendly name for the real function, we can of course just specify the function behind it: Cubic – Bezier (0, 0, 0.58, 1). Instead of using this ease-out for our next small example, which may not be particularly effective, let’s use cubic bezier(0.175, 0.885, 0.32, 1.275) for an obvious one. Now let’s open up the browser, and you can see if you see the little box bouncing back

animations: [
    trigger('signal', [
      state('void', style({
        'transform':'translateY(-100%)'
      })),
      state('go', style({
        'background-color': 'green'.'height':'100px'
      })),
      state('stop', style({
          'background-color':'red'.'height':'50px'
      })),
      transition(= > '* *', animate(5s 1s cubic- Bezier (0.175, 0.85, 0.32, 1.275)))])]Copy the code

Add the easing function to the approach animation

More information about the easing function can be found at easings.net/zh-cn, where you can see the curves and effects of the various functions, as well as the various parameters of the Cubic – Bezier function

Easing.net lists the curves and effects of various easing functions

Note that the Angular2 Animation mechanism is based on the W3C Web Animation standard, which currently does not support all cubic- Bezier functions, but only some. So what if we want to implement some unsupported function? Then we have to bring in our keyframes.

Key frames

What are keyframes? The first thing you need to know is what is a frame? Baidu Encyclopedia gives a definition: frame – is the smallest unit of animation in a single image picture, equivalent to each frame on a film. Frames are represented as a grid or marker on the animation software timeline. Keyframes – the equivalent of the original drawing in 2d animation. Refers to the frame in which the key action of a character or object is in motion or change. Animations between key frames can be created by software, called transition frames or intermediate frames. Let’s do a little experiment. Let’s transform the entrance animation into a keyframe.

import { 
  Component, 
  OnDestroy,
  trigger,
  state,
  style,
  transition,
  animate,
  keyframes
} from '@angular/core';

@Component({
  selector: 'app-playground'.templateUrl: './playground.component.html'.styleUrls: ['./playground.component.css'].animations: [
    trigger('signal', [
      state('void', style({
        'transform':'translateY(-100%)'
      })),
      state('go', style({
        'background-color': 'green'.'height':'100px'
      })),
      state('stop', style({
          'background-color':'red'.'height':'50px'
      })),
      transition('void => *', animate(5000, keyframes([
        style({'transform': 'scale(0)'}),
        style({'transform': 'scale (0.1)'}),
        style({'transform': 'scale (0.5)'}),
        style({'transform': 'scale (0.9)'}),
        style({'transform': 'scale (0.95)'}),
        style({'transform': 'scale(1)'})
      ]))),
      transition(= > '* *', animate(5s 1s cubic- Bezier (0.175, 0.85, 0.32, 1.275)))])]})export class PlaygroundComponent {
  // clock = Observable.interval(1000).do(_=>console.log('observable created'));
  signal: string;

  constructor() { }

  onGo(){
    this.signal = 'go';
  }
  onStop(){
    this.signal = 'stop'; }}Copy the code

Save it and return to your browser. You should see an entry animation of a square growing from small to large.

Keyframe implementation of the entry animation

Now let’s analyze the code, the entry animation is 5 seconds long, we give 6 keyframes, namely 0s, 1s, 2s, 3s, 4s and 5s. For each keyframe, we give the style of scaling, and the scaling ratio gradually increases, and it is fast first and then slow, that is to say, we can simulate the effect of the easing function.

If we do not only zoom in, but also specify the position in the style, the animation will appear to grow as it moves. Try changing the entry animation to something like this.

transition('void => *', animate(5000, keyframes([
        style({'transform': 'scale(0)'.'padding': '0px'}),
        style({'transform': 'scale (0.1)'.'padding': '50px'}),
        style({'transform': 'scale (0.5)'.'padding': '100px'}),
        style({'transform': 'scale (0.9)'.'padding': '120px'}),
        style({'transform': 'scale (0.95)'.'padding': '135px'}),
        style({'transform': 'scale(1)'.'padding': '140px'})))),Copy the code

Plus the effect of displacement

The end result may still not be cool, but with keyframes we can make more complex animations if we combine CSS styles.

Convenient pipes

One thing we haven’t mentioned is pipes, which are a very handy feature in Angular 2, even though we didn’t use them in our example. This feature allows us to quickly output data in the desired format on the interface. For example, if we were to display a date on a page, we would create a simple template:

<p> Without Pipe: Today is {{ birthday }} </p>
<p> With Pipe: Today is {{ birthday | date:"MM/dd/yy" }} </p>Copy the code

Then create the corresponding component file:

import { Component, OnDestroy } from '@angular/core';

@Component({
  selector: 'app-playground'.templateUrl: './playground.component.html'.styleUrls: ['./playground.component.css']})export class PlaygroundComponent {
  birthday = new Date(a);constructor() {}}Copy the code

Date output without pipes and with pipes

If the above example is not obvious, let’s take the template one step further:

<p> Without Pipe: Today is {{ birthday }} </p>
<p> With Pipe: Today is {{ birthday | date:"MM/dd/yy" }} </p>
<p>The time is {{ birthday | date:'shortTime' }}</p>
<p>The time is {{ birthday | date:'medium' }}</p>Copy the code

The same data can be displayed in different ways

For example, we want to capitalize Dec for the date at the bottom of the image:

<p>The time is {{ birthday | date:'medium' | uppercase }}</p>Copy the code

Multiple pipes are used

Customize a Pipe

So what is the experience of writing a Pipe yourself? Creating a Pipe is very simple, so let’s try it out. SRC /app/playground/trim-space.pipe.ts

import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
  name: 'trimSpace'
})
export class TrimSpacePipe implements PipeTransform {
  transform(value: any, args: any[]): any {
    return value.replace(/ /g.' '); }}Copy the code

Declare Pipe: declarations: [PlaygroundComponent, TrimSpacePipe] in the Module file so that other controls can use the Pipe:

import { NgModule } from '@angular/core';
import { SharedModule } from '.. /shared/shared.module';
import { PlaygroundRoutingModule } from './playground-routing.module';
import { PlaygroundComponent }   from './playground.component';
import { PlaygroundService } from './playground.service';
import { TrimSpacePipe } from './trim-space.pipe';

@NgModule({
    imports: [
        SharedModule,
        PlaygroundRoutingModule
    ],
    providers:[
        PlaygroundService
    ],
    declarations: [PlaygroundComponent, TrimSpacePipe]
})
export class PlaygroundModule {}Copy the code

Then in the component template file can be used in the {{birthday | date: ‘medium’ | trimSpace}} :

<p> Without Pipe: Today is {{ birthday }} </p>
<p> With Pipe: Today is {{ birthday | date:"MM/dd/yy" }} </p>
<p>The time is {{ birthday | date:'shortTime' }}</p>
<p>The time is {{ birthday | date:'medium' | trimSpace}} with trim space pipe applied</p>
<p>The time is {{ birthday | date:'medium' | uppercase }}</p>Copy the code

Open the browser and see that the whitespace has been removed from the date where the trimSpace pipe is applied, as shown:

Define a Pipe that removes whitespace

The built-in Pipe

Decimal Pipe

We’ve just looked at DatePipe and UpperCase Pipes, now let’s take a look at other built-in pipes. The first is DecimalPipe for number formatting. DecimalPipe parameters are represented in the form of an expression {minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}. Among them:

  1. MinIntegerDigits is the smallest integer number; the default is 1.
  2. MinFractionDigits indicates the smallest decimal number; the default is 0.
  3. MaxFractionDigits indicates the largest decimal number; the default is 3.
<p>pi (no formatting): {{pi}}</p>
<p>pi (.5-5): {{pi | number:'.5-5'}}</p>
<p>PI (2.10) : {{PI | number: '2.10-10'}}</p>
<p>pi (.3-3): {{pi | number:'.3-3'}}</p>Copy the code

If we define PI: number = 3.1415927 in the component; The numbers above will be formatted to look like the image below

Decimal Pipe is used for formatting numbers

Currency Pipe

Currency [:currencyCode[:symbolDisplay[:digitInfo]]] :currency [:currencyCode[:symbolDisplay[:digitInfo]]

<p>A in USD: {{a | currency:'USD':true}}</p>
<p>B in CNY: {{B | currency: 'CNY: false:' 4.2 2}}</p>Copy the code

In the above code, USD or CNY surface currency code, true or false indicates whether to use the default symbol of the currency, and if there is another expression after that, it specifies the digit limit of the currency. The specific rules for this restriction are similar to those for Decimal Pipe above, as shown in the figure below.

Currecy Pipe is used to format currency

Percent Pipe

This Pipe is of course used to format percentages, and the rules for integer and Decimal places for percentages are the same as for Decimal pipes and Currency Pipes mentioned above. MyNum: number = 0.1415927; The following code should output something like the following:

<p>myNum : {{myNum | percent}}</p>
<p>MyNum (3.2 2) : {{myNum | percent: '3.2 2}}</p>Copy the code

Percent Pipe is used to format the Percent

Json Pipe

This pipe, which I personally feel is more suitable for debugging, can format any object for output in JSON format. If we define an object in the component:

object: Object = {
  foo: 'bar'.baz: 'qux'.nested: {
    xyz: 3.numbers: [1.2.3.4.5]}};Copy the code

The template below will output something like this, which is a great way to output readable object formats during debugging. Of course, if you’re using a modern IDE, this doesn’t make much sense:

<div>
  <p>Without JSON pipe:</p>
  <pre>{{object}}</pre>
  <p>With JSON pipe:</p>
  <pre>{{object | json}}</pre>
</div>Copy the code

Json Pipe is used to format an object as Json

Instructions – Directive

Another important concept that we haven’t mentioned yet is directives, but we’ve already used this one. For example, *ngFor, *ngIf, etc., these are called structural directives, while *ngModel, etc., are attribute directives. There are three types of directives in Angular 2: Structural and Attribute directives. What else is there? The Component itself is a directive with a template. Structural directives can change the layout of the DOM tree by adding and removing DOM elements. For example, we used *ngFor to add multiple Todo-items to the todo-list template. Attribute directives can change the appearance or behavior of a DOM element. For example, we change the default behavior of a DOM element by bidirectional binding with *ngModel. (When we change the value of a variable in a component, this change is directly reflected in the component, which is not defined by the component itself. We change it by *ngModel). The built-in structural directives in Angular 2 are shown in the following table:

The name of the usage instructions
ngIf <div*ngIf="canShow"> Part of the DOM tree is removed or recreated based on the value of the canShow expression.
ngFor <li *ngFor="let todo of todos"> Convert the Li element and its contents into a template and use it to initialize the view for each item in the list.
ngSwitch, ngSwitchCase, ngSwitchDefault <div [ngSwitch]="someCondition"></div> Based on the current value of someCondition, select one of the embedded templates and conditionally switch the contents of the div.

It’s easy to customize a command, so let’s do one. This directive is very simple: any control that clicks on it will say “I am clicked” on the console. Since we want to monitor its host’s click events, we introduce HostListener, using @hostListen (‘ click ‘) on the onClick method to indicate that the method is called when a click event is detected from the host.

import {
  Directive,
  HostListener
} from '@angular/core';

@Directive({
    selector: "[log-on-click]",})export class LogOnClickDirective {

    constructor() {}
    @HostListener('click')
    onClick() { console.log('I am clicked! '); }}Copy the code

Write a simple sentence in the template to see the effect

<button log-on-click>Click Me</button>

The custom directive makes clicking a button log a message

Code: github.com/wpcfan/awes…

Paper book published, than the content of the rich and substantial, welcome everyone to order! Jingdong links: item.m.jd.com/product/120…

Angular goes from zero to one