Angular等待Http请求响应或者Observable完成

假设一个场景,我们需要从服务端请求API1得到一组关于学生成绩的数据,然后根据返回的数据进行判断如果返回成绩为60,则请求API2,并将成绩100,不为60时,成绩10最终返回经过API1和API2两次处理的结果。

问题重现

如果我们整个流程通过Obvervable来模拟,可以实现下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
reproduce(): Observable<Student[]> {
return this.getStudents().pipe(
mergeMap((result) => {
if (result) {
result.map((rr) => {
if (rr.score == 60) {
this.client
.get('https://api.coindesk.com/v1/bpi/currentprice.json')
.subscribe((res) => {
console.log(res);
rr.score = rr.score + 100;
});
} else {
rr.score = rr.score * 10;
}
return rr;
});
}
return of(result);
})
);
}
reproduceTest() {
this.getStudents()
.pipe(
mergeMap((result) => {
if (result && result.length > 0) {
result.forEach((s) => console.log(s.score));
}
return of([]);
})
)
.subscribe();
}
getStudents(): Observable<Student[]> {
return of(this.students());
}
students(): Student[] {
return [{ score: 50 }, { score: 60 }, { score: 70 }];
}

这里执行完成后,我们会发现,返回的结果为50,60,70,而我们正常期望的结果应该是5000,600,700,这个问题的关键在于of直接返回了结果result,导致没有等待map完成输出正确的结果。

通过创建Obvervable分布返回计算结果

关于上面的问题,一种解决方案就是创建Obvervable对象并分布返回计算后的结果,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
createObvervableSolution(): Observable<Student[]> {
return this.getStudents().pipe(
mergeMap((result) => {
return new Observable<Student[]>((subscriber) => {
if (result) {
result.map((rr) => {
if (rr.score == 60) {
this.client
.get('https://api.coindesk.com/v1/bpi/currentprice.json')
.subscribe((res) => {
console.log(res);
rr.score = rr.score * 100;
subscriber.next([rr]);
});
} else {
rr.score = rr.score * 10;
subscriber.next([rr]);
}
});
}
});
})
);
}

通过上面的方式,我们会得到500,700,6000, 顺序不一致主要是因为在60的时候,我们需要等待Http请求完成,会花费时间。

通过Async Await实现

关于将Observable转化为Promise,我们可以通过toPromise实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
async async_await_SolutionTest() {
var result = await this.async_await_Solution();
result.forEach((student) => {
console.log(student.score);
});
}
async async_await_Solution(): Promise<Student[]> {
var students = await this.getStudents().toPromise();
students.forEach(async (student) => {
if (student.score == 60) {
var res = await this.client.get(
'https://api.coindesk.com/v1/bpi/currentprice.json'
);
console.log(res);
student.score = student.score * 100;
} else {
student.score = student.score * 10;
}
});
return students;
}

在线测试

引用

发布

Angular单元测试Mock多次HttpClient请求结果

在Angular单元测试中,对于需要Mock的Service,我们可以通过spy来模拟返回结果。

今天遇到一个问题比较尴尬,花了好长时间。在Angular方法里,通过嵌套的HttpClient请求处理数据。但是在进行单元测试的时候,发现如果单纯的模拟post方法,发现,始终只返回最后一次设置的结果。

最终发现,可以通过and.returnValues来返回多个结果。

通过将 spy 与 and.returnValues 链接起来,对该函数的所有调用将按顺序返回特定值,直到它到达返回值列表的末尾,此时它将为所有后续调用返回 undefined。

官方示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
describe("A spy, when configured to fake a series of return values", function() {
var foo, bar;

beforeEach(function() {
foo = {
setBar: function(value) {
bar = value;
},
getBar: function() {
return bar;
}
};

spyOn(foo, "getBar").and.returnValues("fetched first", "fetched second");

foo.setBar(123);
});

it("tracks that the spy was called", function() {
foo.getBar(123);
expect(foo.getBar).toHaveBeenCalled();
});

it("should not affect other functions", function() {
expect(bar).toEqual(123);
});

it("when called multiple times returns the requested values in order", function() {
expect(foo.getBar()).toEqual("fetched first");
expect(foo.getBar()).toEqual("fetched second");
expect(foo.getBar()).toBeUndefined();
});
});

