Как сервисный метод работает внутри хука ngOnInit (), даже если ngOnInit () выполняется один раз?

Это код моей службы 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 потому что мы только что добавили новый. Итак, мой вопрос в том, как getProducts () внутри ngOnInit выполняется автоматически и обновляет свойство pObjectArray компонента?

Я имею в виду, что ngOnIniti () вызывается только один раз, когда мы загружаем компонент, поэтому на этот раз ngOnInit () не будет выполняться, и я уже протестировал его, распечатав какое-то сообщение с помощью console.log () внутри ngOnInit (). Итак, пожалуйста, объясните мне, как данные автоматически обновляются в компоненте при каждом изменении. например, если мы добавляем новые данные, удаляем или изменяем данные, тогда он автоматически обновляет pObjectArray обновленными данными.

Извините за мой плохой английский...


person Aakash Giri    schedule 21.12.2020    source источник


Ответы (4)


Это из-за возврата массива By reference из служебного файла. Таким образом, любые изменения, которые вы вносите в массив в вашем служебном файле, будут отражены в соответствующих компонентах, которые получат этот массив.

А благодаря стратегии default change detection angular вы увидите обновления в шаблоне или везде, где вы используете этот массив.

Если вы отправите копию массива, вы не увидите поведения, с которым столкнулись.

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

Посмотрите ниже фрагмент о том, как он изменяет массив

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

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

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

Посмотрите этот stackblitz, чтобы узнать, как возвращается копия массива не обновила свойство компонента

person Sivakumar Tadisetti    schedule 21.12.2020
comment
Да, вы правы, я думаю, что теперь я получил ответ на свой вопрос, потому что массив всегда передается по ссылке, а не по значению, поэтому мы получаем ссылку или адрес массива в pObjectArray, поэтому, если мы сделаем какие-либо изменения внутри служебного массива, все будет то же самое отражено в компоненте pObjectArray, потому что и pObjectArray, и products.productList указывают на один и тот же массив. - person Aakash Giri; 21.12.2020

Вы можете использовать наблюдаемые. Поэтому, когда вы обновляете 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>

ПОМНИТЕ: когда вы подписываетесь на наблюдаемое, не забудьте отписаться от него. один из подходов - отменить подписку в noOnDestroy lifeCycle.

person Saghi Shiri    schedule 21.12.2020
comment
Спасибо, что понял. - person Aakash Giri; 21.12.2020

Попробуйте изменить addProducts следующим образом:

addProduct(obj: {id:number, name:string, qty:number}){
this.products.productsList = [...this.products.productsList.push(obj)];
}
person Özcan Diler    schedule 21.12.2020

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);
  }

Что ж, у вас нет наблюдаемых - обычно вы хотите вернуть наблюдаемые и подписаться. Вы можете моделировать с помощью оператора rxjs of, поэтому мы меняем вашу службу, чтобы возвращать наблюдаемые - обычно вы вызываете API с помощью httpClient

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})
     }
 }

Когда мы получили наблюдаемые объекты, у нас есть два варианта: использовать pipe async или подписаться. вижу, что это другое.

Использование pipe async

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

И ваш .html

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

Обычно мы вызываем переменные, которые есть наблюдаемые, с суффиксом $, но это необязательно

Подписка и получение значения подписки в переменной

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.name}}{{item.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 возвращала объект с успехом true или false, но мы можем выбрать, чтобы функция addProduct возвращала вставленный продукт - например, с автоматически увеличивающимся значением id или возвращала весь список.

Что ж, до сих пор мы использовали наблюдаемые объекты с httpClient или с помощью 'of'. Я уверен, что вы спрашиваете о создании подписки в ngOnInit, которая обновляет список. На некоторые наблюдаемые вы можете подписаться на уникальное время и получили изменение. В собственном 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();
    });
  }

И вы видите, как, когда вы добавляете элемент, список обновляется, но помните, что причина в том, что наш сервис делает следующий

В этом stackblitz я написал последний метод

(*) В реальном приложении мы используем оператор switchMap rxjs для изменения значения подписки, но это только для общего представления

person Eliseo    schedule 21.12.2020
comment
Да я получил его. но мне не нужно снова вызывать метод getProducts () внутри addProduct (), чтобы обновлять список вручную, потому что мой список уже обновляется автоматически в моем исходном коде. - person Aakash Giri; 21.12.2020
comment
Я получил ответ на свой вопрос в моем исходном коде, я возвращаю ссылку или адрес products.productList, а затем назначаю эту ссылку переменной pObjectArray, поэтому оба моих свойства pObjectArray и products.productList указывают на один и тот же массив, поэтому даже мой Метод getProducts выполняется один раз, но данные внутри моего pObjectArray обновляются автоматически, даже если я не вызываю метод getProducts после метода addProduct, но мой pObjectArray обновляется автоматически из-за ссылки. - person Aakash Giri; 21.12.2020
comment
Аскаш, если вы работаете с объектами, не имеет значения, где вы меняете объект: в сервисе, в компоненте ... Мой ответ - когда вы хотите использовать наблюдаемые объекты, извините за путаницу - person Eliseo; 21.12.2020
comment
Спасибо, Элисео, я понял вашу точку зрения. Еще раз спасибо за помощь. :) - person Aakash Giri; 21.12.2020