Angular2--高级特性(TODO)

1 基础

关于Angular的基础部分,几个核心部分和框架,在之前都写过了。Angular1--Hello-CSDN博客

Angular的几个核心部分和框架:

模板就是组件中的template,对应MVC的V。

组件类就是Component类,对应对应MVC的C。

服务就是service类,对应MVC的M。

这个理解不能说100%对,但是基本成立吧。有了这个概念,后面就好理解多了。

2 复杂一点的游戏

来自官网例程:Playground • Angular

main.ts

import {A11yModule} from '@angular/cdk/a11y';
import {CommonModule} from '@angular/common';
import {Component, ElementRef, ViewChild, computed, signal} from '@angular/core';
import {MatSlideToggleChange, MatSlideToggleModule} from '@angular/material/slide-toggle';
import {bootstrapApplication} from '@angular/platform-browser';const RESULT_QUOTES = [['Not quite right!','You missed the mark!','Have you seen an angle before?','Your measurements are all over the place!','Your precision needs work!',],['Not too shabby.', 'Getting sharper, keep it up!', 'Not perfect, but getting better!'],['Your angles are on point!','Your precision is unparalleled!','Your geometric skills are divine!',"Amazing! You're acute-y!",'Wow! So precise!',],
];const CHANGING_QUOTES = [["I'm such a-cute-y!", "I'm a tiny slice of pi!", "You're doing great!"],["I'm wide open!", 'Keep going!', 'Wow!', 'Wheee!!'],["I'm so obtuse!", 'The bigger the better!', "Life's too short for right angles!", 'Whoa!'],
];function getChangingQuote(rotateValue: number): string {let possibleQuotes = CHANGING_QUOTES[1];if (rotateValue < 110) {possibleQuotes = CHANGING_QUOTES[0];} else if (rotateValue >= 230) {possibleQuotes = CHANGING_QUOTES[2];}const randomQuoteIndex = Math.floor(Math.random() * possibleQuotes.length);return possibleQuotes[randomQuoteIndex];
}function getResultQuote(accuracy: number) {let possibleQuotes = RESULT_QUOTES[1];if (accuracy < 50) {possibleQuotes = RESULT_QUOTES[0];} else if (accuracy >= 85) {possibleQuotes = RESULT_QUOTES[2];}let randomQuoteIndex = Math.floor(Math.random() * possibleQuotes.length);return possibleQuotes[randomQuoteIndex];
}@Component({selector: 'app-root',imports: [CommonModule, MatSlideToggleModule, A11yModule],styleUrl: 'game.css',templateUrl: 'game.html',
})
export class Playground {protected readonly isGuessModalOpen = signal(false);protected readonly isAccessiblePanelOpen = signal(false);protected readonly rotateVal = signal(40);protected readonly goal = signal(85);protected readonly animatedAccuracy = signal(0);protected readonly gameStats = signal({level: 0,totalAccuracy: 0,});protected readonly resultQuote = signal('');private isDragging = false;private currentInteractions: {lastChangedAt: number; face: number; quote: string} = {lastChangedAt: 75,face: 0,quote: "Hi, I'm NG the Angle!",};@ViewChild('staticArrow') staticArrow!: ElementRef;protected readonly totalAccuracyPercentage = computed(() => {const {level, totalAccuracy} = this.gameStats();if (level === 0) {return 0;}return totalAccuracy / level;});protected readonly updatedInteractions = computed(() => {if (this.rotateVal() > 75 &&Math.abs(this.rotateVal() - this.currentInteractions.lastChangedAt) > 70 &&Math.random() > 0.5) {this.currentInteractions = {lastChangedAt: this.rotateVal(),face: Math.floor(Math.random() * 6),quote: getChangingQuote(this.rotateVal()),};}return this.currentInteractions;});constructor() {this.resetGame();}resetGame() {this.goal.set(Math.floor(Math.random() * 360));this.rotateVal.set(40);}getRotation() {return `rotate(${this.rotateVal()}deg)`;}getIndicatorStyle() {return 0.487 * this.rotateVal() - 179.5;}getIndicatorRotation() {return `rotate(${253 + this.rotateVal()}deg)`;}mouseDown() {this.isDragging = true;}stopDragging() {this.isDragging = false;}mouseMove(e: MouseEvent) {const vh30 = 0.3 * document.documentElement.clientHeight;if (!this.isDragging) return;let pointX = e.pageX - (this.staticArrow.nativeElement.offsetLeft + 2.5);let pointY = e.pageY - (this.staticArrow.nativeElement.offsetTop + vh30);let calculatedAngle = 0;if (pointX >= 0 && pointY < 0) {calculatedAngle = 90 - (Math.atan2(Math.abs(pointY), pointX) * 180) / Math.PI;} else if (pointX >= 0 && pointY >= 0) {calculatedAngle = 90 + (Math.atan2(pointY, pointX) * 180) / Math.PI;} else if (pointX < 0 && pointY >= 0) {calculatedAngle = 270 - (Math.atan2(pointY, Math.abs(pointX)) * 180) / Math.PI;} else {calculatedAngle = 270 + (Math.atan2(Math.abs(pointY), Math.abs(pointX)) * 180) / Math.PI;}this.rotateVal.set(calculatedAngle);}adjustAngle(degreeChange: number) {this.rotateVal.update((x) =>x + degreeChange < 0 ? 360 + (x + degreeChange) : (x + degreeChange) % 360,);}touchMove(e: Event) {let firstTouch = (e as TouchEvent).touches[0];if (firstTouch) {this.mouseMove({pageX: firstTouch.pageX, pageY: firstTouch.pageY} as MouseEvent);}}guess() {this.isGuessModalOpen.set(true);const calcAcc = Math.abs(100 - (Math.abs(this.goal() - this.rotateVal()) / 180) * 100);this.resultQuote.set(getResultQuote(calcAcc));this.animatedAccuracy.set(calcAcc > 20 ? calcAcc - 20 : 0);this.powerUpAccuracy(calcAcc);this.gameStats.update(({level, totalAccuracy}) => ({level: level + 1,totalAccuracy: totalAccuracy + calcAcc,}));}powerUpAccuracy(finalAcc: number) {if (this.animatedAccuracy() >= finalAcc) return;let difference = finalAcc - this.animatedAccuracy();if (difference > 20) {this.animatedAccuracy.update((x) => x + 10.52);setTimeout(() => this.powerUpAccuracy(finalAcc), 30);} else if (difference > 4) {this.animatedAccuracy.update((x) => x + 3.31);setTimeout(() => this.powerUpAccuracy(finalAcc), 40);} else if (difference > 0.5) {this.animatedAccuracy.update((x) => x + 0.49);setTimeout(() => this.powerUpAccuracy(finalAcc), 50);} else if (difference >= 0.1) {this.animatedAccuracy.update((x) => x + 0.1);setTimeout(() => this.powerUpAccuracy(finalAcc), 100);} else {this.animatedAccuracy.update((x) => x + 0.01);setTimeout(() => this.powerUpAccuracy(finalAcc), 100);}}close() {this.isGuessModalOpen.set(false);this.resetGame();}getText() {const roundedAcc = Math.floor(this.totalAccuracyPercentage() * 10) / 10;let emojiAccuracy = '';for (let i = 0; i < 5; i++) {emojiAccuracy += roundedAcc >= 20 * (i + 1) ? '🟩' : '⬜️';}return encodeURI(`📐 ${emojiAccuracy} \n My angles are ${roundedAcc}% accurate on level ${this.gameStats().level}. \n\nHow @Angular are you? \nhttps://angular.dev/playground`,);}toggleA11yControls(event: MatSlideToggleChange) {this.isAccessiblePanelOpen.set(event.checked);}
}bootstrapApplication(Playground);

