服务方法如何在 ngOnInit() 内部工作,甚至 ngOnInit() 钩子执行一次?

Posted

技术标签:

【中文标题】服务方法如何在 ngOnInit() 内部工作,甚至 ngOnInit() 钩子执行一次?【英文标题】:How the service method is working inside the ngOnInit() even ngOnInit() hook executed once? 【发布时间】:2021-03-31 01:11:32 【问题描述】:

这是我的 Angular 服务中的代码......

export class DataService

     products = 
            'productsList': [
                 id: 101, name: 'Apple', qty: 2,
                 id: 102, name: 'Mango', qty: 1,
                 id: 103, name: 'Grapes', qty: 1,
                 id: 104, name: 'Banana', qty: 3
             ]
      ;

     constructor() 

     getProducts()
          return this.products.productsList;
     

     addProduct(obj: id:number, name:string, qty:number)
         this.products.productsList.push(obj);
     
 

这个来自角度组件...

export class AppComponent implements OnInit
   pId!:number;
   pName!:string;
   pQty!:number;
   pObject!:id:number, name:string, qty:number;
   pObjectArray:id:number, name:string, qty:number[] = [];

   constructor(private dataService: DataService) ;

   ngOnInit()
       this.pObjectArray = this.dataService.getProducts();
   

   addProduct()
       this.pObject = 
       id: this.pId,
       name: this.pName,
       qty: this.pQty
       
       this.dataService.addProduct(this.pObject);
  

所以我只想知道我们什么时候加载这个组件,然后 ngOnIniti() 会调用一次,它会从服务类调用 getProducts() 方法,然后我们会通过这个方法从服务中获取数据,然后将其分配给组件内的 pObjectAray 属性。这很好。但是现在如果我通过调用组件的 addProduct() 方法添加任何新产品,然后此方法将调用服务的 addProduct() ,因此它将更新服务的产品对象,因此现在产品对象有 5 个新产品而不是 4 个因为我们刚刚添加了新的。所以我的问题是 ngOnInit 中的 getProducts() 如何自动执行并更新组件的 pObjectArray 属性?

我的意思是 ngOnIniti() 仅在我们加载组件时调用一次,因此 ngOnInit() 这次不会执行,我已经通过在 ngOnInit() 中使用 console.log() 打印一些消息来测试它。 所以请解释一下数据是如何随着每次更改在组件中自动更新的。例如,如果我们添加新数据或删除或修改数据,那么它会自动使用更新后的数据更新 pObjectArray。

对不起,我的英语不好......

【问题讨论】:

【参考方案1】:

这是因为从服务文件返回数组By reference。因此,无论您对服务文件中的数组所做的任何更改,都会反映在获取该数组的相应组件中。

并且由于 Angular 的 default change detection 策略,您将在模板或您使用该数组的任何地方看到更新。

如果您发送数组的副本,那么您将看不到您所面临的行为。

getProducts() 
  // return this.products.productsList.slice();
  or
  // return [ ...this.products.productsList ];

查看下面的 sn-p 以了解它是如何改变数组的

var a = [1, 2, 3, 4, 5]

function changeArray(arr) 
  arr.splice(2, 1);


changeArray(a);
console.log(a);

查看此stackblitz,了解返回数组副本未更新组件属性的原因

【讨论】:

是的,你是对的,我想现在我得到了我的问题的答案,因为数组总是按引用而不是按值传递,所以我们在 pObjectArray 中获取数组的引用或地址,所以如果我们做任何更改在服务数组内部,那么所有的都将在组件 pObjectArray 中反映出来,因为 pObjectArray 和 products.productList 都指向同一个数组。【参考方案2】:

你可以使用 observables。因此,当您在服务中更新 pObjectArray 时,您在组件中订阅它并获得新值。

一种方法是在您的服务中定义一个主题:

 private pObjectArraySubject = new BehaviorSubject<id:number, name:string, qty:number[]>([]);

 public pObjectArray$ = this.pObjectArraySubject .asObservable();

然后在 addProduct 方法中更新您的主题:

addProduct(obj: id:number, name:string, qty:number)
    let ps = this.pObjectArraySubject.getValue();
    ps.push(obj);
    this.pObjectArraySubject.next(ps);
 

在你的组件ngOnInit生命周期中订阅它:

this.dataService.pObjectArray$.subscribe((obj) => 
      this.pObjectArray = obj;
 );

所以每次值更新时,您的this.pObjectArray 也会更新。 您也可以在模板中使用asyc 管道:

this.pObjectArray = this.dataService.pObjectArray$;
<a-component [data]="pObjectArray  | async"></a-component>

记住: 当你订阅一个 observable 时,不要忘记取消订阅它。 一种方法是在noOnDestroylifeCycle 中取消订阅。

【讨论】:

【参考方案3】:

尝试更改您的 addProducts,如下所示:

addProduct(obj: id:number, name:string, qty:number)
this.products.productsList = [...this.products.productsList.push(obj)];

【讨论】:

【参考方案4】:

Aakash,正如你所说,ngOnInit 上的指令不会更新列表,只会执行一次。您需要在添加新产品时手动刷新列表。您可以重复调用 this.dataService.getProducts

  addProduct() 
      ...
      this.dataService.addProduct(this.pObject);
      this.pObjectArray = this.dataService.getProducts();
  

或者直接添加到你的数组中

  addProduct() 
      ...
      this.dataService.addProduct(this.pObject);
      this.pObjectArray.push(this.pObject);
  

