1

In my project I am fetching email via Google Contact API. My problem is that After fetching email, angular UI is not updating.

Here is my code for the fetching emails in ContactComponent.ts

import { Component, OnInit, EventEmitter, Output, ViewChild } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { ContactService } from './contacts.service';
import { Contact } from './contact.model';
import { Subject } from 'rxjs';
import { ContactListComponent } from './contact-list/contact-list.component';
declare var gapi: any;

@Component({
  selector: 'app-contacts',
  templateUrl: './contacts.component.html',
  styleUrls: ['./contacts.component.css'],
  providers: [ContactService]
})
export class ContactsComponent implements OnInit {
  constructor(private http: HttpClient, private contactService: ContactService) {}
  authConfig: any;
  contactsList: Contact[] = [];
  ContactsFound = true;
  ngOnInit() {

    this.ContactsFound = false;

    this.authConfig = {
      client_id:
        '<my_client_id>',
      scope: 'https://www.googleapis.com/auth/contacts.readonly'
    };

  }

  fetchGoogleContacts() {
    gapi.client.setApiKey('<my_key>');
    gapi.auth2.authorize(this.authConfig, this.handleAuthorization);
  }

  handleAuthorization = authorizationResult => {
    if (authorizationResult && !authorizationResult.error) {
      const url: string =
        'https://www.google.com/m8/feeds/contacts/default/thin?' +
        'alt=json&max-results=500&v=3.0&access_token=' +
        authorizationResult.access_token;
      console.log('Authorization success, URL: ', url);
      this.http.get<any>(url).subscribe(response => {
        if (response.feed && response.feed.entry) {
          // console.log(response.feed.entry);
          this.saveContacts(response.feed.entry);
        }
      });
    }
  }

  saveContacts(ContactEntry) {

    this.contactsList = [];

    ContactEntry.forEach((entry) => {
      // tslint:disable-next-line:prefer-const
      let contact: Contact = { email: '', name: '' };

      if (entry.gd$name !== undefined) {
        contact.name = entry.gd$name.gd$fullName.$t;
        // console.log('Name of contact: ' + contact.name);
      }

      if (Array.isArray(entry.gd$email)) {
        entry.gd$email.forEach((emailEntry) => {
          if (emailEntry.address !== undefined) {
           // console.log('Email of contact: ' + emailEntry.address);
            contact.email = emailEntry.address;
          }
        });
      }

      this.contactsList.push(contact);
    });

    this.ContactsFound = true;
     // Calling next in ContactService for propagating the events
    this.contactService.contactsArrived(this.contactsList);
    console.log(`Contacts List Length ${this.contactsList.length}`);

  }



} 

I am using service for subscribing to event for adding emails

import { Injectable } from '@angular/core';
import { Contact } from './contact.model';
import {  BehaviorSubject, Subject } from 'rxjs';
import { TouchSequence } from 'selenium-webdriver';



@Injectable()
export class ContactService {
  contacts: Contact[] = [];
  private contactsSubject = new Subject<Contact[]>();
  contactArrived$ = this.contactsSubject.asObservable();

  constructor() {
  }

  contactsArrived(contacts: Contact[]) {
     console.log(`Contacts Arrived in Contact Service`);
     if (contacts) {
        this.contactsSubject.next(contacts);
     }
  }
}

Here is my contact.component.html

<button class="btn btn-primary" (click)="fetchGoogleContacts()">Import Contacts</button>
<app-contact-list [contacts]="contactsList"></app-contact-list>

Code for Contact-List Component

import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges } from '@angular/core';
import { ContactService } from '../contacts.service';

import { Contact } from '../contact.model';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-contact-list',
  templateUrl: './contact-list.component.html',
  styleUrls: ['./contact-list.component.css']
})
export class ContactListComponent implements OnInit, OnDestroy {
  @Input() contacts: Contact[];
  subscription: Subscription;

  constructor(private contactService: ContactService) {
  }

  ngOnInit() {
    this.subscription = this.contactService.contactArrived$.subscribe(data => {
      console.log(data);
      this.contacts = data;
    });
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }


}

ContactList Component.html

<div>
  <div>
    <br>
    <h4 class="card-title">Your Contacts</h4>
    <div class="card">
      <div class="card-header">Contact Names
        <span class="float-right">Emails Ids</span>
      </div>
      <ul *ngFor="let contact of contacts" class="list-group list-group-flush">
        <app-contact-item [contact]="contact"></app-contact-item>
      </ul>
    </div>
  </div>
</div>

I am getting data in the Contact-List Component after the rest call from the google contact API. But sum how my UI is not updating. List is not getting updated. After googling I came to know Angular behave differently for the events outside the ngZone. How can I update the UI after I receive the data.

Zakir saifi
  • 406
  • 4
  • 23
  • what is the purpose of passing a list through an input property if you are going to set it inside of the child component through a subscribe callback anyway? – Jota.Toledo Oct 20 '18 at 19:32

1 Answers1

-1

Root cause

It looks like the contacts is not being capture as a part of change detector mechanism of Angular.

Fix : You have couple of choices

The first is to have setter and getter for the contacts

Functions always play the vital role as part of change detector so it ensure that changes are in effect and UI is updated accordingly.

in ts

import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges } from '@angular/core';
import { ContactService } from '../contacts.service';

import { Contact } from '../contact.model';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-contact-list',
  templateUrl: './contact-list.component.html',
  styleUrls: ['./contact-list.component.css']
})
export class ContactListComponent implements OnInit, OnDestroy {
  @Input("contacts") _contacts: Contact[];
  subscription: Subscription;

  constructor(private contactService: ContactService) {
  }

  ngOnInit() {
    this.subscription = this.contactService.contactArrived$.subscribe(data => {
      console.log(data);
      this._contacts = data;
    });
  }

    get contacts(){
        return this._contacts;
    }

    set contacts(contacts){
        this._contacts = contacts;
    }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }

}

The second option to have use setTimeout while setting the contacts

ngOnInit() {
        this.subscription = this.contactService.contactArrived$.subscribe(data => {
          console.log(data);
          setTimeout(()=>this.contacts = data);
        });
      }
Sunil Singh
  • 11,001
  • 2
  • 27
  • 48
  • Your first approach will have no effect, and the second is IMO a hack. – Jota.Toledo Oct 20 '18 at 19:30
  • Would you please let me know why would the first approach will not work and what would be the alternative for this ? – Sunil Singh Oct 20 '18 at 19:39
  • After adding the getters and setters Ui was not updating. But after using Ngzone run method, UI is updating. ```ngOnInit() { this.subscription = this.contactService.contactArrived$.subscribe(data => { console.log(data); this._ngzone.run(() => { this._contacts = data; }); }); }``` – Zakir saifi Oct 21 '18 at 04:01