game.html

<div class="wrapper"><div class="col"><h1>Goal: {{ goal() }}º</h1><div id="quote" [class.show]="rotateVal() >= 74">"{{ updatedInteractions().quote }}"</div><divid="angle"(mouseup)="stopDragging()"(mouseleave)="stopDragging()"(mousemove)="mouseMove($event)"(touchmove)="touchMove($event)"(touchend)="stopDragging()"(touchcanceled)="stopDragging()"><div class="arrow" id="static" #staticArrow><div class="center"></div>@if(rotateVal() >= 20) {<div class="svg" [style.transform]="getIndicatorRotation()"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 75 75"><defs><linearGradient id="gradient" x1="0%" y1="0%" x2="0%" y2="100%"><stop offset="0%" stop-color="var(--orange-red)" /><stop offset="50%" stop-color="var(--vivid-pink)" /><stop offset="100%" stop-color="var(--electric-violet)" /></linearGradient></defs><path[style.stroke-dashoffset]="getIndicatorStyle()"class="svg-arrow"stroke="url(#gradient)"d="m64.37,45.4c-3.41,11.62-14.15,20.1-26.87,20.1-15.46,0-28-12.54-28-28s12.54-28,28-28,28,12.54,28,28"/><polylineclass="svg-arrow"stroke="url(#gradient)"points="69.63 36.05 65.29 40.39 60.96 36.05"/></svg></div>}<div class="face"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 103.41 84.33" [class.show]="rotateVal() >= 74">@switch(updatedInteractions().face) {@case(0) {<g><path class="c" d="m65.65,55.83v11c0,7.73-6.27,14-14,14h0c-7.73,0-14-6.27-14-14v-11"/><line class="c" x1="51.52" y1="65.83" x2="51.65" y2="57.06"/><path class="c" d="m19.8,44.06c7.26,7.89,18.83,13,31.85,13s24.59-5.11,31.85-13"/><path class="b" d="m3,14.33c3.35-5.71,9.55-9.54,16.65-9.54,6.66,0,12.53,3.37,16,8.5"/><path class="b" d="m100.3,14.33c-3.35-5.71-9.55-9.54-16.65-9.54-6.66,0-12.53,3.37-16,8.5"/></g>}@case(1) {<g><path class="d" d="m22.11,48.83c-.08.65-.14,1.3-.14,1.97,0,11.94,13.37,21.62,29.87,21.62s29.87-9.68,29.87-21.62c0-.66-.06-1.32-.14-1.97H22.11Z"/><circle cx="19.26" cy="12.56" r="12.37"/><circle cx="84.25" cy="12.56" r="12.37"/><circle class="e" cx="14.86" cy="8.94" r="4.24"/><circle class="e" cx="80.29" cy="8.76" r="4.24"/></g>}@case(2) {<g><circle cx="19.2" cy="12.72" r="12.37"/><circle cx="84.19" cy="12.72" r="12.37"/><circle class="e" cx="14.8" cy="9.09" r="4.24"/><circle class="e" cx="80.22" cy="8.92" r="4.24"/><path class="c" d="m19.45,44.33c7.26,7.89,18.83,13,31.85,13s24.59-5.11,31.85-13"/></g>}@case(3) {<g><path class="b" d="m3.11,14.33c3.35-5.71,9.55-9.54,16.65-9.54,6.66,0,12.53,3.37,16,8.5"/><path class="b" d="m100.41,14.33c-3.35-5.71-9.55-9.54-16.65-9.54-6.66,0-12.53,3.37-16,8.5"/><path class="c" d="m19.91,44.06c7.26,7.89,18.83,13,31.85,13s24.59-5.11,31.85-13"/></g>}@case(4) {<g><circle cx="19.26" cy="12.5" r="12.37"/><circle class="e" cx="14.86" cy="8.88" r="4.24"/><path class="c" d="m19.51,44.11c7.26,7.89,18.83,13,31.85,13s24.59-5.11,31.85-13"/><path class="b" d="m100.08,14.33c-3.35-5.71-9.55-9.54-16.65-9.54-6.66,0-12.53,3.37-16,8.5"/></g>}@default {<g><circle cx="19.14" cy="12.44" r="12.37"/><circle cx="84.13" cy="12.44" r="12.37"/><circle class="e" cx="14.74" cy="8.82" r="4.24"/><circle class="e" cx="80.17" cy="8.64" r="4.24"/><circle class="b" cx="52.02" cy="53.33" r="14"/></g>}}</svg></div></div><divclass="grabbable"[style.transform]="getRotation()"(mousedown)="mouseDown()"(touchstart)="mouseDown()"><div class="arrow" id="moving"></div></div></div></div><div class="col"><div class="overall-stats"><h4>level: {{ gameStats().level + 1 }}</h4><h4>accuracy: {{ totalAccuracyPercentage() > 0 ? (totalAccuracyPercentage() | number : '1.1-1') + '%' : '??' }}</h4><button id="guess" class="gradient-button" (click)="guess()" [disabled]="isGuessModalOpen()"><span></span><span>guess</span></button></div></div>@if(isGuessModalOpen()) {<dialog id="result" cdkTrapFocus><button id="close" (click)="close()">X</button><div class="result-stats"><h2>goal: {{ goal() }}º</h2><h2>actual: {{ rotateVal() | number : '1.1-1' }}º</h2></div><h2 class="accuracy"><span>{{ animatedAccuracy() | number : '1.1-1' }}%</span>accurate</h2><svg class="personified" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 119.07 114.91"><g><polyline class="i" points="1.5 103.62 56.44 1.5 40.73 8.68"/><line class="i" x1="59.1" y1="18.56" x2="56.44" y2="1.5"/><polyline class="i" points="1.61 103.6 117.57 102.9 103.74 92.56"/><line class="i" x1="103.86" y1="113.41" x2="117.57" y2="102.9"/><path class="i" d="m12.97,84.22c6.4,4.04,10.47,11.28,10.2,19.25"/></g>@if(animatedAccuracy() > 95) {<g><path class="i" d="m52.68,72.99c-.04.35-.07.71-.07,1.07,0,6.5,7.28,11.77,16.26,11.77s16.26-5.27,16.26-11.77c0-.36-.03-.72-.07-1.07h-32.37Z"/><circle cx="51.13" cy="53.25" r="6.73"/><circle cx="86.5" cy="53.25" r="6.73"/><circle class="g" cx="48.73" cy="51.28" r="2.31"/><circle class="g" cx="84.35" cy="51.18" r="2.31"/></g>} @else if (animatedAccuracy() > 80) {<g><path class="h" d="m52.59,70.26c3.95,4.3,10.25,7.08,17.34,7.08s13.38-2.78,17.34-7.08"/><path class="h" d="m43.44,54.08c1.82-3.11,5.2-5.19,9.06-5.19,3.62,0,6.82,1.84,8.71,4.63"/><path class="h" d="m96.41,54.08c-1.82-3.11-5.2-5.19-9.06-5.19-3.62,0-6.82,1.84-8.71,4.63"/></g>} @else if (animatedAccuracy() > 60) {<g><path class="h" d="m77.38,76.81v5.99c0,4.21-3.41,7.62-7.62,7.62h0c-4.21,0-7.62-3.41-7.62-7.62v-5.99"/><line class="h" x1="69.69" y1="82.25" x2="69.76" y2="77.47"/><path class="h" d="m52.42,70.4c3.95,4.3,10.25,7.08,17.34,7.08s13.38-2.78,17.34-7.08"/><path class="h" d="m43.28,54.21c1.82-3.11,5.2-5.19,9.06-5.19,3.62,0,6.82,1.84,8.71,4.63"/><path class="h" d="m96.24,54.21c-1.82-3.11-5.2-5.19-9.06-5.19-3.62,0-6.82,1.84-8.71,4.63"/></g>} @else if (animatedAccuracy() > 40) {<g><circle cx="51.55" cy="53.15" r="6.73"/><circle cx="86.92" cy="53.15" r="6.73"/><circle class="g" cx="49.15" cy="51.17" r="2.31"/><circle class="g" cx="84.77" cy="51.08" r="2.31"/><line class="h" x1="61.21" y1="76.81" x2="78.15" y2="76.81"/></g>} @else {<g><circle cx="51.55" cy="53.12" r="6.73"/><circle cx="86.92" cy="53.12" r="6.73"/><circle class="g" cx="49.15" cy="51.14" r="2.31"/><circle class="g" cx="84.77" cy="51.05" r="2.31"/><path class="h" d="m84.01,81.41c-2.37-5.86-8.11-10-14.83-10s-12.45,4.14-14.83,10"/></g>}</svg><div>"{{ resultQuote() }}"</div><div class="result-buttons"><button (click)="close()" class="gradient-button"><span></span><span>again?</span></button><a target="_blank" class="gradient-button" [href]="'https://twitter.com/intent/tweet?text=' + getText()"><span></span><span>share<img src="assets/share.svg" aria-hidden="true"></span></a></div></dialog>}<div class="accessibility">@if(isAccessiblePanelOpen()) {<div><button [disabled]="isGuessModalOpen()" (click)="adjustAngle(-25)" aria-label="decrease angle a lot">--</button><button [disabled]="isGuessModalOpen()" (click)="adjustAngle(-5)" aria-label="decrease angle a little">-</button><button [disabled]="isGuessModalOpen()" (click)="adjustAngle(5)" aria-label="increase angle a little">+</button><button [disabled]="isGuessModalOpen()" (click)="adjustAngle(25)" aria-label="increase angle a lot">++</button></div>}<mat-slide-toggle [disabled]="isGuessModalOpen()" id="toggle" color="primary" (change)="toggleA11yControls($event)">Show Accessible Controls</mat-slide-toggle></div>
</div>

