Angular(1)

一、补充

1. 引入第三方库(bootstrap为例)

a. MDN 引入

  • 在index.html中直接引入mdn
<link rel="stylesheet" href="..." integrity="..." crossorigin="anonymous">
<script src="..." integrity="..." crossorigin="anonymous"></script>

<script src="..." integrity="..." crossorigin="anonymous"></script>
<script src="..." integrity="..." crossorigin="anonymous"></script>
  • 先下载,然后在package.json中引入,重启后生效
"styles":[
	"src/assets/styles/bootstrap@4.min.css"
	"src/styles.scss"
]
"scripts":[
  	"..."
]
  • 先下载,在styles.scss中引入css,在main.ts中引入js
@import "./assets/styles/bootstrap@4.min.css";

b. npm 引入

npm安装

npm install bootstrap

styles.scss引入

@import "~bootstrap";

main.ts引入js (依赖jQuery和popper.js)

import 'bootstrap'

安装jQuery和popper.js

npm i jquery popper.js

2. npm换源

由于node下载第三方依赖包是从国外服务器下载,虽然没有被墙,但是下载的速度是非常的缓慢且有可能会出现异常。所以为了提高效率,我们还是把npm的镜像源替换成淘宝的镜像源。有几种方式供我们选择。

2.1 使用cnpm

使用阿里定制的cnpm命令行工具代替默认的npm,输入以下代码

npm install -g cnpm --registry=https://registry.npm.taobao.org

检测是否安装成功

cnpm -v
  • 安装成功之后,以后安装依赖包的方式和npm的是一样的,只是npm的命令换成是cnpm就可以了

  • 假如你已经习惯了使用npm的安装方式的,不想去下载阿里的cnpm命令工具的话,很简单,我们直接将node的仓库地址换成淘宝仓库地址即可

2.2 换源(单次使用)

npm install --registry=https://registry.npm.taobao.org

2.3 永久使用

在开发react-native的时候,不要使用cnpm!cnpm安装的模块路径比较奇怪,packager不能正常识别。所以,为了方便开发,我们最好是直接永久使用淘宝的镜像源

  • 直接命令行的设置
npm config set registry https://registry.npm.taobao.org
  • 手动修改设置
1.打开.npmrc文件(C:\Program Files\nodejs\node_modules\npm\npmrc,没有的话可以使用git命令行建一个( touch .npmrc),用cmd命令建会报错)
2.增加 registry =https://registry.npm.taobao.org  即可。
  • 检测是否修改成功
// 配置后可通过下面方式来验证是否成功
npm config get registry
// 或
npm info express

注:如果想还原npm仓库地址的话,只需要在把地址配置成npm镜像就可以了

npm config set registry https://registry.npmjs.org/

3. npm设置代理(proxy)

https://blog.csdn.net/yanzi1225627/article/details/80247758

3.1 设置代理

npm config set proxy=http://10.46.148.28:8080
npm config set registry=http://registry.npmjs.org

3.2 关于https

经过上面设置使用了http开头的源,因此不需要设https_proxy了,否则还要增加一句:

npm config set https-proxy http://10.46.148.28:8080

3.3 代理用户名和密码

npm config set proxy http://S2020011:Happy-765432@10.46.148.28:8080
npm confit set https-proxy http://S2020011:Happy-765432@10.46.148.28:8080

3.4 取消代理

npm config delete proxy
npm config delete https-proxy

二、 显示数据

1. 使用插值显示组件属性'{{...}}'

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

@Component({
  selector: 'app-root',
  template: `
    <h1>{{title}}</h1>
    <h2>My favorite hero is: {{myHero}}</h2>
    `
})
export class AppComponent {
  title = 'Tour of Heroes';
  myHero = 'Windstorm';
}

2. 初始化变量

export class AppComponent {
  title: string;
  myHero: string;

  constructor() {
    this.title = 'Tour of Heroes';
    this.myHero = 'Windstorm';
  }
}

3. 模板来源

ng的模版和样式可以是内联(上述示例),也可以是单独的文件

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    <h1>{{title}}</h1>
    <h2>My favorite hero is: {{myHero}}</h2>
    `,
  styles: [
      `h1 { color: red }`
  ]
})
export class AppComponent {
  title = 'Tour of Heroes';
  myHero = 'Windstorm';
}
import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'Tour of Heroes';
  myHero = 'Windstorm';
}

三、模板语法

1. 模板中的 HTML

大部分html标签都能在模版中使用,但有一些是毫无意义的:html body script base

2. 插值 {{...}} 和模板表达式

  • 插值{
<h3>Current customer: {{ currentCustomer }}</h3>
  • 模板表达式
<!-- "The sum of 1 + 1 is not 4" -->
<p>The sum of 1 + 1 is not {{1 + 1 + getVal()}}.</p>
<p>{{1+1}}</p>

a. 绑定变量

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    <h1>{{title}}</h1>
    <h2>My favorite hero is: {{myHero}}</h2>
    `,
  styles: [`h1 { color: red }`]
})
export class AppComponent {
  title = 'Tour of Heroes';
  myHero = 'Windstorm';
}

