Angular 2 - не определено при совместном использовании переменной с данными API между компонентами

В основном то, что я пытаюсь сделать, это один раз нажать на мой API и сохранить результат внутри глобальной переменной в моей службе, а затем поделиться и изменить это значение в моем родительском и дочернем компонентах с помощью двух вспомогательных функций.

repairs.service.ts

public myItems:any[];

   public GetRepairs = ():Observable<any> => {

     this.headers = new Headers();
     this.headers.set('Authorization', 'Bearer' + ' ' + JSON.parse(window.localStorage.getItem('token')));

      return this._http.get(this.actionUrl +'repairs'{headers:this.headers})
              .map((res) => {return res.json();
                }).map((item) => {
                    let result:Array<any> = [];
                    if (item.items) {
                        item.items.forEach((item) => {
                            result.push(item);
                        });
                    }

                    this.myItems = result;
                    return this.myItems;
             });
    };

    public GetItems() {
       return this.myItems;
    };

    public UpdateItems(data:any[]) {
       this.myItems = data;
    };

И затем в моем основном компоненте я делаю

repairs.component.ts

export class RepairsComponent implements OnInit {
    public myItems:any[];

    constructor(private _userService:UserService,
                private _RepairsService:RepairsService,
                public _GlobalService:GlobalService) {
    }

    ngOnInit() {
        this._userService.userAuthenticate();
        this.getAllItems();
    }

    private getAllItems():void {
        this._RepairsService
            .GetRepairs()
            .subscribe((data) => {
                    this._RepairsService.UpdateItems(data);
                },
                error => console.log(error),
                () => {
                    this.myItems = this._RepairsService.GetItems();
                });
          }
   }

Это работает просто отлично, но когда я пытаюсь вызвать GetItems() в дочернем компоненте, я получаю undefinded. Я пытаюсь сделать это внутри конструктора и ngOnInit с тем же результатом.

child.component.ts

export class ChildComponent {
    private items:any[] = [];


    constructor(private _RepairsService:RepairsService, 
                private _Configuration:Configuration) {
        this.items = this._RepairsService.GetItems();
        // undefinded
    }


    ngOnInit() {
        this.items = this._RepairsService.GetItems();
        // undefinded
    }
}

person MichalObi    schedule 11.04.2016    source источник
comment
Когда вы загружаете дочерний компонент? Вы уверены, что это происходит только после успешного завершения вызова getAllItems в компоненте ремонта?   -  person MSwehli    schedule 11.04.2016
comment
Я загружаю дочерний компонент, как обычно, внутри оператора директив для моего repair.component. Можно ли вообще загрузить дочерний компонент ПОСЛЕ успешного завершения getAllItems?   -  person MichalObi    schedule 11.04.2016


Ответы (3)


Из того, что я вижу в ограниченном количестве кода, которым вы поделились, может показаться, что вы пытаетесь получить элементы до завершения вызова http get и сохранения данных. Я думаю, что лучшим шаблоном проектирования было бы сделать функцию GetItems() также наблюдаемой или обещанной и проверить, есть ли данные, если не вызвать вызов http get, и после завершения отправить данные обратно в различные компоненты, которые нужно это.

person MSwehli    schedule 11.04.2016
comment
Можете ли вы предоставить пример кода, чтобы показать мне, как сделать Observable внутри функции GetItems()? - person MichalObi; 11.04.2016
comment
Я немного отредактирую ответ выше, чтобы показать пример, но в то же время я настоятельно рекомендую это видео (которое рекомендуется в самих документах angular2) youtube.com/watch?v=UHI0AzD_WfY - person MSwehli; 11.04.2016

Как упоминал @MSwehli, при выполнении асинхронного кода вы не можете полагаться на порядок строк кода. В этом коде:

ngOnInit() {
    this.items = this._RepairsService.GetItems();
    // undefinded
}

асинхронный код в GetItems(); планируется для последующего выполнения в очереди событий, а затем продолжается с кодом синхронизации. Запланированный код будет выполнен в конечном итоге, но не определено, когда. В этом примере это зависит от ответа сервера.

Если вы возвращаете Promise, вы можете использовать .then(...) цепочку выполнения, чтобы ваш код выполнялся только после завершения асинхронного выполнения.

person Günter Zöchbauer    schedule 11.04.2016
comment
Какой вариант лучше в моей ситуации Promise или, как @MSwehli упоминает Observable ? Можете ли вы показать мне, как точно я должен использовать этот шаблон в моем операторе возврата? - person MichalObi; 11.04.2016
comment
Observable обычно используется для серии событий, а Promise — для одного события. Существует сильное движение за использование Observables везде, потому что их можно отменить и везде использовать один и тот же API. Я думаю, лучший способ - вернуть Observable вместо Subscription. Это требует, чтобы вызывающий абонент выполнил подписку. Я обновлю свой ответ. - person Günter Zöchbauer; 11.04.2016
comment
На самом деле в вашем случае все немного сложнее. Завтра еще посмотрю. Здесь довольно поздно. Может быть, @MSwehli или кто-то другой привел пример ранее. - person Günter Zöchbauer; 11.04.2016
comment
Я думаю, вам следует переключиться на шаблон, показанный в stackoverflow.com/questions/36271899/, а затем вызвать myService.getData().subscribe((data => doSomethign()) где угодно вы хотите получить доступ к данным. - person Günter Zöchbauer; 12.04.2016

В вашем коде есть две ошибки/несоответствия:

  1. За вызовом userAuthenticate() следует вызов getAllItems(). Эти вызовы являются асинхронными, пользователь еще не аутентифицирован к моменту вызова getAllItems(), getAllItems завершится ошибкой.

Решение здесь состоит в том, чтобы связать вызовы с помощью rxjs flatMap:

//assuming userAuthenticate returns Observable
userService.userAuthenticate().flatMap(()=>{
    return repairsService.GetRepairs();
}).subscribe(..process repairs..);
  1. getAllItems() вызывается почти одновременно с GetItems(). В большинстве случаев он также терпит неудачу, потому что предыдущий http-запрос не завершается при вызове GetItems().

На мой взгляд, ранняя инициализация здесь не нужна, используйте сервис напрямую:

//ChildComponent
ngOnInit() {
    this._RepairsService.GetRepairs().subscribe(..do anything with list of repairs i.e. assign to bindable property..);
}

Вы можете добавить операторы console.log в каждую часть кода, чтобы увидеть порядок событий в вашем приложении.

person kemsky    schedule 11.04.2016
comment
Но в этот момент я использую this.getAllItems() в ngOnInit в repair.component.ts для перебора массива myItems с помощью NgFor в моем repair.component.html. - person MichalObi; 11.04.2016
comment
вы можете объявить общедоступное поле компонента как public repairs:Array<Repair> = [];, а затем .subscribe(repairs=> this.repairs = repairs);, а привязка должна сделать все остальное. - person kemsky; 11.04.2016