实践模拟HttpClient多次请求代码

  • Angular Service
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    getStudents(students: any): Observable<any> {
    return this.postRequest(`api/students`, students).pipe(
    mergeMap(result => {
    return new Observable(subscriber =>{
    if (result) {
    result.map(student => {
    if(student.family === 'ABC'){
    const attributes = students.find(x => x.Id == student.Id).attributes;
    this.postRequest(`api/students/GetBucketIdsByAttributes`, attributes).subscribe(bucketIds =>{
    student.finalBucketIds = bucketIds;
    subscriber.next([student]);
    })
    }
    else {
    subscriber.next([unit]);
    }
    });
    }
    else {
    subscriber.next([]);
    };
    });
    })
    );
    }
  • 在上面的代码中,我们需要模拟两次postRequest的输出结果
  • 测试代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    describe('StudentsService', () => {
    let httpMockMethods = jasmine.createSpyObj(['get', 'post', 'put', 'delete']);
    beforeEach(() => {
    TestBed.configureTestingModule({
    providers: [
    { provide: HttpClient, useValue: httpMockMethods }
    ],
    imports: [
    HttpClientTestingModule
    ]
    });
    httpMock = TestBed.get(HttpTestingController);
    });
    afterEach(() => {
    httpMock.verify();
    httpMockMethods = jasmine.createSpyObj(['get', 'post', 'put', 'delete']);
    });
    it('Should Run HttpClient Request Twice', async () => {
    httpMockMethods.post.and.returnValues(of([{ unitId: '123', family: Families.ymc2, bucketIds: JSON.stringify(valueBucketIds) }]), of(bucketId));
    projectTreeService.getBucketIds(request).subscribe({
    next: result => {
    expect(result[0].finalBucketIds).toEqual(bucketId);
    }
    });
    expect(httpMockMethods.post.calls.count()).toBe(2, 'two call');
    }
    })

引用

发布

Flutter支持多语言

通过VSCode插件来配置Flutter的多语言支持。

Flutter Intl

  • 安装VS Code插件 Flutter Intl
  • VS Code中执行命令Flutter Intl: Initialize
  • 设置多语言依赖
    1
    2
    3
    4
    dependencies:
    // Other dependencies...
    flutter_localizations:
    sdk: flutter
  • 在项目入口加入localizationsDelegatessupportedLocales
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import 'package:flutter_localizations/flutter_localizations.dart';
    import 'generated/l10n.dart';

    class MyApp extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    return new MaterialApp(
    localizationsDelegates: [
    S.delegate,
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
    ],
    supportedLocales: S.delegate.supportedLocales,
    title: 'Flutter Demo',
    home: new MyHomePage(title: 'Flutter Demo Home Page'),
    );
    }
    }
  • 添加其他语言, 执行命令Flutter Intl: Add locale
  • 修改lib/l10n目录下对应的键值
    1
    2
    3
    {
    "Welcome":"欢迎"
    }
  • 通过S.of(context).Welcome来引用
  • 通过S.load(Locale('cn')) 来修改当前语言环境

通过ICU消息格式实现条件翻译

例如实现根据当前语言环境返回特定的翻译语言

  • intl_en.arb 内容
    1
    2
    3
    {
    "local":"{local, select, en {English} cn {Chinese} other {Chinese}}"
    }
  • intl_cn.arb 内容
    1
    2
    3
    {
    "local":"{local, select, en {英文} cn {简体中文} other {简体中文}}"
    }
  • 使用
    1
    S.of(context).local(locale.languageCode)

引用

发布

Angular读写客户端本地文件

通常情况下,我们不会在客户端(浏览器内)直接处理文件,而是会把文件当作数据传递到服务器端处理。不过有时候,我们也会希望能通过Angular在客户端加载或者保存数据。

接下来,我们来看看如何通过Angular在客户端实现读写保存本地文件。