game.css

.wrapper {height: 100%;width: 100%;max-width: 1000px;margin: auto;display: flex;justify-content: flex-end;align-items: center;
}.col {width: 100%;display: flex;flex-direction: column;justify-content: space-between;align-items: center;
}.overall-stats {display: flex;flex-direction: column;align-items: center;padding: 1rem;font-size: 1.3rem;user-select: none;
}#goal {font-size: 2rem;
}#quote {margin-top: 10px;opacity: 0;transition: all 0.3s ease;
}#quote.show {opacity: 1;
}.gradient-button {text-decoration: none;color: black;margin: 8px;position: relative;cursor: pointer;font-size: 1rem;border: none;font-weight: 600;width: fit-content;height: fit-content;padding-block: 0;padding-inline: 0;
}.gradient-button span:nth-of-type(1) {position: absolute;border-radius: 0.25rem;height: 100%;width: 100%;left: 0;top: 0;background: linear-gradient(90deg, var(--orange-red) 0%, var(--vivid-pink) 50%, var(--electric-violet) 100%);
}.gradient-button span:nth-of-type(2) {position: relative;padding: 0.75rem 1rem;background: white;margin: 1px;border-radius: 0.2rem;transition: all .3s ease;opacity: 1;display: flex;align-items: center;
}.gradient-button:enabled:hover span:nth-of-type(2), 
.gradient-button:enabled:focus span:nth-of-type(2) {opacity: 0.9;
}a.gradient-button:hover span:nth-of-type(2), 
a.gradient-button:focus span:nth-of-type(2) {opacity: 0.9;
}.gradient-button:disabled {cursor: not-allowed;color: #969696;
}.gradient-button img {display: inline;height: 0.8rem;margin-left: 4px;
}#angle {height: 60vh;width: 60vh;display: flex;flex-direction: column;justify-content: flex-start;align-items: center;padding: 10px;margin: 10px;
}.grabbable {height: 30vh;width: 25px;position: absolute;cursor: pointer;transform-origin: bottom center;
}.arrow {height: 30vh;width: 4px;background-color: black;position: absolute;
}.arrow::before,
.arrow::after {content: '';position: absolute;top: -4px;left: -6px;height: 20px;transform: rotate(45deg);width: 4px;background-color: black;border-radius: 0px 0px 5px 5px;
}.arrow::after {left: 6px;transform: rotate(-45deg);
}#static > div.center {height: 4px;width: 4px;background-color: black;position: absolute;bottom: -2px;border-radius: 100%;
}#static > div.svg {height: 75px;width: 75px;position: absolute;bottom: -37.5px;left: -35.5px;transform-origin: center;transform: rotate(294deg);
}#static svg .svg-arrow {fill: none;stroke-linecap: round;stroke-miterlimit: 10;stroke-width: 3px;
}#static svg path {stroke-dasharray: 180;
}#moving {transform-origin: bottom center;left: calc(50% - 2px);
}.face svg {position: absolute;height: 13vh;width: 13vh;bottom: 2vh;left: 4vh;opacity: 0;transition: all 0.2s ease;
}.face svg.show {opacity: 1;
}.face svg .b {stroke-width: 6px;
}.face svg .b, .c {stroke-miterlimit: 10;
}.face svg .b, .c, .d {fill: none;stroke: #000;stroke-linecap: round;
}.face svg .e {fill: #fff;
}.face svg .c, .d {stroke-width: 7px;
}.face svg .d {stroke-linejoin: round;
}#result {background-color: white;border-radius: 8px;border: 1px solid #f6f6f6;box-shadow: 0 3px 14px 0 rgba(0,0,0,.2);position: fixed;top: 50%;left: 50%;transform: translate(-50%, -50%);width: 50%;display: flex;flex-direction: column;justify-content: space-around;align-items: center;padding: 2rem;z-index: 10;
}svg.personified  {height: 125px;
}.personified .g {fill: #fff;
}.personified .h {stroke-miterlimit: 10;stroke-width: 4px;
}.personified .h, .personified .i {fill: none;stroke: #000;stroke-linecap: round;
}.personified .i {stroke-linejoin: round;stroke-width: 3px;
} #close {border: none;background: none;position: absolute;top: 8px;right: 8px;font-size: 19px;cursor: pointer;
}.result-stats,
.result-buttons {display: flex;width: 100%;justify-content: center;
}.result-stats > * {margin: 4px 16px;
}.result-buttons {margin-top: 16px;
}.accuracy {font-weight: 700;margin: 1rem;
}.accuracy span {font-size: 4rem;margin-right: 6px;
}#copy {display: none;
}.accessibility {position: fixed;left: 10px;bottom: 10px;
}#toggle {margin-top: 8px;
}.accessibility button {width: 2rem;height: 2rem;font-size: 1rem;border: 2px solid var(--electric-violet);border-radius: 4px;cursor: pointer;margin: 0 4px;background-color: #fff;transition: all 0.3s ease;box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.3607843137);
}.accessibility button:focus:enabled, .accessibility button:hover:enabled {background-color: #e8dbf4;
}.accessibility button:disabled {cursor: not-allowed;background-color: #eee;
}@media screen and (max-width: 650px) {.wrapper {flex-direction: column-reverse;align-items: center;}.overall-stats {align-items: center;margin-bottom: 16px;}#result {box-sizing: border-box;min-width: auto;height: 100%;width: 100%;padding: 20px;top: 0;left: 0;border-radius: 0;transform: none;}
}

 效果如下:

从代码可以出,代码的主体还是一个组件。

@Component({selector: 'app-root',imports: [CommonModule, MatSlideToggleModule, A11yModule],styleUrl: 'game.css',templateUrl: 'game.html',
})
export class Playground {
...
}

这里实现没有提取出来,都是放在组件里面,所以挺大的。

这里的函数定义如下:

  protected readonly totalAccuracyPercentage = computed(() => {const {level, totalAccuracy} = this.gameStats();if (level === 0) {return 0;}return totalAccuracy / level;});

这里的computed(()是可以实时获取并响应this.gameStats()的变化。

还有一个就是

@ViewChild('staticArrow') staticArrow!: ElementRef;

这个的意思就是在 Angular 组件中获取模板中标记为 #staticArrow 的 DOM 元素或子组件的引用,后续可通过 staticArrow 属性安全地操作该元素或组件实例。

3 组件

组件的标准格式:

@Component({selector: 'app-user',template: `Username: {{ username }}`,imports: [App1],
})
export class User {username = 'youngTech';
}

一个组件基本上就对对应一个显示区域,包含了定义和控制。

组件控制流

事件处理

@Component({...template: `<button (click)="greet()">`
})
class App {greet() {alert("Hi. Greeting!");}
}

这里用双引号做的事件绑定。

在angular的模板中,双引号还有几个作用:

1. ​​静态属性值(纯字符串),表示就是普通字符串,不要做解析。

<input type="text" placeholder="请输入用户名">

2. ​​属性绑定(动态值)​,绑定表达式

<button disabled="{{isDisabled}}">按钮</button>

3. ​​指令输入(Input Binding),传递指令或组件的输入参数。

<app-child [title]="'固定标题'"></app-child>

4. ​​事件绑定(Event Binding)​

<button (click)="handleClick($event)">点击</button>

5. ​​特殊场景:模板引用变量​,声明模板局部变量。

<input #emailInput type="email">

4 模板

模板中可以增加控制,比如@if:

  template: `@if (isLoggedIn) {<span>Yes, the server is running</span>}`,
//in @Componenttemplate: `@for (user of users; track user.id) {<p>{{ user.name }}</p>}`,//in classusers = [{id: 0, name: 'Sarah'}, {id: 1, name: 'Amy'}, {id: 2, name: 'Rachel'}, {id: 3, name: 'Jessica'}, {id: 4, name: 'Poornima'}];
template: `<div [contentEditable]="isEditable"></div>`,

在模板中还可以做到延迟显示:

@defer {<comments />
} @placeholder {<p>Future comments</p>
} @loading (minimum 2s) {<p>Loading comments...</p>
}

效果如下: 

在模板中,可以将图片的关键字换成ngSrc:

Dynamic Image:<img [ngSrc]="logoUrl" [alt]="logoAlt" width="320" height="320" />

区别如下:

特性ngSrc (Angular 指令)src (原生 HTML)
动态绑定✅ 支持 Angular 表达式(如变量、函数调用)❌ 直接写死字符串,无法动态绑定
加载控制✅ 避免无效请求和竞争条件❌ 可能发送 404 或重复请求
性能优化✅ 可结合懒加载、占位图等策略❌ 无内置优化
框架集成✅ 与 Angular 变更检测无缝协作❌ 需手动处理动态更新

数据绑定

  template: `<p>Username: {{ username }}</p><p>{{ username }}'s favorite framework: {{ favoriteFramework }}</p><label for="framework">Favorite Framework1:<input id="framework" type="text" [(ngModel)]="favoriteFramework" /></label>`,

可以看到就是(ngModel)这个。除了ngModel,还有以下模板语法

类型语法 / 指令用途说明示例
绑定[property]绑定 HTML 属性[src]="imgUrl"
{{ expression }}插值表达式{{ user.name }}
bind-xxx等价于 [xxx]bind-title="msg"
事件(event)监听事件(click)="doSomething()"
on-xxx等价于 (xxx)on-click="save()"
双向绑定[(ngModel)]绑定输入与数据[(ngModel)]="user.name"
条件结构*ngIf条件显示*ngIf="isLoggedIn"
列表结构*ngFor遍历数据渲染*ngFor="let item of list"
切换结构*ngSwitch*ngSwitchCase类似 switch-case见下方示例
样式绑定[ngClass]动态 class 切换[ngClass]="{'active': isActive}"
[ngStyle]动态 style[ngStyle]="{color: colorVar}"
属性绑定[attr.xxx]绑定非标准属性[attr.aria-label]="label"
类绑定[class.className]控制某个类是否启用[class.active]="isActive"
样式绑定[style.xxx]控制某个样式值[style.backgroundColor]="color"
内容投影<ng-content>插槽内容传递用于组件中嵌套插入内容
模板引用变量#var在模板中获取 DOM 或组件引用<input #nameInput>
管道`expressionpipe`数据格式转换
自定义指令@Directive创建结构/属性指令如:[appHighlight]
表单控件[formControl], [formGroup]响应式表单语法<input [formControl]="nameControl">

5 路由

路由就是在angular内根据url切换到不同的组件。最小的路由大概是三个部分。

定义路由模块

app.routes.ts

import {Routes} from '@angular/router';
export const routes: Routes = [];

在主模块中导入

app.config.ts

import {ApplicationConfig} from '@angular/core';
import {provideRouter} from '@angular/router';
import {routes} from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes)],
};

 模板中使用路由