b. 绑定方法

模版中除了绑定变量,还能绑定方法

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
      <div>Value: {{ getVal() }}/div>
  `,
})
export class AppComponent {
  getVal(): number {
    return 33;
  }
}

c. 模板表达式

模版中还可以写些简单的逻辑,比如判断或运算

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
      <p>The sum of 1 + 1 is {{1 + 1}}.</p>
      <p>price的7折 {{price * 0.7}}.</p>
      <p>与方法结合 {{price * 0.7 + getVal()}}.</p>
  `,
})
export class AppComponent {
  price = 30
  getVal(): number {
    return 33;
  }
}

当使用模板表达式时,请遵循下列指南:

  • 非常简单

  • 执行迅速

  • 没有可见的副作用(即模版中的逻辑不能改变组件的变量)

3. 绑定语法

3.1. 绑定属性

  • 绑定图片
import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    <img src="../assets/images/madao.jpg" alt="madao" />
    <img [src]="madaoSrc" alt="madao" />
    <img bind-src="madaoSrc" alt="madao" />
    `,
  styles: []
})
export class AppComponent {
  madaoSrc = '../assets/images/madao.jpg';
}
  • 绑定普通属性
import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    <img [src]="user.pic" [alt]="user.name" />
    <table class="table-bordered">
      <tr>
        <th>name</th>
        <th>phone</th>
        <th>age</th>
      </tr>
      <tr>
        <td>张三</td>
        <td>13398490594</td>
        <td>33</td>
      </tr>
      <tr>
        <td [colSpan]="colSpan">李四</td>
        <td>15079049984</td>
        <td>22</td>
      </tr>
    </table>
    <button class="btn btn-primary" [disabled]="isDisabled">click</button>
    `,
  styles: []
})
export class AppComponent {
  madaoSrc = '../assets/images/madao.jpg';
  user = {
   name: 'madao',
   pic: this.madaoSrc
  };
  colSpan = 2;
  isDisabled = false;
}
  • 绑定自定义属性
import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    <span [attr.data-title]="customTitle">一行文字</span>
    <span [attr.title]="customTitle">test title</span>
    <span [title]="customTitle">test title</span>
    `,
  styles: []
})
export class AppComponent {
  madaoSrc = '../assets/images/madao.jpg';
  customTitle = 'bbb';
}
  • 使用插值表达式绑定属性

插值也可以用于属性,但常规做法还是用中括号[],建议整个项目保持风格统一

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
    <img src="{{ user.pic }}" alt="{{ user.name }}" />
    `,
  styles: []
})
export class AppComponent {
  madaoSrc = '../assets/images/madao.jpg';
  user = {
    name: 'madao',
    pic: this.madaoSrc
  };
}


<img [src]="' madao'" [alt]="user"/>等价于
<img src="madao" [alt]="'user' + user2.pic" />

3.2. 绑定样式class,style

绑定单个class
import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
      <button type="button" class="btn" [class.btn-primary]="theme === 'primary'">Primary</button>
      <button type="button" class="btn" [class.btn-secondary]="true">secondary</button>
      <button type="button" class="btn" [class.btn-success]="isSuccess">success</button>
      <button type="button" class="btn" [class.btn-danger]="'啦啦啦'">danger</button>
      <button type="button" class="btn" [class.btn-danger]="0">danger</button>
      <button type="button" class="btn" [class.btn-danger]="undefined">danger</button>
    `,
  styles: []
})
export class AppComponent {
    theme = 'primary';
    isSuccess = true;
}
绑定多个class
import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
      <button type="button" [class]="btnCls">btnCls</button>
      <button type="button" [class]="btnCls2">btnCls2</button>
      <button type="button" [class]="btnCls3">btnCls3</button>

      <!-- 也可以用内置指令ngClass -->
      <button type="button" [ngClass]="btnCls">btnCls</button>
      <button type="button" [ngClass]="btnCls2">btnCls2</button>
      <button type="button" [ngClass]="btnCls3">btnCls3</button>
    `,
  styles: []
})
export class AppComponent {
    btnCls = 'btn btn-primary';
    btnCls2 = ['btn', 'btn-success'];
    btnCls3 = {
      btn: true,
      'btn-info': true
    };
}
绑定单个style
import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
      <p [style.color]="'#f60'">一段文字</p>
      <p [style.height]="'50px'" [style.border]="'1px solid'">设置高度</p>
      <p [style.height.px]="50" [style.border]="'1px solid'">设置高度</p>
    `,
  styles: []
})
export class AppComponent {}
绑定多个style
import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
      <p [style]="style1">style1</p>
      <p [style]="style2">style2</p>
      <p [style]="style3">style3</p>
      <!-- 也可以用内置指令ngStyle, 但不推荐,以后可能会弃用 -->
      <!--  <p [ngStyle]="style1">style1</p>-->
      <!--  <p [ngStyle]="style2">style2</p>-->

      <!-- ngStyle只接收对象 -->
      <p [ngStyle]="style3">style3</p>
    `,
  styles: []
})
export class AppComponent {
  style1 = 'width: 200px;height: 50px;text-align: center;border: 1px solid;';
  style2 = ['width', '200px', 'height', '50px', 'text-align', 'center', 'border', '1px solid']; // 有问题
  style3 = {
    width: '200px',
    height: '50px',
    'text-align': 'center',
    border: '1px solid'
  };
}