Angular客户端保存文件

  • 通过构建a标签并自动化点击来实现下载
1
2
3
4
5
6
7
8
9
10
11
12
saveFileWithLink() {
var data = {
test: 'Hello World',
};
var json = JSON.stringify(data);
var blob = new Blob([json], { type: 'application/json' });
var url = window.URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = 'test.json';
a.click();
}
  • 通过file-saver
    • 安装file-saver
      1
      npm install file-saver --save
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      saveFileWithFileSaver() {
      var data = {
      test: 'Hello World',
      };
      var json = JSON.stringify(data);
      var file = new File([json], 'test.json', {
      type: 'application/json;charset=utf-8',
      });
      saveAs(file);
      }

      Angular本地文件上传

  • Angular预定义事件绑定上传
    • app.component.html
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      <h3>
      <button mat-flat-button (click)="file.click()" color="secondary">
      Angular预定义事件绑定上传
      </button>
      <input
      type="file"
      id="file"
      (change)="upload($event)"
      style="display:none"
      #file
      />
      </h3>
    • app.component.ts
      1
      2
      3
      upload(event): void {
      console.log(event.target.files[0].name);
      }
  • Angular动态绑定change事件上传
    • app.component.html
      1
      2
      3
      4
      5
      6
      <h3>
      <button mat-flat-button (click)="uploadDynamic()" color="secondary">
      Angular动态绑定change事件
      </button>
      <input type="file" id="fileUpload" style="display:none" #fileUpload />
      </h3>
    • app.component.ts
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      @ViewChild("fileUpload", {static: false}) fileUpload: ElementRef;

      uploadDynamic() {
      const fileUpload = this.fileUpload.nativeElement;
      fileUpload.onchange = () => {
      const file = fileUpload.files[0];
      console.log(file.name);
      };
      fileUpload.click();
      }

      Angular客户端读取文件内容

      1
      2
      3
      4
      5
      6
      7
      readFile(file: File) {
      var reader = new FileReader();
      reader.onload = () => {
      console.log(reader.result);
      };
      reader.readAsText(file);
      }
      如果想要实现将读取文件内容作为函数,可以通过
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      readFileContent(file: File): Promise<string> {
      return new Promise<string>((resolve, reject) => {
      if (!file) {
      resolve('');
      }
      const reader = new FileReader();
      reader.onload = (e) => {
      const text = reader.result.toString();
      resolve(text);
      };
      reader.readAsText(file);
      });
      }
      调用方法:
      1
      const fileContent = await readFileContent(file);

      在线测试

  • Angular读写客户端本地文件

引用

发布

AspNet.Core 教程

Asp.net Core 教程

目录

Startup

Startup配置服务和App请求管道

  • ConfigureServices配置程序的services
  • Configure配置程序请求处理的管道
    一个最简单项目的Startup
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public class Startup
    {
    // This method gets called by the runtime. Use this method to add services to the container.
    // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
    public void ConfigureServices(IServiceCollection services)
    {
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
    if (env.IsDevelopment())
    {
    app.UseDeveloperExceptionPage();
    }

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
    endpoints.MapGet("/", async context =>
    {
    await context.Response.WriteAsync("Hello World!");
    });
    });
    }
    }

    Startup类在创建程序的host的时候指定,比如
    1
    2
    3
    4
    5
    6
    public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
    .ConfigureWebHostDefaults(webBuilder =>
    {
    webBuilder.UseStartup<Startup>();
    });
    通过以上的方式,我们可以创建多个Startup,并依据环境变量进行动态调用

    依赖注入

    Service的生命周期

  • Transient
    Transient服务在它们每次被服务容器调用的时候创建,通常适用于轻量级和无状态的服务,通过AddTransient注册
  • Scoped
    相对于web应用程序,指每一次请求的生命周期,通过AddScoped注册。针对于Entity Framework CoreAddDbContextScoped生命周期
  • Singleton
    单例模式,从程序启动到关闭,访问同一个实例

    推荐方式

  • 避免通过GetService来获取service实例,应该通过依赖注入实现
  • 避免在ConfigureServices里调用BuildServiceProvider,调用BuildServiceProvider会创建两个容器,导致撕裂的单例,并被多个容易引用。应该通过依赖注入的方式在ConfigureServices注册的时候实现