app.ts

import {Component} from '@angular/core';
import {RouterOutlet} from '@angular/router';@Component({selector: 'app-root',template: `<nav><a href="/">Home1</a>|<a href="/user">User</a></nav><router-outlet />`,imports: [RouterOutlet],
})
export class App {}

6 表单

内容都在@angular/forms。

响应式表单ReactiveFormsModule。这个后面还要再看看TODO

  template: `<form [formGroup]="profileForm" (ngSubmit)="handleSubmit()"><input type="text" formControlName="name" /><input type="email" formControlName="email" /><button type="submit">Submit</button></form><h2>Profile Form</h2><p>Name: {{ profileForm.value.name }}</p><p>Email: {{ profileForm.value.email }}</p>`,imports: [ReactiveFormsModule],

响应式表单的三大核心能力:

能力说明示例
数据驱动表单状态(值、校验)完全由代码控制,与模板解耦通过 formGroup.get('field').value 获取值
动态字段管理运行时增减字段(如购物车动态添加商品)使用 FormArray 动态操作字段
复杂校验支持跨字段校验、异步校验(如用户名实时查重)自定义 ValidatorFn 或异步校验

在真实 IoT 或企业后台里,设备管理、配置页面常常字段多且动态——选 Reactive Forms 几乎是“默认选项”。只有最轻量的表单才考虑模板驱动。