样式优先级

  • 某个类或样式绑定越具体,它的优先级就越高
  • 绑定总是优先于静态属性

3.3. 绑定事件

基本用法
import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
      <button type="button" class="btn btn-primary" (click)="onClick()">Primary</button>
    `,
  styles: []
})
export class AppComponent {
    onClick() {
      console.log('onClick');
    } 
}
事件对象

$event就是原生的事件对象

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
      <button type="button" class="btn btn-primary" (click)="onClick($event)">Primary</button>
    `,
  styles: []
})
export class AppComponent {
    onClick(event: MouseEvent) {
      console.log('onClick', event.target);
    }
}
  • 阻止事件冒泡
<div (click)="clickParent($event)" class="wrap" style="width: 200px;background-color: #0c5460;">
  <div (click) = "clickChild($event)" class="child" style="width: 100px;height: 200px;background-color: #ff8f77;"></div>
  <!-- <div (click) = "clickChild($event.stopPropagation())" class="child" style="width: 100px;height: 200px;background-color: #ff8f77;"></div> -->
</div>
clickParent(event: MouseEvent){
  event.stopPropsgation();
  console.log("parent")
}

clickChild(event: MouseEvent){
  event.stopPropsgation();
  console.log("child")
}
  • 不是所有元素 event.target都有value
<input type="text" (keyup)="onKeyup($event)">
onKeyup(event:KeyboardEvent){
    console.log("onInput:",(event.target as HTMLInputElement).value);
  }

<!-- onInput(event:keyboardEvent) {
  console.log('onInput',event.target.value);
} -->

4. @Input和@Output

4.1 @Input输入属性

子组件中:

import { Component, Input } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `<p>
               Today's item: {{item}}
             </p>`
})
export class ItemDetailComponent  {
  @Input() item: string; // decorate the property with @Input()
}

父组件中:

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
     <app-item-detail [item]="currentItem"></app-item-detail>
  `,
})
export class AppComponent {
  currentItem = 'Television';
}

4.2 @Output输出属性

子组件中:

import { Component, Output, EventEmitter } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
	   <label>Add an item: <input #newItem></label>
       <button (click)="addNewItem(newItem.value)">Add to parent's list</button>
  `,
})
export class ItemOutputComponent {
  @Output() newItemEvent = new EventEmitter<string>();
  addNewItem(value: string) {
    this.newItemEvent.emit(value);
  }
}

父组件中:

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
     <app-item-output (newItemEvent)="addItem($event)"></app-item-output>
  `,
})
export class AppComponent {
    items = ['item1', 'item2', 'item3', 'item4'];
    addItem(newItem: string) {
      this.items.push(newItem);
    }
}

4.3 bootstart模态框组件

app.components.html

<!-- Button trigger modal -->
<button type="button" class="btn-primary" (click)="showModal=true">
  Launch demo modal
</button>

<app-dialog 
[show]="showModal" 
[title]="'标题'" 
(confirm)="onConfirm()"
(close)="onClose()"
(backdropClick)="onClose()">
</app-dialog>

app.components.ts

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'hero';
  showModal = false;
  onConfirm() {
    console.log('接收 onConfirm');
  }
  onClose() {
    this.showModal = false;
  }
}

dialog.components.html

  <!-- Modal -->
  <div class="modal fade show black-back" (click)="backdropClick.emit()" [class.d-block]="show"  id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
    <div class="modal-dialog modal-dialog-centered" (click)="$event.stopPropagation()">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title" id="exampleModalLabel">{{title}}</h5>
          <button type="button" (click)="onClose()" class="close" data-dismiss="modal" aria-label="Close">
            <span aria-hidden="true" >&times;</span>
          </button>
        </div>
        <div class="modal-body">
          ...
        </div>
        <div class="modal-footer">
          <button type="button"  
          	(click)="onClose()" 
		  class="btn btn-secondary" 
		  data-dismiss="modal">
              {{cancelLabel}}
           </button>
          <button type="button" class="btn btn-primary" (click)=onConfirm()>{{confirmLable}}</button>
        </div>
      </div>ts
    </div>
  </div>

dialog.componen.ts

import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';

@Component({
  selector: 'app-dialog',
  templateUrl: './dialog.component.html',
  styleUrls: ['./dialog.component.scss']
})
export class DialogComponent implements OnInit {
  @Input() show = false;
  @Input() title = '';
  @Input() confirmLable = "确定";
  @Input() cancelLabel = "取消";

  @Output() close = new EventEmitter<void>();
  @Output() backdropClick = new EventEmitter<void>();
  @Output() confirm = new EventEmitter<void>();

  constructor() { }

  ngOnInit(): void {
  }

  onClose(){ 
    // this.show = false;
    this.close.emit()
    console.log("发射成功1");
  }

  onConfirm(){ 
    // this.show = false;
    this.confirm.emit()
    console.log("发射成功2");
  }

  // backdropClick(){
  //   console.log("遮罩")
  // }
}

在元数据中声明输入和输出属性

固然可以在 @Directive 和 @Component 元数据中声明 inputs 和 outputs,但不推荐示例请看视频演示

提供别名

@Input()和@Output()可以接收一个参数,作为变量的别名,那么父组件中只能用别名绑定

子组件中:

import { Component, Input, EventEmitter, Output } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `<p>
               Today's item: {{item}}
             </p>`
})
export class ItemDetailComponent  {
  @Input('aliasItem') item: string; // decorate the property with @Input()
  @Output('newItem') newItemEvent = new EventEmitter<string>();
  addNewItem(value: string) {
     this.newItemEvent.emit(value);
   }
}

父组件中:

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
     <app-item-detail [aliasItem]="currentItem" (newItem)="addItem($event)"></app-item-detail>
  `,
})
export class AppComponent {
  currentItem = 'Television';
  items = ['item1', 'item2', 'item3', 'item4'];
  addItem(newItem: string) {
    this.items.push(newItem);
  }
}