错误处理

Excel合并单元格并保留所有单元格内容

一般情况下,在Excel中合并单元格的时候,Excel只会保留合并区间第一个单元格的内容,并删除其他单元格的内容进行合并。

如果想要实现在合并单元格的时候能自动进行单元格内容的合并,可以通过VBA实现。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
'@知乎 米可爱分享
'https://www.zhihu.com/people/meekou
Sub Merge(rng As Range)
Dim current As Range
Dim result As String
result = ""
Dim target As Range
Application.DisplayAlerts = False
For Each current In rng
If result = "" Then
Set target = current
result = result & current.Text
Else
result = result & Chr(10) & current
End If
Next
rng.Merge
target.Value = result
Application.DisplayAlerts = True
End Sub

Sub MergeTest()
Merge Range("A1:A2")
End Sub

发布

计算字符串公式和表达式

有时候我们的公式或者表达式是动态生成了,为了计算动态生成的字符串型的公式和表达式,我们可以通过以下的方式来实现

DataTable计算

我们可以通过DataTableCompute或者DataColumn.Expression

  • 示例代码一
    1
    2
    3
    4
    5
    string expression = "('FAMILY'='AA' AND SUBSTRING('Configure.COILS',1,2)<>'Co') AND ('Configure.COILS'='T')";
    expression = expression.Replace("FAMILY", "YK");
    expression = expression.Replace("Configure.COILS", "T");
    DataTable dt = new DataTable();
    var result = dt.Compute(expression, "");
  • 示例代码二
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public static bool EvaluateLogicalExpression(string logicalExpression)
    {
    System.Data.DataTable table = new System.Data.DataTable();
    table.Columns.Add("", typeof(bool));
    table.Columns[0].Expression = logicalExpression;

    System.Data.DataRow r = table.NewRow();
    table.Rows.Add(r);
    bool result = (Boolean)r[0];
    return result;
    }

Flee

Flee支持计算布尔值和表达式

示例代码

1
2
3
4
5
6
7
8
ExpressionContext context = new ExpressionContext();
VariableCollection variables = context.Variables;
variables.Add("a", 100);
variables.Add("b", 1);
variables.Add("c", 24);

IGenericExpression<bool> e = context.CompileGeneric<bool>("(a = 100 OR b > 0) AND c <> 2");
bool result222 = e.Evaluate();

其他解决方案

发布

Angular与Ngrx

Store是受 Redux 启发的用于 Angular 应用程序的 RxJS驱动的全局状态管理。Store是一个受控状态容器,旨在帮助在Angular之上编写高性能、一致的应用程序。

常规条件下构建Angular项目

  • 运行命令ng new angular-ngrx
  • 进入到新创建的项目文件夹cd angular-ngrx
  • 通过VSCode打开项目code .
  • 执行npm start检查项目初始化是否正确