7 其它

7.1 注入

就是类似单例工厂类。。

@Injectable({providedIn: 'root',
})
export class CarService {
...
}
@Component({
})
export class App {carService = inject(CarService);
}

7.2 HTTP Client

7.3 WebSocket

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.tpcf.cn/news/911817.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

pikachu靶场通关笔记44 SSRF关卡02-file_get_content(三种方法渗透)

目录 一、SSRF 1、简介 2、原理 二、file_get_contents函数 1、功能 2、参数 3、返回值 4、file_get_contents与SSRF 三、渗透实战 1、基本探测 2、http协议 &#xff08;1&#xff09;访问upload-labs靶场 &#xff08;2&#xff09;访问yijuhua.txt 3、file协议…

Android 控件 - EditText 的 Hint(Hint 基本用法、Hint 进阶用法、单独设置 Hint 的大小)

一、EditText 的 Hint 1、基本介绍 在 Android 开发中&#xff0c;EditText 的 Hint 用于显示提示文本 提示文本当用户没有输入任何内容时显示&#xff0c;输入内容后自动消失 2、基本使用 &#xff08;1&#xff09;在 XML 布局文件中设置 在 XML 布局文件中设置 Hint …

PostgreSQL(知识片):索引关联度indexCorrelation

索引关联度的绝对值越大&#xff0c;说明这个索引数据越好。绝对值最大为1。 首先我们创建一个表&#xff1a;tbl_corr&#xff0c;包含列&#xff1a;col、col_asc、col_desc、col_rand、data&#xff0c;col_asc存储顺序数据&#xff0c;col_desc存储降序数据&#xff0c;col…