输入属性一定要用中括号[]绑定?

如果绑定的值是静态的,就不需要[]

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
     <app-item-detail item="static item"></app-item-detail>
  `,
})
export class AppComponent {
  // currentItem = 'Television';
}

5.基本双向绑定

sizer.components.html

<div>
    <button class="btn btn-danger" (click)="dec()" title="smaller">-</button>
    <button class="btn btn-primary" (click)="inc()" title="bigger">+</button>
    <label [style.font-size.px]="size">FontSize: {{size}}px</label>
</div>

sizer.components.ts

import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';

@Component({
  selector: 'app-sizer',
  templateUrl: './sizer.component.html',
  styleUrls: ['./sizer.component.scss']
})
export class SizerComponent implements OnInit {
  @Input() size = 16;
  @Output() sizeChange = new EventEmitter<Number>()
  constructor() { }
  ngOnInit(): void {}

  dec() { 
    // this.size-- ;
    this.sizeChange.emit(this.size - 1);
  }
  inc() { 
    // this.size++; 
    this.sizeChange.emit(this.size + 1);
  }

}

app.components.html

<app-sizer [size] = "size" (change)="size = $event"></app-sizer> 
<app-sizer [(size)] = "size"></app-sizer>
<p><label [style.font-size.px]="size">FontSize: {{size}}px</label></p> 

app.components.ts

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  size = 16;

  title = 'ngone';
  con = "OK";
}

6. 表单双向绑定

基本使用

根据之前基本的双向绑定知识,[(ngModel)]语法可拆解为:

  1. 名为ngModel的输入属性
  2. 名为ngModelChange的输出属性
import {Component} from '@angular/core';

@Component({
  selector: 'example-app',
  template: `
    <input [(ngModel)]="name" #ctrl="ngModel" required>

    <p>Value: {{ name }}</p>
    <p>Valid: {{ ctrl.valid }}</p>

    <button (click)="setValue()">Set value</button>
  `,
})
export class SimpleNgModelComp {
  name: string = '';

  setValue() {
    this.name = 'Nancy';
  }
}

<input [(ngModel)]="name" />

上面这行代码相当于:

<input [value]="name" (input)="name = $event.target.value" />

在表单中使用

表单中使用[(ngModel)],需要做下面两件事的其中之一:

  • 给控件加上name属性
  • 将ngModelOptions.standalone设为true
<form>
    <input [(ngModel)]="value" name="name" />  //第一种,更推荐
    <input [(ngModel)]="value" [ngModelOptions]="{ standalone: true }" /> //第二种
    <input [(ngModel)]="value" [ngModelOptions]="{name: 'name' }" /> //第二种   
</form>

7. 模板引用变量

基本使用

使用井号(#)声明模板引用变量,可以获取DOM 元素、指令、组件、TemplateRef 或 Web Component。

之前用到的ng-template上的 # ,就是模板引用变量,并在组件中获取到了对应的TemplateRef

import {Component} from '@angular/core';
@Component({
  selector: 'app-tpl-var',
  template: `
    <input #phone placeholder="phone number" />
    <button (click)="callPhone(phone.value)">Call</button>
  `,
})
export class TplVarComponent {
  constructor() { }
  callPhone(value: string) {
    console.log('callPhone', value);
  }
}

ref

还有种写法就是ref, 下面两种写法是一样的

<input #fax placeholder="fax number" />
<br />
<input ref-fax placeholder="fax number" />

引用组件

import {Component} from '@angular/core';
@Component({
  selector: 'app-tpl-var',
  template: `
    <div class="demo-sec">
      <button class="btn btn-primary" (click)="sizer.inc()">app inc</button>
      <app-sizer [(size)]="size" #sizer></app-sizer>
      size: {{ size }}
    </div>
  `,
})
export class TplVarComponent {
  size = 16;
  constructor() { }
}

8. 模板运算符

管道( | )

管道可以理解为把模板上的值丢进一条或多条管道,经过管道的处理输出一条新的值

import {Component} from '@angular/core';
@Component({
  selector: 'app-tpl-var',
  template: `
    <p>Title through uppercase pipe: {{title | uppercase}}</p>
    <p>Title through uppercase pipe: {{title | uppercase | lowercase}}</p>
    <p>json: {{ obj | json }}</p>
  `,
})
export class TplOperatorsComponent {
  title = 'aBc';
  obj = {
      name: 'aaa',
      time: '1980-02-25T05:00:00.000Z',
      price: '$333'
    };
  constructor() {}
}

带参数的管道

如内置的date管道

import {Component} from '@angular/core';
@Component({
  selector: 'app-tpl-var',
  template: `
    <p>Manufacture date with date format pipe: {{now | date:'longDate'}}</p>
    <p>Manufacture date with date format pipe: {{now | date:'yyyy-MM-dd'}}</p>
  `,
})
export class TplOperatorsComponent {
  now = Date.now();
  constructor() {}
}

所有内置管道

可选链(?)

安全导航运算符是 es2020 中的新语法,又叫可选链

import {Component} from '@angular/core';
@Component({
  selector: 'app-tpl-var',
  template: `
    <!--<p *ngIf="hero">hero: {{ hero.name }}</p>-->
    <p>hero: {{ hero?.name }}</p>
  `,
})
export class TplOperatorsComponent {
   hero: Hero;
    constructor() {
      setTimeout(() => {
        this.hero = {
          id: 'hero_00',
          name: '龙龟'
        };
      }, 2000);
    }
}

空属性路径

非空断言(!)

在ts中,开启--strictNullChecks后,将一个可能是undefined或null的变量赋给一个有确切类型的变量时,会报错

但在特定情况下,我们很确定那个变量一定不是undefined或null,这个时候就可以用非空断言操作符
用了这个操作符的变量,可以理解为叫ts不要去操心了,我这个变量一定是有值的 非空断言生效的前提是开启 --strictNullChecks

使用非空断言的两个步骤:

  • tsconfig.json中设置"strictNullChecks": true,
  • tslint.json中设置 "no-non-null-assertion": false
import {Component} from '@angular/core';
@Component({
  selector: 'app-tpl-var',
  template: `
    <input #phone placeholder="phone number" />
    <button (click)="callPhone(phone.value)">Call</button>
  `,
})
export class TplOperatorsComponent {
  name: string | null = '';
  constructor() {
    // 报错,this.name可能为null, 不能赋给heroName
    const heroName: string = this.name;

    // 不报错,告诉ts,this.name一定不是null
    const heroName: string = this.name!;

    // 以上写法相当于
    if (this.name) {
      const heroName: string = this.name;
    }
  }
}

类型转换函数 $any()

有时候,绑定的表达式不能或很难指定类型。要消除这种报错,你可以使用 $any() 转换函数来把表达式转换成 any 类型
假设无法确定item的类型,也就不能确定item是否有bestByDate,这时就会报错,可以用$any()把item视为any类型,避免其报错

<p>The item's undeclared best by date is: {{$any(item).bestByDate}}</p>

也可以用这个函数绑定组件中不存在的变量

<p>The item's undeclared best by date is: {{$any(this).bestByDate}}</p>

四、属性型指令

ng generate directive highlight

组件是一种特殊的指令

import {Component} from '@angular/core';
@Component({
  selector: '[app-for]',
  //selector: 'app-for',
  template: `
    <!--<app-for></app-for>-->
    <div app-for>dasfsada</div>
  `,
})
export class AppComponent {
  constructor() {}
}

example

app.component.html

<p appHighlight>Highlight me!</p>
<p appHighlight [highlightColor]="orange">Highlight me!</p>
<p appHighlight highlightColor="color">Highlight me!</p>
<p appHighlight = "blue">Highlight me!</p>

highlight.directive.ts

import {Directive, ElementRef, EventEmitter, HostListener, Input, Output} from '@angular/core';

@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {
  @Input('appHighlight') highlightColor: string;
  @Output() colorChange = new EventEmitter<string>();
    
  constructor(private el: ElementRef) {
    console.log('appHighlight');
    this.el.nativeElement.style.backgroundColor = color;
  }
    //监听宿主p
  @HostListener('mouseenter') onMouseEnter() {
    this.highlight(this.highlightColor || 'yellow');
  }
 // @HostListener('mouseenter',['$event']) onMouseEnter(event) {
 // console.log('event',event)  
 //   this.highlight(this.highlightColor || 'yellow');
 // }
  @HostListener('mouseleave') onMouseLeave() {
    this.highlight('');
  }

  private highlight(color: string) {
    this.el.nativeElement.style.backgroundColor = color;
    this.colorChange.emit(color);
  }
}

五、结构型指令

1. *ngIf

基本使用

ngIf是内置的结构型指令,控制宿主元素的添加或删除,取决于绑定的值是否为真

import {Component} from '@angular/core';
@Component({
  selector: 'app-if',
  template: `
    <div *ngIf="condition">Content to render when condition is true.</div>
  `,
})
export class IfComp {
  condition = true;
}

扩展写法

*ngIf是个语法糖,上个例子完整的写法如下

import {Component} from '@angular/core';
@Component({
  selector: 'app-if',
  template: `
    <ng-template [ngIf]="condition">
      <div>Content to render when condition is true.</div>
    </ng-template>
  `,
})
export class IfComp {
  condition = true;
}
  • ng-template是一块内嵌模板,类型是TemplateRef

使用TemplateRef

上面示例中的else 或 then 后面跟的变量都是模板的引用而非组件中的变量,下面演示怎么用组件中的变量

import {Component, OnInit, ChangeDetectionStrategy, ViewChild, TemplateRef, AfterViewInit} from '@angular/core';
@Component({
  selector: 'app-if',
  template: `
    <button class="btn btn-primary" (click)="condition = !condition">toggle block</button>
    <p *ngIf="condition else elseBlocks">{{ condition }} === true 时显示</p>
    <ng-template #firstTpl>
      <p>{{ condition }} === false 时显示</p>
    </ng-template>
  `,
})
export class IfComponent implements OnInit, AfterViewInit {
  elseBlocks: TemplateRef<any> = null;
  @ViewChild('firstTpl', {static: true}) primaryBlock: TemplateRef<any> = null;
  condition = false;
  constructor() {

  }
  ngOnInit(): void {
    console.log('ngOnInit', this.primaryBlock);
    this.elseBlocks = this.primaryBlock;
  }
}

2. *ngSwitch

  • 基本使用
    ngSwitch是内置的结构型指令,控制显示那个模版,类似js中的switch
  • Angular 的 NgSwitch 实际上是一组相互合作的指令:NgSwitchNgSwitchCaseNgSwitchDefault
import {Component} from '@angular/core';
@Component({
  selector: 'app-switch',
  template: `
    <p>
      <input type="radio" name="fruit" value="apple" id="apple" [(ngModel)]="fruit" />
      <label for="apple">苹果</label>
    </p>
    <p>
      <input type="radio" name="fruit" value="pear" id="pear" [(ngModel)]="fruit" />
      <label for="pear">梨</label>
    </p>
    <p>
      <input type="radio" name="fruit" value="grape" id="grape" [(ngModel)]="fruit" />
      <label for="grape">葡萄</label>
    </p>
    <p>
      <input type="radio" name="fruit" value="other" id="other" [(ngModel)]="fruit" />
      <label for="other">other</label>
    </p>
    
    selected fruit: {{ fruit }}
    
    <div class="content" [ngSwitch]="fruit">
      <p *ngSwitchCase="'apple'">这是 苹果</p>
      <p *ngSwitchCase="'pear'">这是 梨</p>
      <p *ngSwitchCase="'grape'">这是 葡萄</p>
      <p *ngSwitchDefault>啥都不是</p>
    </div>
  `,
})
export class SwitchComponent {
  fruit = '';
}

3. *ngFor

基本使用

import {Component} from '@angular/core';
const Heros = [
  {
    id: 'hero_0',
    name: '盖伦'
  },
  {
    id: 'hero_1',
    name: '赵信'
  },
  {
    id: 'hero_2',
    name: '嘉文'
  },
  {
    id: 'hero_3',
    name: '易大师'
  },
  {
    id: 'hero_3',
    name: '泰达米尔'
  }
];
interface Hero {
  id: string;
  name: string;
}

@Component({
  selector: 'app-switch',
  template: `
    <ul>
      <li *ngFor="let item of heros" [style.color]="item.id === 'hero_2' ? 'orange' : '#333'">{{ item.id }}</li>
    </ul>
  `,
})
export class SwitchComponent {
  heros: Hero[] = Heros;
}

trackBy

trackBy接收一个函数,返回 NgFor 应该跟踪的值(比如id),这样刷新列表时,id相同的dom不会触发更新

import {Component} from '@angular/core';
@Component({
  selector: 'app-switch',
  template: `
    <p>
      add hero:
      <button class="btn btn-info" (click)="reset()">reset</button>
    </p>
    <ul>
      <li *ngFor="let item of heros; trackBy: trackByHero" [style.color]="item.id === 'hero_2' ? 'orange' : '#333'">{{ item.id }}</li>
    </ul>
  `,
})
export class SwitchComponent {
  heros: Hero[] = Heros;
  reset() {
    this.heros = [
      {
        id: 'hero_4',
        name: '盖伦4'
      },
      {
        id: 'hero_5',
        name: '赵信5'
      },
      {
        id: 'hero_2',
        name: '嘉文'
      },
      {
        id: 'hero_6',
        name: '易大师6'
      },
      {
        id: 'hero_7',
        name: '泰达米尔7'
      }
    ];
  }
  trackByHero(hero: Hero): string {
    return hero.id;
  }
}

局部变量

  • $implicit: T:迭代目标(绑定到ngForOf)中每个条目的值。
  • ngForOf: NgIterable:迭代表达式的值。当表达式不局限于访问某个属性时,这会非常有用,比如在使用 async 管道时(userStreams | async)。
  • index: number:可迭代对象中当前条目的索引。
  • count: number:可迭代对象的长度。
  • first: boolean:如果当前条目是可迭代对象中的第一个条目则为 true。
  • last: boolean:如果当前条目是可迭代对象中的最后一个条目则为 true。
  • even: boolean:如果当前条目在可迭代对象中的索引号为偶数则为 true。
  • odd: boolean:如果当前条目在可迭代对象中的索引号为奇数则为 true。
import {Component} from '@angular/core';
@Component({
  selector: 'app-switch',
  template: `
    <ul>
      <li
        *ngFor="let item of heros; index as i count as len; let ev = even; let od = odd; let f = first; let l = last trackBy: trackByHero"
      [class.even]="ev"
      [class.odd]="od">
        <p>first: {{ f }} -- last: {{ l }}</p>
        <p>name: {{ item.name }}</p>
        <p>length: {{ len }}</p>
        <p>index: {{ i }}</p>
        <hr />
      </li>
    </ul>
  `,
    styles: [`
    .even {
      color: #82fa54;
    }

    .odd {
      color: #698efa;
    }
  `]
})
export class SwitchComponent {
  heros: Hero[] = Heros;
  trackByHero(hero: Hero): string {
    return hero.id;
  }
}

4. 内置指令的展开写法

ngIf

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
   <button (click)="show = !show">toggle</button>
   <p *ngIf="show as aa">一段文字 {{ aa }}</p> 

   <ng-template [ngIf]="show" let-aa="ngIf">
     <p>一段文字 {{ aa }}</p>
   </ng-template>
  `,
})
export class AppComponent {
  show = true;
}