Angular常规情况下共享数据

  • 对于Angular Component内或者Componenet之间,我们常规情况下通过Service来实现依赖注入并共享数据

  • 添加 ProductComponent并实现新增、删除功能

  • 通过ng g c product来创建新的组件

  • src/app目录下新建目录models,actions,reducers,states,

  • models目录下新建product.model.ts

    1
    2
    3
    4
    export interface Product{
    name:string;
    price:number;
    }
  • 修改product.component.ts实现数据加载,新增,删除

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    import { Component, OnInit } from '@angular/core';
    import { Product } from '../models/product.model';

    @Component({
    selector: 'app-product',
    templateUrl: './product.component.html',
    styleUrls: ['./product.component.css']
    })
    export class ProductComponent implements OnInit {
    products:Product[]
    newProduct:Product = {name:'',price:0}
    constructor() { }

    ngOnInit() {
    this.products = [
    {name:'Product A', price:200},
    {name:'Product B', price:300},
    {name:'Product C', price:500},
    ]
    }
    addProduct(product:Product){
    this.products.push(Object.assign({},product));
    }
    removeProduct(index:number){
    this.products.splice(index, 1);
    }
    }

  • 修改app.module.ts添加FormsModule

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { AppRoutingModule } from './app-routing.module';
    import { AppComponent } from './app.component';
    import { ProductComponent } from './product/product.component';
    import { FormsModule } from '@angular/forms';

    @NgModule({
    declarations: [
    AppComponent,
    ProductComponent
    ],
    imports: [
    FormsModule,
    BrowserModule,
    AppRoutingModule
    ],
    providers: [],
    bootstrap: [AppComponent]
    })
    export class AppModule { }

  • 修改product.component.html

    1
    2
    3
    4
    5
    6
    7
    8
    <input [(ngModel)]="newProduct.name"/>
    <input [(ngModel)]="newProduct.price"/>
    <button (click)="addProduct(newProduct)">新增</button>
    <ul>
    <li *ngFor="let product of products;let i = index">
    {{ product.name }} {{product.price}} <button (click)="removeProduct(i)">删除</button>
    </li>
    </ul>
  • 测试界面,可以实现简单的新增,删除,这里的新增删除都是通过products变量来管理的,如果我们想跨组件新增,删除,获取products就没有办法实现

    通过Ngrx来管理Angular项目的数据

  • Ngrx通过Store来管理并在组件间共享数据的状态

  • 我们通过StoreActionReduer来改写上面的常规共享数据

  • 执行命令npm install @ngrx/store安装ngrx

  • actions文件夹下新建product.action.ts

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import { Action } from "@ngrx/store";
    import { Product } from "../models/product.model";

    export const enum ProductActionType {
    ProductAdd = '[Product Component] Product Add',
    ProductRemove = '[Product Component] Product Remove'
    }

    export class ProductAddAction implements Action{
    type = ProductActionType.ProductAdd;
    constructor(payload:Product){}
    }

    export class ProductRemoveAction implements Action{
    type = ProductActionType.ProductRemove;
    constructor(payload:number){}
    }

    export type ProductActions =
    ProductAddAction |
    ProductRemoveAction;
  • reducers目录下创建product.reducer.ts

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import { ProductActions, ProductActionType } from "../actions/product.action";
    import { Product } from "../models/product.model";

    export const initialProductState:Product[] =[
    {name:'Product A', price:200},
    {name:'Product B', price:300},
    {name:'Product C', price:500}
    ]
    export function ProductReducer(state:Product[] = initialProductState, action:ProductActions){
    switch(action.type){
    case ProductActionType.ProductAdd:
    return [...state, Object.assign({},action.payload) ];
    case ProductActionType.ProductRemove:
    state.splice(action.payload,1);
    return state;
    default:
    return state;
    }
    }
  • 修改app.module.ts注册StoreModule

    1
    2
    3
    4
    5
    6
    imports: [
    FormsModule,
    BrowserModule,
    AppRoutingModule,
    StoreModule.forRoot({products:ProductReducer})
    ],
  • 更新product.component.ts以启用Store

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    import { Component, OnInit } from '@angular/core';
    import { Store } from '@ngrx/store';
    import { ProductAddAction, ProductRemoveAction } from '../actions/product.action';
    import { Product } from '../models/product.model';

    @Component({
    selector: 'app-product',
    templateUrl: './product.component.html',
    styleUrls: ['./product.component.css']
    })
    export class ProductComponent implements OnInit {
    products: Product[]
    newProduct:Product = {name:'',price:0}
    constructor(private productStore:Store<{products:Product[]}>) { }

    ngOnInit() {
    // this.products = [
    // {name:'Product A', price:200},
    // {name:'Product B', price:300},
    // {name:'Product C', price:500},
    // ]
    this.productStore.select('products').pipe().subscribe((data:Product[]) => this.products = data);
    }
    addProduct(product:Product){
    //this.products.push(Object.assign({},product));
    this.productStore.dispatch(new ProductAddAction(product));
    }
    removeProduct(index:number){
    //this.products.splice(index, 1);
    this.productStore.dispatch(new ProductRemoveAction(index));
    }
    }

    引入State来管理对象的多个属性

  • states目录下创建product.state.ts

    1
    2
    3
    4
    5
    import { Product } from "src/app/models/product.model";

    export interface ProductState{
    products:Product[]
    }
  • 修改product.reducer.ts创建createFeatureSelectorcreateSelector

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    import { createFeatureSelector, createSelector } from "@ngrx/store";
    import { ProductState } from "src/state/product.state";
    import { ProductActions, ProductActionType } from "../actions/product.action";
    import { Product } from "../models/product.model";

    export const productFeatureState = createFeatureSelector<ProductState>('productState');
    export const productState = createSelector(
    productFeatureState,
    state => state.products
    )

    export const initialProductState:Product[] =[
    {name:'Product A', price:200},
    {name:'Product B', price:300},
    {name:'Product C', price:500}
    ]
    export function ProductReducer(state:Product[] = initialProductState, action:ProductActions){
    switch(action.type){
    case ProductActionType.ProductAdd:
    return [...state, Object.assign({},action.payload) ];
    case ProductActionType.ProductRemove:
    state.splice(action.payload,1);
    return state;
    default:
    return state;
    }
    }
  • 创建product-action-reducer-map.ts

    1
    2
    3
    4
    5
    6
    7
    import { ActionReducerMap } from "@ngrx/store";
    import { ProductReducer } from "src/app/reducers/product.reducer";
    import { ProductState } from "../state/product.state";

    export const ProductActionReduerMap:ActionReducerMap<ProductState> = {
    products: ProductReducer
    }
  • 修改app.module.ts注入productFeatureState

    1
    2
    StoreModule.forFeature('productState', ProductActionReduerMap),
    StoreModule.forRoot({})
  • 修改product.component.ts

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    export class ProductComponent implements OnInit {
    products: Product[]
    newProduct:Product = {name:'',price:0}
    constructor(private productStore:Store<ProductState>) { }

    ngOnInit() {
    // this.products = [
    // {name:'Product A', price:200},
    // {name:'Product B', price:300},
    // {name:'Product C', price:500},
    // ]
    //this.productStore.select('products').pipe().subscribe((data:Product[]) => this.products = data);
    this.productStore.select(productState).pipe().subscribe((data:Product[]) => this.products = data);
    }
    addProduct(product:Product){
    //this.products.push(Object.assign({},product));
    this.productStore.dispatch(new ProductAddAction(product));
    }
    removeProduct(index:number){
    //this.products.splice(index, 1);
    this.productStore.dispatch(new ProductRemoveAction(index));
    }
    }

    FAQ

  • 如果遇到错误Cannot delete property '2' of [object Array]

    • 通过命令npm install @ngrx/store@8.5.2安装"@ngrx/store": "^8.5.2"
  • 如果遇到错误ERROR TypeError: Cannot assign to read only property '1' of object '[object Array]'

    • 通过命令npm install @ngrx/store@8.5.2安装"@ngrx/store": "^8.5.2"

      发布

  • 知乎

    引用

VSCode调试Angular

配置Chrome的Debugger实例

  • 创建Chrome Debgger实例快捷方式
    • 右键桌面->新建快捷方式->输入chrome.exe的路径
    • 修改快捷方式的属性
    • 将目标更改为C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9221 --user-data-dir=/tmp/chrome-debug -- "%1
  • 通过命令行打开Chrome Debug实例
    • 打开cmd窗口
    • 通过下面的命令导航到chrome.exe的文件夹
      1
      cd C:\Program Files (x86)\Google\Chrome\Application
    • 执行下面的命令打开Chrome Debug实例
      1
      chrome.exe --remote-debugging-port=9221 --user-data-dir=/tmp/chrome-debug
  • 启动Angular项目
  • 在Chrome Debug实例输入Angular的调试地址
  • 修改.vscode/launch.json
    1
    2
    3
    4
    5
    6
    7
    {
    "type": "chrome",
    "request": "attach",
    "name": "Debug Angular",
    "port": 9221,
    "urlFilter": "https://localhost:44358*",
    }
  • 启动项目测试