好吧,你没有 observables - 通常你想要返回 observables 并订阅。您可以使用 rxjs 运算符 of 进行模拟,因此我们正在更改您的服务以返回 observables - 通常您使用 httpClient 调用 API

import of,Observable from 'rxjs'

export class DataService 

     products = ...

     constructor() 

     getProducts():Observable<any[]> 
          //possible you'll change by a return this.httpClient.get('....')
          return of(this.products.productsList);
     

     addProduct(obj: id:number, name:string, qty:number):Observable<any>
         //we are going to return an object success:true or success:false
         //if we add successful the element
         bool ok=true;
         this.products.productsList.push(obj);
         if (ok)
           return of(success:true)
         
         return of(success:false)
     
 

当我们收到 observables 时,我们有两个选择,使用 pipe async 或订阅。看到不一样了。

使用管道异步

pObjectArray$:Observable<any[]>
ngOnInit()
       this.pObjectArray$ = this.dataService.getProducts();
   

还有你的 .html

<div *ngFor="let item of pObjectArray$ |async as >
     item.nameitem.qty
</div>

通常我们调用的变量是有后缀“$”的observables,但它是可选的

订阅并在变量中获取订阅的值

pObjectArray:any[]=[] //see that it's an array and we can initialize with an empty array
ngOnInit() 
       this.dataService.getProducts().subscribe(res=>
            this.pObjectArray=res.productList
       )
   

还有你的 .html

<div *ngFor="let item of pObjectArray>
     item.nameitem.qty
</div>

好吧,这不能解决这个问题,当您添加一个元素时列表已更新,我们需要再次更改我们的函数 addProduct

addProduct() 
   ...
   this.dataService.addProduct(this.pObject).subscribe(res => 
       if (res.success)
       
          //if you're using async pipe again repeat 
          this.pObjectArray$ = this.dataService.getProducts();
          //or if you're are subscribing you can
              //subscribing again (*)
              this.dataService.getProducts().subscribe(res=>
                 this.pObjectArray=res.productList
              )
              //or adding manually the element to the array
              this.pObjectArray.push(this.pObject);
       
   )
  

好吧,我们选择函数 addProduct 返回一个成功为真或假的对象,但我们可以选择函数 addProduct 返回插入的产品 - 例如值“id”自动递增,或者返回整个列表。

嗯,到目前为止,我们使用带有 httpClient 的 observables 或使用 'of'。我确定您是在询问在 ngOnInit 中创建订阅以刷新列表。一些 observables 你可以订阅一个独特的时间并收到更改。在自己的 Angular 中,您有一个 FormControl,您可以订阅 valueChanges 或 ActiveRouted 并订阅 paramMap。

我们正在更改您的一些服务

import of,Observable,Subject from 'rxjs'

export class DataService
  productSubject:Subject<any[]>=new Subject<any[]>();
  products = ...

  init() 
    this.productSubject.next(this.products.productsList); 
  

  addProduct(obj:  id: number; name: string; qty: number ) 
      obj.id=this.products.productsList.length+1;
      obj.name=obj.name+ " "+obj.id
      this.products.productsList.push(obj);
      this.productSubject.next(this.products.productsList);
  

看到函数 addProduct 或 init 没有返回任何东西,它们只生成一个 this.productObservables.next

我们可以在 ngOnInit 中订阅

   ngOnInit()
       this.pObjectArray$ = this.dataService.productSubject;
   
   //or 
   ngOnInit()
       this.dataService.productSubject.subscribe(res=>
         this.pObjectArray=res;
       )
   

并且在任何时候初始化服务,例如在 ngAfterViewInit 中(包含在 setTimeout 中)

ngAfterViewInit() 
    setTimeout(() => 
      this.dataService.init();
    );
  

您会看到,当您添加一个项目时,列表是如何刷新的,但请记住原因是因为我们的服务使“下一个”

在this stackblitz我写了最后一个方法

(*)在实际应用中我们使用switchMap rxjs操作符来改变订阅的值,但这只是一个大概的思路

【讨论】:

是的,我明白了。但我不必在 addProduct() 中再次调用 getProducts() 方法来手动刷新列表,因为我的列表已经在我的原始代码中自动刷新。 我在原始代码中得到了问题的答案,我返回 products.productList 的引用或地址,然后将该引用分配给 pObjectArray 变量,因此我的两个属性 pObjectArray 和 products.productList 都指向到同一个数组,这就是为什么即使我的 getProducts 方法执行了一次,但我的 pObjectArray 中的数据仍然会自动更新,即使我没有在 addProduct 方法之后调用 getProducts 方法,但我的 pObjectArray 由于引用而自动更新。 Askash,如果你正在使用对象,那么你在哪里更改对象并不重要:在服务中,在组件中......当你想使用 observables 时,我的回答是,抱歉造成混淆 谢谢Eliseo,我明白了。再次感谢您的帮助。 :)

以上是关于服务方法如何在 ngOnInit() 内部工作,甚至 ngOnInit() 钩子执行一次?的主要内容,如果未能解决你的问题,请参考以下文章

表单的 post 方法如何在幕后实际工作

小白如何在工作中不慌?如果淡定解决问题?博主分享经验

带有软删除的 Spring Boot GET 方法如何在服务 impl 中添加另一个异常

多次调用 ngOnint() 服务方法内部

K-NN 算法如何在 rapidminer 中以相同的距离工作?

方法如何在 Ruby 中使用哈希参数?