ngSwitch

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `

    <div class="content" [ngSwitch]="fruit">
      <p *ngSwitchCase="'apple'">这是苹果</p>
      <p *ngSwitchCase="'pear'">这是梨</p>
      <p *ngSwitchCase="'grape'">这是葡萄</p>
      <p *ngSwitchDefault>啥都不是</p>
    </div>

   <div class="content" [ngSwitch]="fruit">
       <ng-template ngSwitchCase="apple">
         <p>这是苹果</p>
       </ng-template>
       <ng-template ngSwitchCase="pear">
         <p>这是梨</p>
       </ng-template>
       <ng-template ngSwitchCase="grape">
         <p>这是葡萄</p>
       </ng-template>
       <ng-template ngSwitchDefault>
         <p>啥都不是</p>
       </ng-template>
     </div>
  `,
})
export class AppComponent {
  fruit = 'pear';
}

ngFor

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
   <ul class="list">
     <li
       *ngFor="let item of heros; index as i; count as len; let ev = even; 
               let od = odd; let f = first; let l = last; trackBy: trackByHero"
       [class.even]="ev"
       [class.odd]="od"
     >
       <p>index: {{ i }}</p>
       <p>count: {{ len }}</p>
       <p>name: {{ item.name }}</p>
       <p>first: {{ f }} -- last: {{ l }}</p>
       <hr>
     </li>
   </ul>
   
   <ul>
     <ng-template
       ngFor
       [ngForOf]="heros"
       [ngForTrackBy]="trackByHero"
       let-item
       let-i="index"
       let-od="odd"
       let-ev="even"
       let-len="count"
       let-f="first"
       let-l="last">
       <li [class.even]="ev" [class.odd]="od">
         <p>index: {{ i }}</p>
         <p>count: {{ len }}</p>
         <p>name: {{ item.name }}</p>
         <p>first: {{ f }} -- last: {{ l }}</p>
         <hr>
       </li>
     </ng-template>
   </ul>
  `,
})
export class AppComponent {
  show = true;
  heros = [
              {
                id: 'hero_4',
                name: '盖伦4'
              },
              {
                id: 'hero_5',
                name: '赵信5'
              },
              {
                id: 'hero_2',
                name: '嘉文'
              },
              {
                id: 'hero_6',
                name: '易大师6'
              },
              {
                id: 'hero_7',
                name: '泰达米尔7'
              }
        ];
}

5. ng-template

是一个 Angular 元素,用来渲染 HTML。 它永远不会直接显示出来。 事实上,在渲染视图之前,Angular 会把 及其内容替换为一个注释。

如果没有使用结构型指令,而仅仅把一些别的元素包装进 中,那些元素就是不可见的

<p>Hip!</p>
<ng-template>
	<!-- 不可见 -->
    <p>Hip!</p>
</ng-template>
<p>Hooray!</p>

6. ng-container

Angular 的 是一个分组元素,但它不会污染样式或元素布局,因为 Angular 压根不会把它放进 DOM 中。

<p>
  I turned the corner
  <ng-container *ngIf="hero">
    and saw {{hero.name}}. I waved
  </ng-container>
  and continued on my way.
</p>

是一个由 Angular 解析器负责识别处理的语法元素。 它不是一个指令、组件、类或接口,更像是 JavaScript 中 if 块中的花括号。

if (someCondition) {
  statement1;
  statement2;
  statement3;
}

7. 自定义unless指令

import {Directive, Input, OnChanges, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core';
export class UnlessContext<T = unknown> {
  $implicit: T = null;
  appUnless: T = null;
  attr: T = null;
}
@Directive({
  selector: '[appUnless]'
})
export class UnlessDirective implements OnChanges {
  @Input('appUnless') unless: boolean;
  private hasView = false;
  private context = new UnlessContext();
  constructor(private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef) {
    // console.log(this.templateRef);
    // console.log(this.viewContainer);
  }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes['unless']) {
    this.context.$implicit = this.context.appUnless = this.unless;
    this.context.attr = 'aaab';
      if (this.unless) {
        if (this.hasView) {
          this.viewContainer.clear();
          this.hasView = false;
        }
      } else {
        if (!this.hasView) {
          // 这里使用的构造提供的模版(this.templateRef)
          // 实战中可以通过一个input属性传入模版
          this.viewContainer.createEmbeddedView(this.templateRef, this.context);
          this.hasView = true;
        }
      }
    }
  }
}

调用:

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
     <section>
       <h3>unless</h3>
       <button class="btn btn-primary" (click)="showUnless = !showUnless">toggle unless {{ showUnless }}</button>
       <p *appUnless="showUnless">测试unless driective -- {{ showUnless }}</p>
       <p *appUnless="showUnless as un">测试unless driective alias un -- {{ un }}</p>
       <p *appUnless="showUnless; let un; let attr=attr;">测试unless driective alias let un -- {{ un }} attr: {{ attr }}</p>
     </section>
  `,
})
export class AppComponent {
  show = false;
}