React纯函数和hooks原理

纯函数 JS 若满足其下条件 &#xff0c;被称为纯函数 1。确定的输入一定产生确定的输出 2 不产生副作用 另外redux中的reducer也要求是纯函数 Fiber 架构和hooks原理 useRef 在组件的整个声明周期内保持不变 用法&#xff1a;1绑定dom元素 或者 绑定一个类组件 因为函数式…

养老专业实训室虚拟仿真建设方案:助力人才培养与教育教学革新

随着我国老龄化程度加深&#xff0c;养老服务行业人才需求激增。养老专业实训室虚拟仿真建设方案凭借虚拟仿真技术&#xff0c;为养老专业教育教学带来革新&#xff0c;对人才培养意义重大。点击获取实训室建设方案 一、构建多元化虚拟场景&#xff0c;丰富实践教学内容 模拟居…

LangChain 提示词工程:语法结构详解与完整实战指南

LangChain 提示词工程&#xff1a;语法结构详解与完整实战指南 我将为您系统性地解析 LangChain 中各类提示模板的核心语法结构&#xff0c;通过清晰展示语法与对应代码示例&#xff0c;帮助您彻底掌握提示工程的实现方法。所有示例均围绕报幕词生成场景展开。 在这里插入图片…

20250625解决在Ubuntu20.04.6LTS下编译RK3588的Android14出现cfg80211.ko的overriding问题

Z:\14TB\versions\rk3588-android14-FriendlyElec\mkcombinedroot\res\vendor_modules.load 【拿掉/删除这一项目&#xff01;】 cfg80211.ko 20250625解决在Ubuntu20.04.6LTS下编译RK3588的Android14出现cfg80211.ko的overriding问题 2025/6/25 20:20 缘起&#xff1a;本文针对…

在WSL下搭建JavaWeb: JDBC学习环境

在WSL下搭建JavaWeb: JDBC学习环境 前言 ​ 笔者最近打算放松一下&#xff0c;接触一点经典的Java Web技术&#xff0c;自己在闲暇时间时玩一玩JavaWeb技术。这里开一个小系列整理一下最近学习的东西&#xff0c;以供参考和学习。 ​ 笔者的计划是使用VSCode写代码&#xff…

