AngularFireList - Избегайте перезагрузки всех данных при изменении подписки - Angular5 / Ionic3 - PullRequest
0 голосов
/ 06 июля 2018

Доброе утро,

У меня проблема при загрузке списка из firebase и отображении его в моем виде с AngularFire2.

Пояснения:

У меня на страницефункция, позволяющая загрузить список сообщений и сопоставить сообщения с пользовательскими данными, чтобы затем связать весь список с представлением:

Timeline.ts:

// Get the timeline data
getTimeline() {
    console.log('Get timeline data');
    this.loadingProvider.show();
    this.createUserData(); // create user data if accoutn just created
    this.dataProvider.getCurrentUser().valueChanges().subscribe((user) => {
      this.user = <any>user;
    });

    this.getUserFriends().then((friends) => {
      this.friends = friends;
    }).then(() => {
      this.dataProvider.getTimelinePost().valueChanges().subscribe((posts) => {
        console.log("Change in timeline");
        this.timelineData = posts.slice(0).reverse().map(p => {
          return this.checkPostData(p);
        });
        this.loadingProvider.hide();
      });

    });
  }

// Get the friend's user so we can display only the post who are relevants for the user
  getUserFriends() {
    return new Promise((resolve) => {
      this.dataProvider.getFriends().valueChanges().subscribe((f) => {
        resolve(f)
      });
    });
  }

// Map post data with user's info such as avatar, etc
  checkPostData(currentPost) {
    let postedByFriend = this.checkIfPostedByFriend(currentPost);
    if (postedByFriend || currentPost.postBy === firebase.auth().currentUser.uid) {
      this.dataProvider.getUser(currentPost.postBy).valueChanges().subscribe((user) => {
        currentPost.avatar = user.img;
        currentPost.name = user.name
      });
      //  Liked ?
      this.dataProvider.getLike(currentPost.key).valueChanges().subscribe((likes) => {
        currentPost.likes = likes.length;
        currentPost.isLike = this.checkIfPostIsLiked(likes);
      });
      //  Disliked ?
      this.dataProvider.getdisLike(currentPost.key).valueChanges().subscribe((dislikes) => {
        currentPost.dislikes = dislikes.length;
        currentPost.isdisLike = this.checkIfPostIsDislike(dislikes);
      });
      // Commented ?
      this.dataProvider.getComments(currentPost.key).valueChanges().subscribe((comments) => {
        currentPost.comments = comments.length;
        currentPost.isComment = this.checkIfPostIsCommented(comments);
      });
    }
    return currentPost;
}

Временная шкала.html:

    <ion-card *ngFor="let item of timelineData">
    <ion-item >
      <ion-avatar item-left>
        <img src="{{item.avatar}}"  >
      </ion-avatar>
      <h2>{{item.name}}</h2>
      <p>{{item.dateCreated | DateFormat}}</p>
    </ion-item>

    <ion-card-content>
      <p>{{item.postText}}</p>
    </ion-card-content>
    [...]
  </ion-card>

DataProvider.ts:

// Get Timeline post
  getTimelinePost(){
    return this.angularDb.list<any>('/timeline');
  }

Моя проблема заключается в следующем: когда я добавляю сообщение на временную шкалу, это занимает несколько секунд, поскольку Timeline.tsстраница перезагружает весь список (что является нормальным, потому что есть подписка, сделанная с помощью getTimeLinePost ()).Я хотел бы знать, возможно ли обнаружить только последние изменения в моем списке, чтобы добавить их в представление и, таким образом, избежать перезагрузки всех данных.

Я уже пробовал SnapshotChanges () и StateChange (), но безуспешно.

Я заметил, что когда я добавляю сообщение, подписка срабатывает дважды:

Изменение временной шкалы (2) timeline.ts: 89: 8

Код Addpost.ts:

pushNewPost(url) {
    this.angularDb.list('timeline').push({
      dateCreated: new Date().toString(),
      postBy: firebase.auth().currentUser.uid,
      postText: this.postText,
      image: url
    })
}

Используемые версии: "@angular": "5.2.11", "angularfire2": "^ 5.0.0-rc.5-next",

Есть идеи для этого?Я что-то упустил?

спасибо заранее

ОБНОВЛЕНИЕ:

Я нашел альтернативу, которая оказывает меньшее влияние на производительность рендеринга (на представление), но все данные все ещезагружается при изменении в базе данных:

    getTimeline() {
    console.log('Get timeline data');
    this.loadingProvider.show();
    this.createUserData();
    this.dataProvider.getCurrentUser().valueChanges().subscribe((user) => {
      this.user = <any>user;
    });

    this.getUserFriends().then((friends) => {
      this.friends = friends;
    }).then(() => {
      this.dataProvider.getTimelinePost().snapshotChanges().map(actions =>
        actions.map(a => ({ type: a.type, ...a.payload.val() }))
      ).subscribe(items => {
        this.checkPostData(items);
        this.loadingProvider.hide();
      });
    });
  }

  getUserFriends() {
    return new Promise((resolve) => {
      this.dataProvider.getFriends().valueChanges().subscribe((f) => {
        resolve(f)
      });
    });
  }

  checkPostData(currentPost) {
   // Check if whe have a child added to the timeline
    if (this.timelineLoaded === true) {
      currentPost.forEach(p => {
        if (p.type === 'child_added') {
          this.mapPost(p);
        }
      });
    } else {
      this.timelineData = [];
      currentPost.map(p => {
        return this.mapPost(p);
      });
    }
  }

  mapPost(currentPost) {
    let postedByFriend = this.checkIfPostedByFriend(currentPost);
    if (postedByFriend || currentPost.postBy === firebase.auth().currentUser.uid) {
      this.dataProvider.getUser(currentPost.postBy).valueChanges().subscribe((user) => {
        currentPost.avatar = user.img;
        currentPost.name = user.name
      });
      //  Liked ?
      this.dataProvider.getLike(currentPost.key).valueChanges().subscribe((likes) => {
        currentPost.likes = likes.length;
        currentPost.isLike = this.checkIfPostIsLiked(likes);
      });
      //  Disliked ?
      this.dataProvider.getdisLike(currentPost.key).valueChanges().subscribe((dislikes) => {
        currentPost.dislikes = dislikes.length;
        currentPost.isdisLike = this.checkIfPostIsDislike(dislikes);
      });
      // Commented ?
      this.dataProvider.getComments(currentPost.key).valueChanges().subscribe((comments) => {
        currentPost.comments = comments.length;
        currentPost.isComment = this.checkIfPostIsCommented(comments);
      });
      this.timelineData.unshift(currentPost);
      this.timelineLoaded = true
    }
    return currentPost;
  }

1 Ответ

0 голосов
/ 06 июля 2018

На моем опыте нет удобного способа сделать это.

Но поскольку это временная шкала, изменения в списке всегда будут добавляться потомками. Вы можете сделать что-то вроде этого:

  this.timelineData = [];

  this.dataProvider.getTimelinePost().valueChanges().subscribe((posts) => {
    posts.forEach(p => {
      if (!this.timelineData.find(data => data.dateCreated === p.dateCreated)) {
        const elem = this.checkPostData(p);

        // this will put it in front of the list, don't need to reverse
        this.timelineData.unshift(elem);
      }
    })
  });

Кстати, вы не должны подписываться на подписку или подписываться, чтобы создать обещание.

Давайте рассмотрим:

const obs1$ = afDb.object(ANYREF).valueChanges();

obs1$.subscribe(data => {
  const obs2$ = afDb.object(data).valueChanges();
  obs2$.subscribe(a => console.log(a));
});

Это будет работать, но каждый раз, когда obs1 $ срабатывает, obs2 $ будет "повторно" подписан.

т.е.: если есть 5 изменений на obs1 $, у вас будет 5 подписок obs2 $ '. каждый новый obs2 $ меняется, у вас будет 5 раз console.log ..., это утечка памяти

Исправление может быть с использованием оператора take (1). Это может привести к активации подписки один раз.

const obs1$ = afDb.object(ANYREF).valueChanges();

obs1$.subscribe(data => {
  const obs2$ = afDb.object(data).valueChanges();

  // Rxjs 5 way :
  obs2$.take(1).subscribe(a => console.log(a));
  // Rxjs 6 way :
  obs2$.pipe(take(1)).subscribe(a => console.log(a));
});

obs2 $ всегда будет запускаться один раз, без утечек памяти, но у вас не будет "живых данных базы данных" с obs2 $.

лучший способ:

const obs1$ = afDb.object(ANYREF).valueChanges();

// Rxjs 5 way :
obs1$.switchMap(data => afDb.object(data).valueChanges())
  .subscribe(a => console.log(a));
// Rxjs 6 way :
obs1$.pipe(switchMap(data => afDb.object(data).valueChanges()))
  .subscribe(a => console.log(a));

С помощью этого метода вы сохраните обе наблюдаемые «живыми» привязками к вашей базе данных без утечек. (За исключением созданного, потому что он никогда не отписывается).

Вы можете сделать это:

// ionViewWillEnter
const obs1$ = afDb.object(ANYREF).valueChanges();
this.sub = obs1$.pipe(switchMap(data => afDb.object(data).valueChanges()))
  .subscribe(a => console.log(a));

// ionViewWillLeave
this.sub.unsubscribe();

Если вы этого не сделаете, ваши подписки будут сохраняться, когда вы не на той же странице.

Ваш код кажется мне действительно сложным, но если вы «примерный ученик», вы можете обратиться к этому документу: https://www.learnrxjs.io/

...