六、管道

七、transfer组件

  • 样式为bootstrap
  • 组件展示

image-20201027113706819

app.component.html

<div class="demo-sec">
  <div class="panel">
    <button class="btn btn-primary" (click)="setList()">按钮</button>
    <app-transfer-panel [search]="true" [list]="list" 
                        (changed) = "onChanged($event)"></app-transfer-panel>
  </div>
</div>

app.component.ts

import { Component } from '@angular/core';
import { TransferItem } from './components/transfer-panel/types';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  list: TransferItem[] = [];
  constructor(){
    this.setList()
}
onChanged(selicteds:TransferItem[]){
  console.log('onChanged',selicteds);
}
setList() {
  this.list = [];
  const prefix = 'item' + Date.now().toString().slice(-3);
  for (let i = 0; i < 20; i++) {
    this.list.push({
      key: prefix + '_' + i,
      value: `${prefix}${i + 1}`,
      checked: i % 6 === 0
    });
  }
}
}

transfer-panel.component.html

<div class="transfer-panel border rounded">
    <div class="head pl-3">
      <p>
        <span *ngIf="selecteds.length">{{selecteds.length}} /</span> {{showList.length}}
      </p>
    </div>
    <div class="search-box p-2" *ngIf="search">
      <input (input)="onInput($event)" 
             type="text" class="form-control" placeholder="请输入关键字" />
    </div>
    
    <ul class="list mb-0 overflow-auto pl-0">
      <li *ngFor="let item of showList" 
        [class.active] = 'targetIndex(item.key) > -1'
        (click) = "itemClick(item)"
        class="list-item text-truncate list-group-item-action">
        {{item.value}}
      </li>
      
      <!-- <li
        *ngFor="let item of showList; trackBy: trackByItem"
        class="list-item text-truncate list-group-item-action">
        {{ item.value }}
      </li> -->
    </ul>
  </div>