pscc系统如何部署,怎么更安全更便捷?

磐石云PSCC系统的安全高效部署需结合云原生架构与零信任安全模型&#xff0c;以下是经过大型项目验证的部署方案及最佳实践&#xff1a; 一、智能部署架构&#xff08;混合云模式&#xff09; 二、安全增强部署方案 1. 基础设施安全 网络隔离 采用 三层网络分区&#xff1a;互…

协程驱动的高性能异步 HTTP 服务器:基础实现与任务调度机制

一、引言&#xff1a;为什么用协程实现 HTTP 服务器&#xff1f; 传统 HTTP 服务器的编程模型大致分为&#xff1a; 多线程阻塞型&#xff1a;每连接一线程&#xff0c;简洁但扩展性差 事件驱动模型&#xff08;如 epoll 状态机&#xff09;&#xff1a;高性能但逻辑复杂 回…

《视频:点亮数字时代的光影魔方》

视频的前世今生&#xff1a;从诞生到爆火 视频的发展历程是一部充满创新与变革的历史&#xff0c;它见证了科技的飞速进步和人类对信息传播与娱乐方式不断追求的过程。从早期的雏形到如今的全面普及&#xff0c;视频经历了多个重要阶段&#xff0c;每一个阶段都伴随着关键节点与…

秋招Day14 - MySQL - 运维

百万级别以上的数据如何删除&#xff1f; 这么大量的DELETE操作可能会导致长时间锁表 可以进行批量删除&#xff0c;把要删除的数据分为多个小批次处理。 也可以采用创建新表&#xff0c;把不需要删除的数据迁移过来&#xff0c;然后废弃旧表。需要检查新表空间是否足够、分…

(C++)vector数组相关基础用法(C++教程)(STL库基础教程)

源代码&#xff1a; #include <iostream> #include <vector> #include <string> using namespace std;int main(){char a;int b;int c;vector <char> numbers;cout<<"请输入一组字符&#xff08;按下#结束&#xff09;:\n";while(1){…

面试的问题

主题&#xff1a;LLM相关、多模态相关、python编程、java编程 参见&#xff1a;小红书面试相关的帖子 LLM相关&#xff1a; 02.大语言模型架构/1.attention/1.attention.md qzl66/llm_interview_note - Gitee.com 02.大语言模型架构/Transformer架构细节/Transformer架构细…

【EDA软件】【应用功能子模块网表提供和加载编译方法】

1.背景 使用者做FPGA应用开发&#xff0c;将开发成果交给自己的客户&#xff0c;但是并不想提供RTL源码以及加密的源码&#xff0c;只想提供网表文件。 2.方法 2.1 指定应用功能子模块设置为Top层&#xff1b; 2.2 运行综合&#xff0c;在outputs文件夹下会生成该应用功能子…

Spring:多数据源配置多个事务管理器DEMO

Spring配置文件&#xff1a; 配置2个事务管理器&#xff1a;txManager和txManager2 <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/2001/XMLS…

通用 Excel 导出功能设计与实现:动态列选择与灵活配置

在企业级应用开发中&#xff0c;数据导出是高频需求。本文介绍一种支持动态列选择、灵活配置的通用 Excel 导出方案&#xff0c;通过前后端协同设计&#xff0c;实现导出字段、列顺序、数据格式的自定义&#xff0c;满足多样化业务场景。 一、功能架构设计 核心特性 动态字段…

安全壁垒 - K8s 的 RBAC、NetworkPolicy 与 SecurityContext 精要

安全壁垒 - K8s 的 RBAC、NetworkPolicy 与 SecurityContext 精要 如果说 Kubernetes 是我们构建云原生应用的“城市”,那么我们已经学会了如何规划道路(网络)、建设住宅(Pod 调度)、提供水电(存储)以及智能调节城市规模(自动伸缩)。现在,是时候为这座城市安装“城门…

服务器开放端口如何设置,本地内网开通应用端口让外网访问连接步骤

在互联网时代&#xff0c;服务器扮演着至关重要的角色&#xff0c;为了让本地搭建部署的服务器能够正常提供互联网服务&#xff0c;我们需要开放特定端口以供外部网络的客户端访问&#xff0c;本文将带领大家深入了解内网本地服务器如何设置端口开放给公网访问。 服务器开放端…

【深度学习新浪潮】什么是上下文工程?

什么是上下文工程? 上下文工程(Context Engineering) 是指通过设计、优化与大语言模型(LLM)交互时的输入内容(即“上下文”),引导模型生成更符合预期、更精准回答的系统性方法。这里的“上下文”通常包括 提示词(Prompt)、示例(Few-Shot Examples)、历史对话记录、…