transfer-panel.component.ts

import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
import { TransferItem } from './types';

@Component({
  selector: 'app-transfer-panel',
  templateUrl: './transfer-panel.component.html',
  styleUrls: ['./transfer-panel.component.scss']
})
export class TransferPanelComponent implements OnInit,OnChanges {
  @Input() list: TransferItem[]=[];
  @Input() search = false;
  @Output() changed = new EventEmitter<TransferItem[]>();
  showList: TransferItem[] = [];
  selecteds:TransferItem[] = [];
  constructor() { }
  ngOnChanges(changes: SimpleChanges): void {
    const { list } = changes;
    if(list){
      console.log('list:',list.currentValue)
      // this.selecteds = []
      this.showList = list.currentValue.slice();
      this.selecteds = this.list.filter(item=>item.checked)
    }
  }
  
  ngOnInit(): void {}

  onInput(event:Event){
    const {value} = (event.target as HTMLInputElement);
    console.log(value);
    this.showList = this.list.filter(item=>item.value.includes(value))
  }
  itemClick(target:TransferItem){
    console.log(target);
    // const index = this.selecteds.findIndex(item =>item.key === target.key);
    const index = this.targetIndex(target.key)
    if (index>-1){
      this.selecteds.splice(index,1);
    }else{
      this.selecteds.push(target);
    }
    this.changed.emit(this.selecteds);
  }
  targetIndex(key:string):number{
    return this.selecteds.findIndex(item =>item.key === key);
  }
}
posted @ 2020-11-25 17:36  Daeeman  阅读(212)  评论(0编辑  收藏  举报