<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Christophe Surbier, Author at Ionic and Django Tutorial</title>
	<atom:link href="https://www.ionicanddjangotutorial.com/author/admin/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.ionicanddjangotutorial.com/author/admin/</link>
	<description>Build a mobile application with Ionic  and Django</description>
	<lastBuildDate>Mon, 16 Aug 2021 12:38:09 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.0.9</generator>
	<item>
		<title>Day 9 : Chatting with a user</title>
		<link>https://www.ionicanddjangotutorial.com/day-9-chatting-with-a-user/</link>
		
		<dc:creator><![CDATA[Christophe Surbier]]></dc:creator>
		<pubDate>Fri, 13 Aug 2021 15:34:29 +0000</pubDate>
				<category><![CDATA[Create a real world mobile chat application with Ionic and Django]]></category>
		<guid isPermaLink="false">https://www.ionicanddjangotutorial.com/?p=1332</guid>

					<description><![CDATA[<p>Create a chat application with Ionic 5/ Angular 12/ Capacitor 3 and Django 3 &#8211; Series &#8211; Part Nine We&#8230; <a href="https://www.ionicanddjangotutorial.com/day-9-chatting-with-a-user/" class="more-link">Continue Reading <span class="meta-nav">&#8594;</span></a></p>
<p>The post <a rel="nofollow" href="https://www.ionicanddjangotutorial.com/day-9-chatting-with-a-user/">Day 9 : Chatting with a user</a> appeared first on <a rel="nofollow" href="https://www.ionicanddjangotutorial.com">Ionic and Django Tutorial</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h1>Create a chat application with Ionic 5/ Angular 12/ Capacitor 3 and Django 3 &#8211; Series &#8211; Part Nine</h1>
<p>We learned how to search users, create chat and display existing chats. Now it&#8217;s time to deals with the <strong>Chat</strong> screen. </p>
<h1>Day 9 : Chatting with a user</h1>
<p>Developping chatting with a user screen will not be an easy part, so we will go slowly. </p>
<h2>Navigate to the Chat screen</h2>
<p>First and easy task, we will create our new <strong>ChatPage</strong>:</p>
<pre><code class="language-shell">ionic g page ChatPage
mv src/app/chat-page src/app/pages/</code></pre>
<p>Then update the app-routing.module.ts file:</p>
<pre><code class="language-typescript">   {
    path: 'chat-page',
    canLoad:[AutoGuard],
    loadChildren: () =&gt; import('./pages/chat-page/chat-page.module').then( m =&gt; m.ChatPagePageModule)
  },</code></pre>
<p>And modify our <strong>createChat</strong> method in <strong>HomePage</strong> to redirect to this new screen, with the **chat*** just created:</p>
<pre><code class="language-typescript">createChat(userToChat) {
    if (this.apiService.networkConnected) {
      this.apiService.showLoading().then(() =&gt; {
        this.apiService.createChat(this.userManager.currentUser.id, userToChat.id).subscribe((response) =&gt; {
          this.apiService.stopLoading()
          console.log(response)
          if (response){
            const navigationExtras: NavigationExtras = {
              state : {
                chat : new Chat().initWithJSON(response)
              }
            };
            this.router.navigate(['chat-page'], navigationExtras);
          }
        })
      })
    }
    else {
      this.apiService.showNoNetwork()
    }
  }</code></pre>
<p>If we have a response from our <strong>API</strong> then we can navigate to our new <strong>ChatPage</strong> screen, and pass the <strong>chat</strong> created and received from the API:</p>
<pre><code class="language-typescript"> if (response){
    const navigationExtras: NavigationExtras = {
      state : {
        chat : new Chat().initWithJSON(response)
      }
    };
    this.router.navigate(['chat-page'], navigationExtras);
  }</code></pre>
<p>We will also modif our <strong>HomePage</strong> html to be able to click on a user in our chat list, and then go to the <strong>ChatScreen</strong>. In our <strong>ion-list</strong> of existing chat:</p>
<pre><code class="language-html"> &lt;ion-list mode="ios" no-lines message-list&gt; 
    &lt;ion-item  *ngFor="let chat of chatList" (click)="goToChat(chat)"&gt;</code></pre>
<p>We add a click event on each item of the list, and we can create the <strong>goToChat</strong> method:</p>
<pre><code class="language-typescript">goToChat(chat){
    const navigationExtras: NavigationExtras = {
      state : {
        chat : chat
      }
    };
    this.router.navigate(['chat-page'], navigationExtras);
  }</code></pre>
<div class="page-break"></div>
<p>And to avoid code duplication, we can even modify our <strong>createChat</strong> method to use this <strong>goToChat</strong> method:</p>
<pre><code class="language-typescript"> createChat(userToChat) {
    if (this.apiService.networkConnected) {
      this.apiService.showLoading().then(() =&gt; {
        this.apiService.createChat(this.userManager.currentUser.id, userToChat.id).subscribe((response) =&gt; {
          this.apiService.stopLoading()
          console.log(response)

          if (response){
            let chat = new Chat().initWithJSON(response)
            this.goToChat(chat)
          }
        })
      })
    }
    else {
      this.apiService.showNoNetwork()
    }
  }</code></pre>
<p>Let&#8217;s edit the <strong>chat-page.page.html</strong> :</p>
<pre><code class="language-html">&lt;ion-header no-border mode="ios"&gt;
  &lt;ion-toolbar color="light" mode="ios"&gt;
    &lt;ion-buttons slot="start"&gt;
      &lt;ion-button mode="ios" color="dark" routerDirection="back" routerLink="/home"&gt;
        &lt;ion-icon name="arrow-back-outline"&gt;&lt;/ion-icon&gt;Back
      &lt;/ion-button&gt;
    &lt;/ion-buttons&gt;
   &lt;ion-title color="primary"&gt;{{username}}&lt;/ion-title&gt;
  &lt;/ion-toolbar&gt;
&lt;/ion-header&gt;

&lt;ion-content&gt;

&lt;/ion-content&gt;
</code></pre>
<p>and in the <strong>chat-page.page.ts</strong> file, we will get the <strong>chat</strong> passed as argument, determine which user is our user (remember we have a <strong>fromUser</strong> and <strong>toUser</strong> in the <strong>chat</strong> object) and then setup the username to display :</p>
<pre><code class="language-typescript">import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Chat } from 'src/app/models/chat';
import { User } from 'src/app/models/user';
import { ApiserviceService } from 'src/app/services/api-service.service';
import { UserManagerServiceService } from 'src/app/services/user-manager-service.service';

@Component({
  selector: 'app-chat-page',
  templateUrl: './chat-page.page.html',
  styleUrls: ['./chat-page.page.scss'],
})
export class ChatPagePage implements OnInit {

  chat : Chat;
  username : string =''
  myUser : User
  otherUser : User
  constructor(public apiService:ApiserviceService,
    public userManager:UserManagerServiceService,
    public router: Router,
    public route:ActivatedRoute) { 

    this.route.queryParams.subscribe(params =&gt; {
      if (this.router.getCurrentNavigation().extras.state) {
        this.chat = this.router.getCurrentNavigation().extras.state.chat;
        console.log(this.chat)
        if (this.chat.fromUser.id==this.userManager.currentUser.id){
          this.myUser = this.chat.fromUser
          this.otherUser = this.chat.toUser
        }
        else{
          this.myUser = this.chat.toUser
          this.otherUser = this.chat.fromUser
        }
        this.username = this.otherUser.first_name+" "+this.otherUser.last_name
      }
      else{
        //Return home
        this.router.navigateByUrl("/home")
      }
    });
  }

  ngOnInit() {
  }

}</code></pre>
<p>We can try our new code (run <strong>ionic serve</strong> if not already running) and navigate from choosing a user in our existing chat list OR from creating a new chat using the searchbar. </p>
<p>Now we have one issue to fix. When searching and creating a new chat, we should close the <strong>searchBar</strong> and refresh our existing chat list. If not, when doing a **back*<strong> action from the chat screen, the </strong>searchBar** will still have focus and the list has not beed updated.</p>
<p>We can fix this by adding to our <strong>createChat</strong> method:</p>
<pre><code class="language-typescript">this.isSearching = false
this.searchbarElement.value=""
this.listOfUser = []
this.loadExistingChat()</code></pre>
<p>Final code: </p>
<pre><code class="language-typescript">createChat(userToChat) {
    if (this.apiService.networkConnected) {
      this.apiService.showLoading().then(() =&gt; {
        this.apiService.createChat(this.userManager.currentUser.id, userToChat.id).subscribe((response) =&gt; {
          this.apiService.stopLoading()

          this.isSearching = false
          this.searchbarElement.value=""
          this.listOfUser = []
          this.loadExistingChat()

          if (response){
            let chat = new Chat().initWithJSON(response)
            this.goToChat(chat)
          }

        })
      })
    }
    else {
      this.apiService.showNoNetwork()
    }
  }</code></pre>
<h2>Creating chat screen design</h2>
<p>Now we will focus on chat screen design and will hardcoding things to facilitate our task. Our chat screen will look like this:</p>
<p><img src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/08/Capture-décran-2021-08-11-à-17.24.10-e1628695483421.png" alt="BasicChatInterface" /></p>
<div class="page-break"></div>
<p>Let&#8217;s start with our messages. We should have a <strong>messages list</strong> of items to display. A message will be composed of the author of the message (yourself or the other user), a text, a created date and the information if the message has been read or not. </p>
<p>Let&#8217;s edit our typescript to hardcode this list of messages:</p>
<pre><code class="language-typescript">import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Chat } from 'src/app/models/chat';
import { User } from 'src/app/models/user';
import { ApiserviceService } from 'src/app/services/api-service.service';
import { UserManagerServiceService } from 'src/app/services/user-manager-service.service';

@Component({
  selector: 'app-chat-page',
  templateUrl: './chat-page.page.html',
  styleUrls: ['./chat-page.page.scss'],
})
export class ChatPagePage implements OnInit {

  chat : Chat;
  username : string =''
  myUser : User
  otherUser : User
  messages = [
    {
      "author":{
        "id":"3cde3f7e-261e-4ebb-8437-fa4c27d35bf0"
      },
      "text":"This is a message",
      "createdAt":"2021-08-11T14:03:05.381477Z",
      "isRead":true
    },
    {
      "author":{
        "id":"fb261053-d336-4a9b-9bcc-d206ac4b7753"
      },
      "text":"This is a response",
      "createdAt":"2021-08-11T14:05:05.381477Z"
    },
  ]
  messageToSend = ""
  constructor(public apiService:ApiserviceService,
    public userManager:UserManagerServiceService,
    public router: Router,
    public route:ActivatedRoute) { 

    this.route.queryParams.subscribe(params =&gt; {
      if (this.router.getCurrentNavigation().extras.state) {
        this.chat = this.router.getCurrentNavigation().extras.state.chat;
        console.log(this.chat)
        if (this.chat.fromUser.id==this.userManager.currentUser.id){
          this.myUser = this.chat.fromUser
          this.otherUser = this.chat.toUser
        }
        else{
          this.myUser = this.chat.toUser
          this.otherUser = this.chat.fromUser
        }
        this.username = this.otherUser.first_name+" "+this.otherUser.last_name
      }
      else{
        //Return home
        this.router.navigateByUrl("/home")
      }
    });
  }

  ngOnInit() {
  }

  checkScrolling(){

  }

}</code></pre>
<div class="page-break"></div>
<p>And now we can write the html code: </p>
<pre><code class="language-html">    &lt;ion-list&gt;  
    &lt;div *ngFor="let message of messages"&gt;
        &lt;div class="chat"&gt;
      &lt;div [class]="message.author.id === myUser.id ? 'messageFromMe' : 'messageFromOther'"&gt;
        &lt;span&gt;
          {{message.text}}
        &lt;/span&gt;
        &lt;span class="timestamp"&gt;
          {{ message.createdAt | date:'HH:mm' }}
          &lt;ion-icon name="checkmark-done-circle-outline" *ngIf="message.author.id === myUser.id &amp;&amp; message.isRead"&gt;&lt;/ion-icon&gt;
          &lt;ion-icon name="ellipse-outline" *ngIf="message.author.id === myUser.id &amp;&amp; !message.isRead"&gt;&lt;/ion-icon&gt;
        &lt;/span&gt;
      &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;/ion-list&gt;</code></pre>
<ol>
<li>
<p>We create a <strong>ion-list</strong> and iterate the list of <strong>messages</strong></p>
</li>
<li>We check if the author of the message is our user and if so we check the <strong>css class</strong> to <strong>messageFromMe</strong>. Otherwise the <strong>css class</strong> will be <strong>messageFromOther</strong>. The  <strong>messageFromMe</strong> will display the message at the right of the screen, with the date and an icon to display if the other user has read it or not. The <strong>messageFromOther</strong> will be displayed on the left of the screen. </li>
</ol>
<p>We can write our <strong>chat-page.page.scss</strong> file:</p>
<pre><code class="language-scss">.chat {
    display: flex;
    flex-direction: column;
}
.messageFromOther {
    display: flex;
    flex-direction: column;
    margin: 0.2em 0;
    padding: 0.5em;
    max-width: 70%;
    margin-left: 13px;
    align-self: flex-start;
    background-color: #F1F0F0;
    color: black;
    border-radius: 10px 10px 10px 0px;
    font-size: 16px;
    .timestamp {
        font-size: 12px;
        opacity: 0.6;
    }
}
.messageFromMe {
    display: flex;
    flex-direction: column;
    margin: 0.2em 0;
    padding: 0.5em;
    max-width: 70%;
    align-self: flex-end;
    background-color:#2636be;
    color: white;
    border-radius: 10px 10px 0px 10px;
    margin-right: 13px;
    font-size: 16px;
    .timestamp {
        display: flex;
        flex-direction: row;
        justify-content: flex-end;
        align-items: center;
        font-size: 12px;
        opacity: 0.6;
        text-align: end;

        ion-icon {
            font-size: 20px;

        }
    }
}</code></pre>
<div class="page-break"></div>
<blockquote>
<p>If you don&#8217;t know css or are not a design expert (like me), please remember you still can buy a ready to use template or hire a designer. </p>
</blockquote>
<p>And finally, we need to add the typing box and sent message button:</p>
<pre><code class="language-html">&lt;ion-footer&gt;
  &lt;ion-toolbar color="light"&gt;
   &lt;ion-row class="ion-align-items-center"&gt;
     &lt;ion-col size="10"&gt;
      &lt;ion-textarea   [(ngModel)]="messageToSend" autocapitalize="sentence"
      placeholder="Type a message" spellcheck="false" color="dark" autoGrow="true"
      rows="1"&gt;&lt;/ion-textarea&gt;
     &lt;/ion-col&gt;
     &lt;ion-col size="2"&gt;
      &lt;ion-button (click)="sendMessage()" shape="round" fill="clear" [disabled]="messageToSend === ''"&gt;
        &lt;ion-icon name="paper-plane-outline"&gt;&lt;/ion-icon&gt;
      &lt;/ion-button&gt;
     &lt;/ion-col&gt;
   &lt;/ion-row&gt;
  &lt;/ion-toolbar&gt;
&lt;/ion-footer&gt; </code></pre>
<h2>Getting messages from Django backend</h2>
<p>Now we will replace our hardcoded message list with real messages from the backend. And because we still don&#8217;t have message, i will use the Django admin to create fake messages. You just need to take a chat between you and a user that you created previously, and filling the message information:</p>
<p><img src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/08/Capture-décran-2021-08-11-à-18.10.08-e1628698239536.png" alt="Createmessage" /></p>
<blockquote>
<p>You will need to repeat the process multiple times to have enought data for testing purpose&#8230;</p>
</blockquote>
<p>We already have a default <strong>API</strong> to get <strong>Message</strong> from the backend. We will just modify the view to be able to filter for a specific <strong>chat id</strong>:</p>
<pre><code class="language-python">class MessageListView(generics.ListCreateAPIView):
    permission_classes = [permissions.IsAuthenticated]
    queryset = Message.objects.all().select_related("refChat")
    serializer_class = MessageSerializer
    filterset_fields = ['id','type','isRead','refChat']
    filter_backends = [DjangoFilterBackend,filters.SearchFilter,filters.OrderingFilter]
    ordering = ['-createdAt']</code></pre>
<ol>
<li>We added the <strong>select_related(&quot;refChat&quot;)</strong> instruction to our queryset to boost performance and use <strong>sql join</strong> instruction</li>
<li>We added a <strong>refChat</strong> in the list of <strong>filterset_fields</strong> which is the fields we can use to filter.</li>
<li>We are ordering the messages from the most recent to the oldest</li>
</ol>
<p>If i try the <strong>API</strong> manually</p>
<pre><code class="language-shell">curl --location --request GET 'http://localhost:8000/api/message/?refChat=66fdac13-79ff-4d0f-a0f2-7001708f51d5' \
--header 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjI4Njk5NzAxLCJqdGkiOiIzMDAzMzYyNDY3OWY0ZjU3YmI0YzZlNTYyN2VhYmI4NiIsInVzZXJfaWQiOiIzY2RlM2Y3ZS0yNjFlLTRlYmItODQzNy1mYTRjMjdkMzViZjAifQ.oZu0NYF3ls9giSNQgACA_ByqphOsuSKhJ1pLWIf8gXg'</code></pre>
<div class="page-break"></div>
<p>I will get the following <strong>json</strong>:</p>
<pre><code class="language-json">{
    "count": 3,
    "next": null,
    "previous": null,
    "results": [
        {
            "id": "a3e2d2dc-9f30-4a00-99e1-ca79a07aa792",
            "message": "message 3",
            "type": 0,
            "extraData": null,
            "isRead": false,
            "createdAt": "2021-08-11T16:11:56.926778Z",
            "updatedAt": "2021-08-11T16:11:56.926849Z",
            "refChat": "66fdac13-79ff-4d0f-a0f2-7001708f51d5",
            "author": "3cde3f7e-261e-4ebb-8437-fa4c27d35bf0"
        },
        {
            "id": "1ac78684-9cde-44ab-ab0a-dc393ba80631",
            "message": "message 2",
            "type": 0,
            "extraData": null,
            "isRead": false,
            "createdAt": "2021-08-11T16:11:45.103939Z",
            "updatedAt": "2021-08-11T16:11:45.104027Z",
            "refChat": "66fdac13-79ff-4d0f-a0f2-7001708f51d5",
            "author": "3cde3f7e-261e-4ebb-8437-fa4c27d35bf0"
        },
        {
            "id": "6cefd678-ba0c-4531-80a3-22aed289b51b",
            "message": "message 1",
            "type": 0,
            "extraData": null,
            "isRead": false,
            "createdAt": "2021-08-11T16:11:31.888809Z",
            "updatedAt": "2021-08-11T16:11:31.888915Z",
            "refChat": "66fdac13-79ff-4d0f-a0f2-7001708f51d5",
            "author": "3cde3f7e-261e-4ebb-8437-fa4c27d35bf0"
        }
    ]
}</code></pre>
<p>Let&#8217;s create a new <strong>message.ts</strong> file in our <strong>models</strong> directory of our <strong>ionic application</strong>:</p>
<pre><code class="language-typescript">export class Message {
    id:string;
    message:string;
    type:number;
    extraData:any;
    isRead:boolean;
    refChat:string;
    author:string;
    createdAt : Date;
    updateAt : Date;

    constructor() {

    }

    initWithJSON(json) : Message{
      for (var key in json) {

            this[key] = json[key];

      }
      return this;
    }
}</code></pre>
<blockquote>
<p>With that class, we will be able to convert our api json response to a typescript message object.</p>
</blockquote>
<p>You may wonder why i&#8217;m getting the message list from the most recent to the oldest one. It is because on a chat screen, we always display the last message to the end of the screen (bottom) and if scrolling to the top, at some point the view will need to retrieved previous messages (if exists). </p>
<div class="page-break"></div>
<p>To know if we have previous messages, we will use the <strong>next</strong> variable of the API response and when calling the next url, we will get a list of previous messages. </p>
<pre><code class="language-json">{
    "count": 3,
    "next": null,
    "previous": null,
    "results": </code></pre>
<blockquote>
<p>I just created 3 messages with the <strong>Django Admin</strong> so the <strong>next</strong> parameter is null. </p>
<p>By default we set the Django rest pagination at 20 items in our <strong>settings.py</strong> backend file: </p>
</blockquote>
<pre><code class="language-python">REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
   'rest_framework_simplejwt.authentication.JWTAuthentication',
),
'DEFAULT_FILTER_BACKENDS': (
    'django_filters.rest_framework.DjangoFilterBackend',
    'rest_framework.filters.OrderingFilter',
),
'DEFAULT_RENDERER_CLASSES': (
    'rest_framework.renderers.JSONRenderer',
    'rest_framework.renderers.BrowsableAPIRenderer',
),
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 20,
}</code></pre>
<p>Let&#8217;s modify our <strong>api-service.service.ts</strong> file to add the message API:</p>
<pre><code class="language-typescript"> getMessage(refChat,page) {
    const options = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json'
      })
    };
    let url = this.getMessageUrl+"?refChat="+refChat+"&amp;page="+page
    return Observable.create(observer =&gt; {
      this.http.get(url, options)
        .pipe(retry(1))
        .subscribe(res =&gt; {
          this.networkConnected = true
          observer.next(res);
          observer.complete();
        }, error =&gt; {
          observer.next(false);
          observer.complete();
          console.log(error);// Error getting the data
        });
    });
  }</code></pre>
<p>and don&#8217;t forget to declare the url:</p>
<pre><code class="language-typescript">getMessageUrl : string = ''</code></pre>
<p>and </p>
<pre><code class="language-typescript">private initUrls() {
    // ================ AUTHENTIFICATION METHODS ====================
    this.getLoginUrl = this.virtualHostName + "auth/jwt/create/"
    this.getCreateUserUrl = this.virtualHostName + "auth/users/"
    this.getResetPwdUrl = this.virtualHostName + "auth/users/reset_password/"
    this.getRefreshTokenUrl = this.virtualHostName + "auth/jwt/refresh/"
    this.getMeUrl = this.virtualHostName + "auth/users/me/"
    // =================================================================

    this.getUserUrl = this.virtualHostName + this.apiPrefix + "/user/"
    this.getSearchUserUrl = this.virtualHostName + this.apiPrefix + "/searchUser/"
    this.getCreateChatUrl = this.virtualHostName + this.apiPrefix + "/createChat/"
    this.getChatUrl = this.virtualHostName + this.apiPrefix + "/chat/"
    this.getMessageUrl = this.virtualHostName + this.apiPrefix + "/message/"
  }</code></pre>
<p>Then in our <strong>chat-page.page.ts</strong> file:</p>
<div class="page-break"></div>
<pre><code class="language-typescript"> loadChat(page){
    if (this.apiService.networkConnected){
      this.apiService.showLoading().then(()=&gt;{
        this.apiService.getMessage(this.chat.id,page).subscribe((list)=&gt;{
          this.apiService.stopLoading()
          if (list) {
            let count = list["count"]
            if (count &gt; 0) {
              //Iterate existing message
              for (let aMessage of list["results"]) {
                let theMessage = new Message().initWithJSON(aMessage)
                console.log(aMessage)
                this.messages.push(theMessage)
              }

            }
          }
        })
      })
    }
    else{
      this.apiService.showNoNetwork()
    }
  }</code></pre>
<p>The <strong>page</strong> parameter will be used for the pagination. We can call this method once we had get and set the <strong>chat</strong> variable :</p>
<pre><code class="language-typescript">export class ChatPagePage implements OnInit {

  chat : Chat;
  username : string =''
  myUser : User
  otherUser : User
  messages = [

  ]
  messageToSend = ""
  currentPage=1 

  constructor(public apiService:ApiserviceService,
    public userManager:UserManagerServiceService,
    public router: Router,
    public route:ActivatedRoute) { 

    this.route.queryParams.subscribe(params =&gt; {
      if (this.router.getCurrentNavigation().extras.state) {
        this.chat = this.router.getCurrentNavigation().extras.state.chat;
        console.log(this.chat)
        if (this.chat.fromUser.id==this.userManager.currentUser.id){
          this.myUser = this.chat.fromUser
          this.otherUser = this.chat.toUser
        }
        else{
          this.myUser = this.chat.toUser
          this.otherUser = this.chat.fromUser
        }
        this.username = this.otherUser.first_name+" "+this.otherUser.last_name
        this.messages = []
        this.loadChat(this.currentPage)
      }
      else{
        //Return home
        this.router.navigateByUrl("/home")
      }
    });
  }</code></pre>
<div class="page-break"></div>
<p>and we can adapt our <strong>chat-page.page.html</strong> file to our new <strong>message json</strong>:</p>
<pre><code class="language-html">    &lt;ion-list&gt;  
    &lt;div *ngFor="let message of messages"&gt;
        &lt;div class="chat"&gt;
      &lt;div [class]="message.author=== myUser.id ? 'messageFromMe' : 'messageFromOther'"&gt;
        &lt;span&gt;
          {{message.message}}
        &lt;/span&gt;
        &lt;span class="timestamp"&gt;
          {{ message.createdAt | date:'HH:mm' }}
          &lt;ion-icon name="checkmark-done-circle-outline" *ngIf="message.author === myUser.id &amp;&amp; message.isRead"&gt;&lt;/ion-icon&gt;
          &lt;ion-icon name="ellipse-outline" *ngIf="message.author === myUser.id &amp;&amp; !message.isRead"&gt;&lt;/ion-icon&gt;
        &lt;/span&gt;
      &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;/ion-list&gt;</code></pre>
<p>The page displayed will look like this: </p>
<p><img src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/08/Capture-décran-2021-08-11-à-19.27.01-e1628702862937.png" alt="ChatListReverse" /></p>
<p>As we can see the messages are not in the correct order in the view. This is because of our <strong>ordering</strong> in our API. The <strong>trick</strong> will be to reverse the element of the list with the <strong>reverse()</strong> method of an array. Updated code is :</p>
<pre><code class="language-typescript">loadChat(page){
    if (this.apiService.networkConnected){
      this.apiService.showLoading().then(()=&gt;{
        this.apiService.getMessage(this.chat.id,page).subscribe((list)=&gt;{
          this.apiService.stopLoading()
          if (list) {
            let count = list["count"]
            if (count &gt; 0) {
              // Need to reverse values of API message list (oldest to recent)
              let arrayToReverse = list["results"]
              let finalListe =arrayToReverse.reverse()
              //Iterate existing message
              for (let aMessage of finalListe) {
                let theMessage = new Message().initWithJSON(aMessage)
                console.log(aMessage)
                this.messages.push(theMessage)
              }
            }
          }
        })
      })
    }
    else{
      this.apiService.showNoNetwork()
    }
  }</code></pre>
<div class="page-break"></div>
<p>Now result will be better:</p>
<p><img src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/08/Capture-décran-2021-08-11-à-19.33.05-e1628703210860.png" alt="OrderedMessage" /></p>
<h2>Dealing with pagination</h2>
<p>Because we will need lot more messages, we can create a <strong>python</strong> script (on backend) to create <strong>fake messages</strong> for us, let&#8217;s call it <strong>createFakeMessages.py</strong></p>
<pre><code class="language-python">import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "chattuto.settings")
import django
django.setup()
from faker import factory, Faker
from chat.models import Chat,Message
from model_bakery.recipe import Recipe, foreign_key

fake = Faker()
for i in range(100):
    from random import randrange
    import time
    from datetime import datetime
    randomNumber = randrange(10)
    message = Message()
    message.refChat_id="66fdac13-79ff-4d0f-a0f2-7001708f51d5"
    print(randomNumber)
    if randomNumber&lt;=5:
        #myself
        message.author_id="3cde3f7e-261e-4ebb-8437-fa4c27d35bf0"
    else:
        #the other user
        message.author_id = "fb261053-d336-4a9b-9bcc-d206ac4b7753"
    message.isRead=False
    now = datetime.now()  # current date and time
    # Add to texte for better debug purpose
    message.message = fake.sentence(nb_words=10)+":"+now.strftime("%H:%M:%S")
    message.save()
    # sleep a little to have different createdAt
    time.sleep(1)</code></pre>
<blockquote>
<p>You need to adapt this script with your own <strong>refChat_id</strong></p>
<p>To have the author of the message being myself or the other user, i use a random number and based on value between 0 and 10, i set the <strong>author_id</strong> with my own <strong>user_id</strong> or with the <strong>other_user_id</strong></p>
<p>To generate a message i use <strong>fake.sentence</strong> and add it the current time, which will help us to visualize more easily on chat screen, if messages are order properly. </p>
<p>Finally i sleep 1 seconde between each message generation to avoid having the same <strong>createdAt</strong> value</p>
<p>You can get the <strong>ids</strong> using the <strong>Django admin</strong>. </p>
</blockquote>
<p>Run the script with:</p>
<pre><code class="language-shell">python createFakeMessage.py</code></pre>
<div class="page-break"></div>
<p>Once the messages are generated, we can try again our application. Our chat messages list will be displayed and the <strong>next</strong> parameter of the <strong>JSON reponse</strong> will be set with the next url to get other messages.</p>
<pre><code class="language-json">{
  count: 103, 
  next: "http://127.0.0.1:8000/api/message/?page=2&amp;refChat=66fdac13-79ff-4d0f-a0f2-7001708f51d5",
  previous: null,
  results: [ ... ]
}</code></pre>
<p>Now that we have lot of messages, we need to scroll to see the last message in the list. We need to change that behaviour to automatically scroll to the bottom of the list.</p>
<p>In our <strong>html</strong>, we tagged the <strong>ion-content</strong> with the tag <strong>#content</strong></p>
<pre><code class="language-html">&lt;ion-content padding #content [scrollEvents]="true"  (ionScroll)="checkScrolling($event)"&gt;</code></pre>
<p>In our typescript, like with our <strong>searchBar</strong>, we can access with the <strong>ViewChild</strong> instruction:</p>
<pre><code class="language-typescript"> @ViewChild('content', { static: true }) content;</code></pre>
<p>and then after getting the messages list, we can scroll to the bottom like this: </p>
<pre><code class="language-typescript"> //Iterate existing message
let arrayToReverse = list["results"]
let finalListe =arrayToReverse.reverse()
for (let aMessage of finalListe) {
  let theMessage = new Message().initWithJSON(aMessage)
  this.messages.push(theMessage)
}
//Move to bottom
this.content.scrollToBottom(1000);</code></pre>
<p>If user scrolls to the top, it means he wants to access previous historic messages, which means we need to load the previous messages from the backend (using the <strong>next</strong> url), then we need to add the messages to our chat list. </p>
<p>To check that the user is scrolling, i subscribed to <strong>ionScroll</strong> events, which call a method <strong>checkScrolling</strong>:</p>
<pre><code class="language-html">&lt;ion-content padding #content [scrollEvents]="true"  (ionScroll)="checkScrolling($event)"&gt;</code></pre>
<p>Let&#8217;s write the method:</p>
<pre><code class="language-typescript">  checkScrolling($event){
    if ($event.detail.scrollTop === 0) {
      //should load previous
      if (this.hasPrevious &amp;&amp; !this.loadingPrevious){
        this.currentPage+=1
         this.loadChat(this.currentPage,true)
      }
      else{
        //No more previous
      }
    }
  }</code></pre>
<ol>
<li>First we check if the user has scrolled to the top of the list:  <strong>if ($event.detail.scrollTop === 0)</strong></li>
<li>Then if we have previous messages to load (based on the <strong>next</strong> variable) and not already loading the messages, we increment our <strong>currentPage</strong> variable and call again the <strong>loadChat</strong> method which has been modified to pass a second argument to know that the call is from this <strong>checkScrolling</strong> method.</li>
</ol>
<p>So we need to modify our <strong>chat-page.page.ts</strong> file and declare these new variables:</p>
<pre><code class="language-typescript"> hasPrevious = false
 loadingPrevious = false</code></pre>
<div class="page-break"></div>
<p>Then modify our <strong>loadChat</strong> method:</p>
<pre><code class="language-typescript">loadChat(page,fromScrolling){
    if (this.apiService.networkConnected){

        this.loadingPrevious = true
        this.apiService.getMessage(this.chat.id,page).subscribe((list)=&gt;{
          this.loadingPrevious = false
          this.apiService.stopLoading()
          if (list) {
            console.log(list)
            let count = list["count"]
            let next = list["next"]
            if (next){
              this.hasPrevious=true
            }
            else{
              this.hasPrevious=false
            }
            if (count &gt; 0) {
              //Iterate existing message
              let arrayToReverse = list["results"]
              let finalListe =arrayToReverse.reverse()
              let newList = []
              for (let aMessage of finalListe) {
                let theMessage = new Message().initWithJSON(aMessage)
                newList.push(theMessage)
              }
              //concat newList and existings messages 
              let prov = newList.concat(this.messages)
              this.messages = prov
              this.content.scrollToBottom(1000);
            }
          }

      })
    }
    else{
      this.apiService.showNoNetwork()
    }</code></pre>
<ol>
<li>Before loading messages from backend we set the <strong>this.loadingPrevious = true</strong></li>
<li>Once we have the results, we set the <strong>this.loadingPrevious = false</strong> </li>
<li>Now that we have a list of previous messages, we need to reverse the list as we have done before.</li>
<li>Then the final list of messages to display will be : the previous messages (just loaded) then the existing messages (that we have already fetched). </li>
</ol>
<pre><code class="language-typescript"> let finalListe =arrayToReverse.reverse()
  let newList = []
  for (let aMessage of finalListe) {
      let theMessage = new Message().initWithJSON(aMessage)
      newList.push(theMessage)
  }
  //concat newList and existings messages 
  let prov = newList.concat(this.messages)
  this.messages = prov</code></pre>
<p>But now we have a problem with the <strong>this.content.scrollToBottom(1000);</strong> instruction. Because we fetched previous messages, constructed a new list of messages and then we scroll to the bottom of the chat, meaning that the user will see again the messages he already saw and he will need to scroll on top again&#8230; </p>
<p>This is not acceptable. We need to find the last message he saw before loading the new previous messages list and then scroll again to this last seen message:</p>
<pre><code class="language-typescript"> if (!fromScrolling){
    //Move to bottom
    this.content.scrollToBottom(1000);
  }
  else{
        //Need to scroll to last viewed message, wait for list refresh 100ms
        setTimeout(()=&gt;{
            let listArray = this.list.nativeElement.children
            let itemToScroll = listArray[20]
            itemToScroll.scrollIntoView({behavior:'instant',block:'end'})
        },100)
  }</code></pre>
<ol>
<li>
<p>If the <strong>loadChat</strong> method is not called from the scrolling method, we can jump to the bottom of the list</p>
<div class="page-break"></div>
</li>
<li>Otherwise we know that we just fetched some previous messages, we wait a little (100ms) that the <strong>ion-list</strong> refresh on the screen, then we get the elements of that list using the <strong>this.list.nativeElement.children</strong> instruction. We can get this <strong>list</strong> variable like this:</li>
</ol>
<pre><code class="language-typescript">@ViewChild(IonList, { read:ElementRef }) list:ElementRef;</code></pre>
<ol>
<li>And then we scroll immediatly to last seen message:</li>
</ol>
<pre><code class="language-typescript">let itemToScroll = listArray[20]
itemToScroll.scrollIntoView({behavior:'instant',block:'end'})</code></pre>
<blockquote>
<p>The trick is to use our pagination number. We know that we fetched messages 20 by 20 (our <strong>Django rest framework</strong> pagination). We also know that new loaded messages will be in first position on our list (from 0 to 19), so the last seen message is in position 20 (our pagination number) ! </p>
<p>Now to be honest the user experience is not optimal while scrolling to the top, because we need to fetch messages using network. To improve the experience, we should save message history on local cache and then use it immediatly, meaning we could set the list once and jump directly to the end of the list. </p>
</blockquote>
<p>Here is the final code of the <strong>chat-page.page.ts</strong> file:</p>
<pre><code class="language-typescript">import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { IonList } from '@ionic/angular';
import { from } from 'rxjs';
import { Chat } from 'src/app/models/chat';
import { Message } from 'src/app/models/message';
import { User } from 'src/app/models/user';
import { ApiserviceService } from 'src/app/services/api-service.service';
import { UserManagerServiceService } from 'src/app/services/user-manager-service.service';

@Component({
  selector: 'app-chat-page',
  templateUrl: './chat-page.page.html',
  styleUrls: ['./chat-page.page.scss'],
})
export class ChatPagePage implements OnInit {
  @ViewChild('content', { static: true }) content;
  @ViewChild(IonList, { read:ElementRef }) list:ElementRef;

  chat : Chat;
  username : string =''
  myUser : User
  otherUser : User
  messages = [

  ]
  messageToSend = ""
  currentPage=1 
  hasPrevious = false
  loadingPrevious = false

  constructor(public apiService:ApiserviceService,
    public userManager:UserManagerServiceService,
    public router: Router,
    public route:ActivatedRoute) { 

    this.route.queryParams.subscribe(params =&gt; {
      if (this.router.getCurrentNavigation().extras.state) {
        this.chat = this.router.getCurrentNavigation().extras.state.chat;
        console.log(this.chat)
        if (this.chat.fromUser.id==this.userManager.currentUser.id){
          this.myUser = this.chat.fromUser
          this.otherUser = this.chat.toUser
        }
        else{
          this.myUser = this.chat.toUser
          this.otherUser = this.chat.fromUser
        }
        this.username = this.otherUser.first_name+" "+this.otherUser.last_name
        this.messages = []
        this.loadChat(this.currentPage,false)
      }
      else{
        //Return home
        this.router.navigateByUrl("/home")
      }
    });
  }

  ngOnInit() {
  }

  loadChat(page,fromScrolling){
    if (this.apiService.networkConnected){

        this.loadingPrevious = true
        this.apiService.getMessage(this.chat.id,page).subscribe((list)=&gt;{
          this.loadingPrevious = false
          this.apiService.stopLoading()
          if (list) {
            console.log(list)
            let count = list["count"]
            let next = list["next"]
            if (next){
              this.hasPrevious=true
            }
            else{
              this.hasPrevious=false
            }
            if (count &gt; 0) {
              //Iterate existing message
              let arrayToReverse = list["results"]
              let finalListe =arrayToReverse.reverse()
              let newList = []
              for (let aMessage of finalListe) {
                let theMessage = new Message().initWithJSON(aMessage)
                newList.push(theMessage)
              }
              //concat newList and existings messages 
              let prov = newList.concat(this.messages)
              this.messages = prov
              if (!fromScrolling){
                  //Move to bottom
              this.content.scrollToBottom(1000);
              }
              else{
                //Need to scroll to last viewed message, wait for list refresh 100ms
                setTimeout(()=&gt;{
                  let listArray = this.list.nativeElement.children
                  let itemToScroll = listArray[20]
                  itemToScroll.scrollIntoView({behavior:'instant',block:'end'})
                },100)
              }
            }
          }

      })
    }
    else{
      this.apiService.showNoNetwork()
    }
  }

  checkScrolling($event){
    if ($event.detail.scrollTop === 0) {
      //should load previous
      if (this.hasPrevious &amp;&amp; !this.loadingPrevious){
        this.currentPage+=1
         this.loadChat(this.currentPage,true)
      }
      else{
        //No more previous
      }
    }
  }

}</code></pre>
<p>There is only one missing feature to our <strong>list of messages</strong>. We need to display the date of the messages when it is different:</p>
<pre><code class="language-html">    &lt;ion-list&gt;  
    &lt;div *ngFor="let message of messages; let i = index"&gt;
        &lt;div class="chat"&gt;
      &lt;ion-row class="dateMessage" *ngIf="isDifferentDay(i)"&gt;
        &lt;ion-badge&gt;{{getMessageDate(i)}}&lt;/ion-badge&gt;
      &lt;/ion-row&gt;
      &lt;div [class]="message.author=== myUser.id ? 'messageFromMe' : 'messageFromOther'"&gt;
        &lt;span&gt;
          {{message.message}}
        &lt;/span&gt;
        &lt;span class="timestamp"&gt;
          {{ message.createdAt | date:'HH:mm' }}
          &lt;ion-icon name="checkmark-done-circle-outline" *ngIf="message.author === myUser.id &amp;&amp; message.isRead"&gt;&lt;/ion-icon&gt;
          &lt;ion-icon name="ellipse-outline" *ngIf="message.author === myUser.id &amp;&amp; !message.isRead"&gt;&lt;/ion-icon&gt;
        &lt;/span&gt;
      &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;/ion-list&gt;</code></pre>
<p>We add the <strong>dateMessage</strong> class to our <strong>chat-page.page.scss</strong> file:</p>
<pre><code class="language-scss">.dateMessage {
    display: flex;
    flex-direction: column;
    align-self: center;
    margin-bottom: 10px;
    margin-top: 10px;
}</code></pre>
<p>and in our typescript:</p>
<pre><code class="language-typescript"> isDifferentDay(messageIndex: number): boolean {

    if (messageIndex === 0) return true;

    const d1 = new Date(this.messages[messageIndex - 1].createdAt);
    const d2 = new Date(this.messages[messageIndex].createdAt);

    return d1.getFullYear() !== d2.getFullYear()
      || d1.getMonth() !== d2.getMonth()
      || d1.getDate() !== d2.getDate();
}

 getMessageDate(messageIndex: number): string {

   const wholeDate = new Date(this.messages[messageIndex].createdAt).toDateString();

   this.dateMessage = wholeDate.slice(0, wholeDate.length - 5);

   return this.dateMessage;

 }</code></pre>
<ol>
<li>For each item of the list, we added an index</li>
<li>Then we add a <strong>ion-row</strong> which is displayed only if the date changes.</li>
</ol>
<p>And voila :</p>
<p><img src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/08/Capture-décran-2021-08-12-à-11.32.36-e1628760797628.png" alt="ChatDate" /></p>
<div class="page-break"></div>
<h2>Managing isRead message status</h2>
<p>To know if a message has been read, we will just suppose that it has been displayed in the chat to the concerned user. We can add :</p>
<pre><code class="language-typescript">for (let aMessage of finalListe) {
  let theMessage = new Message().initWithJSON(aMessage)
  if (theMessage.author!=this.myUser.id &amp;&amp; theMessage.isRead==false){
    // I just read the message need to change the isRead to true

  }
  newList.push(theMessage)
}</code></pre>
<p>And then we could use our <strong>API message</strong> to update the status of the message. But this will mean that if we have 20 messages unread (sent by the other user), we will do 20 Http query requests to update each messages !<br />
This is a serious performance issue !</p>
<p>Instead we will do only <strong>ONE</strong> request with all the IDs to update. And to manage the list we will do: </p>
<pre><code class="language-typescript">let messagesIdToUpdate = []
for (let aMessage of finalListe) {
  let theMessage = new Message().initWithJSON(aMessage)
  if (theMessage.author!=this.myUser.id &amp;&amp; theMessage.isRead==false){
      // I just read the message need to change the isRead to true
      messagesIdToUpdate.push(theMessage.id)
  }
  newList.push(theMessage)
}
if (messagesIdToUpdate.length&gt;0){
  this.updateReadStatusForMessages(messagesIdToUpdate)
}</code></pre>
<p>So now we need to write an <strong>API</strong> that will get this list of IDs and will update with only one query all messages.<br />
First we will add the new route in the <strong>api/urls.py</strong> file : </p>
<pre><code class="language-python">url(r'^updateMessagesStatus/$', UpdateMessagesStatusView.as_view()),</code></pre>
<p>and will create the view in the <strong>api/views.py</strong> file:</p>
<pre><code class="language-python">class UpdateMessagesStatusView(APIView):
    def post(self, request, format=None):
        try:
            ids = request.data["messageIds"]
            Message.objects.filter(id__in=ids).update(isRead=True)
            newdict = {"status": "OK"}
            return JsonResponse(newdict)
        except Exception as e:
            logger.error(e)
            newdict = {"status": "KO"}
            return JsonResponse(newdict)</code></pre>
<p>Now we add this method in our <strong>api-service.service.ts</strong> file (don&#8217;t forget to declare the <strong>getUpdateMessagesStatusUrl</strong>):</p>
<pre><code class="language-typescript">updateMessageStatus(listOfIds) {
    const options = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json'
      })
    };

    let params = {
      "messageIds":listOfIds
    }

    return Observable.create(observer =&gt; {
      this.http.post(this.getUpdateMessagesStatusUrl, params, options)
        .pipe(retry(1))
        .subscribe(res =&gt; {
          this.networkConnected = true
          observer.next(res);
          observer.complete();
        }, error =&gt; {
          observer.next(false);
          observer.complete();
          console.log(error);// Error getting the data
        });
    });
  }</code></pre>
<div class="page-break"></div>
<p>and in our <strong>chat-page.page.ts</strong> file we can call this method:</p>
<pre><code class="language-typescript"> updateReadStatusForMessages(listOfIds){
    if (this.apiService.networkConnected){
      this.apiService.updateMessageStatus(listOfIds).subscribe((done)=&gt;{
        console.log(done)
      })
    }
  }</code></pre>
<blockquote>
<p>This is a huge performance improvment with simple code !</p>
</blockquote>
<h2>Chat with a user</h2>
<p>It&#8217;s time to dive into the next big subject : sending real time messages to the other user using <strong>websocket</strong> !</p>
<p>If you remember on <strong>day two</strong> we learned how to implement a <strong>chat</strong> in <strong>Django</strong> using <strong>Django channels</strong>. The idea and process will be the same but from a <strong>Ionic application</strong> perspective !</p>
<p>Meaning we need to learn how to : </p>
<ol>
<li>Managing websockets with Ionic </li>
<li>Websockets chat events listener</li>
<li>Send / receive text messages</li>
</ol>
<p>First we will create a new <strong>service</strong> to manage websockets:</p>
<pre><code class="language-shell">ionic g service WebSocketService
mv src/app/web-socket-service.service.* src/app/services </code></pre>
<p>And will edit the <strong>web-socket-service.service.ts</strong> with the code:</p>
<pre><code class="language-typescript">import { Injectable } from '@angular/core';
import { webSocket, WebSocketSubject } from "rxjs/webSocket";
import { BehaviorSubject, EMPTY, Observable, Subject, timer } from 'rxjs';
import { ApiserviceService } from './api-service.service';

@Injectable({
  providedIn: 'root'
})

export class WebSocketServiceService {
  RECONNECT_INTERVAL = 5000 // 5 seconds

  public messageReceived: BehaviorSubject&lt;any&gt; ;
  constructor(public apiService:ApiserviceService) { }

  myWebSocket: WebSocketSubject&lt;any&gt;;
  RETRY_SECONDS = 10; 
  isConnected = true;

  processMessage(msg){
    console.log("=== Publish message received"+JSON.stringify(msg))
    this.messageReceived.next(msg)
  }

  connect(chat) {
    return new Promise(resolve =&gt; {
      let url = "ws://127.0.0.1:8000/ws/chat/"+chat.id+"/"
      try {
        this.isConnected=true
        this.myWebSocket  = webSocket(url)
        this.messageReceived = new BehaviorSubject&lt;any&gt;(null);
           // Called whenever there is a message from the server    
        this.myWebSocket.subscribe(    
          msg =&gt; {
            console.log('message received: ' + JSON.stringify(msg))
            this.processMessage(msg["message"])
          }, 
          err =&gt;{
            this.isConnected=false;
            this.apiService.showError("Unable to connect to chat")
            console.log(err)
            }, 
          // Called if WebSocket API signals some kind of error    
          () =&gt; {
            console.log('complete') 
            this.isConnected=false;
        }
          // Called when connection is closed (for whatever reason)  
       );
      resolve(true)

      } catch (error) {
        console.log(error)
        resolve(false)
     }
    })
  }

  sendMessage(params){
    this.myWebSocket.next(params);
  }

  close() {
    this.messageReceived.unsubscribe()
    this.myWebSocket.complete(); 
  }

} </code></pre>
<p>The <strong>WebSocketService</strong> will have methods to :</p>
<ol>
<li>connect</li>
<li>sendMessage to the chat</li>
<li>process a received message</li>
<li>disconnect</li>
</ol>
<p>The variable <strong>public messageReceived: BehaviorSubject<any> ;</strong> is an Angular behaviour subject. This means other code can subscribe to this variable and receive events from it. We will use this to push incoming new messages from the chat.</p>
<p>To connect to a websocket with *Ionic**, it is really simple : </p>
<pre><code class="language-typescript"> this.myWebSocket  = webSocket(url)</code></pre>
<p>In day two tutorial, we define the url of our <strong>chat room</strong> to be : <strong>&#8216;ws/chat/(?P<room_name>[-a-zA-Z0-9_]+)/&#8217;</strong>, where <strong>room_name</strong> is the identifier of the chat between the two users. </p>
<p>This is why we hardcoded the url values like this:</p>
<pre><code class="language-typescript">  let url = "ws://127.0.0.1:8000/ws/chat/"+chat.id+"/"</code></pre>
<p>To listen incoming events to our created socket, we can do like this:</p>
<pre><code class="language-typescript"> this.myWebSocket.subscribe(    
      msg =&gt; {
            console.log('message received: ' + JSON.stringify(msg))
            this.processMessage(msg["message"])
      }, 
      err =&gt;{
            this.isConnected=false;
            this.apiService.showError("Unable to connect to chat")
            console.log(err)
      }, 
      // Called if WebSocket API signals some kind of error    
      () =&gt; {
            console.log('complete') 
            this.isConnected=false;
      }
);</code></pre>
<p>If a message is received, we called the <strong>processMessage</strong> method with the message, which will be transmitted to <strong>messageReceived</strong> subscribers: </p>
<pre><code class="language-typescript">this.messageReceived.next(msg)</code></pre>
<p>To send a message thru the websocket (aka to our Django backend) we can use:</p>
<pre><code class="language-typescript">sendMessage(params){
    this.myWebSocket.next(params);
}</code></pre>
<div class="page-break"></div>
<p>So we can use this <strong>WebSocketService</strong> in our <strong>ChatPage</strong> to subscribe to messages received or to send message to the chat (aka the other user). Let&#8217;s add it to our <strong>chat-page.page.ts</strong> constructor method, and then once we have set our <strong>chat</strong> variable, we can connect to the <strong>websocket</strong>:</p>
<pre><code class="language-typescript"> constructor(public apiService:ApiserviceService,
    public userManager:UserManagerServiceService,
    public router: Router,
    public route:ActivatedRoute,
    private webSocketService:WebSocketServiceService) { 

    this.route.queryParams.subscribe(params =&gt; {
      if (this.router.getCurrentNavigation().extras.state) {
        this.chat = this.router.getCurrentNavigation().extras.state.chat;
        // can connect to the chat websocket 
        this.webSocketService.connect(this.chat)

        if (this.chat.fromUser.id==this.userManager.currentUser.id){
          this.myUser = this.chat.fromUser
          this.otherUser = this.chat.toUser
        }
        else{
          this.myUser = this.chat.toUser
          this.otherUser = this.chat.fromUser
        }
        this.username = this.otherUser.first_name+" "+this.otherUser.last_name
        this.messages = []
        this.loadChat(this.currentPage,false)
      }
      else{
        //Return home
        this.router.navigateByUrl("/home")
      }
    });
  }</code></pre>
<p>To manage the disconnect, we will implement the <strong>ngOnDestroy</strong> method:</p>
<pre><code class="language-typescript">export class ChatPagePage implements OnInit,OnDestroy {</code></pre>
<p>which is called automatically when a view is destroyed in memory, and will close the websocket connection:</p>
<pre><code class="language-typescript">ngOnDestroy(){
    console.log("=== Disconnecting websocket ====")
    this.webSocketService.close()
}</code></pre>
<p>Before subscribing to incoming message, we need to check that the socket is connected:</p>
<pre><code class="language-typescript"> // can connect to the chat websocket 
this.webSocketService.connect(this.chat)
if (this.webSocketService.isConnected){
  this.webSocketService.messageReceived.subscribe((message)=&gt;{
     if (message){
              console.log("WS Received message ")
              console.log(message)
      }
  })
}</code></pre>
<p>To send a message, we declared a <strong>sendMessage()</strong> method into our html. We can now implement it:</p>
<pre><code class="language-typescript">sendMessage(){
    if (this.webSocketService.isConnected){
       //create the expected json message
      let newMessage = {
        "author" : this.myUser.id,
        "refChat" : this.chat.id,
        "message" : this.messageToSend,
        "type" : 0,
        "extraData": ""
      }
      this.webSocketService.sendMessage(newMessage)
    }
  }</code></pre>
<blockquote>
<p><strong>messageToSend</strong> is the text the user is typing into it&#8217;s chat input box. </p>
<div class="page-break"></div>
<p>We need to construct the JSON that our server is expecting as we have seen in tutorial day two:</p>
<pre><code class="language-json">{
refChat : refChat
author : refUser
message : message
type : 0
extraData : ""
}</code></pre>
</blockquote>
<p>Now you can try again the application and try our chat page, you should see the message: <strong>Unable to connect to chat</strong>.</p>
<p>This is because we declared in our backend <strong>ChatConsumer</strong> method, that the user should be authentificated:</p>
<pre><code class="language-python">class ChatConsumer(WebsocketConsumer):
    def connect(self):
        try:
            self.room_name = self.scope['url_route']['kwargs']['room_name']
            chat = Chat.objects.get(id=self.room_name)
            self.room_group_name = 'chat_%s' % str(chat.id)
            # Join room group
            async_to_sync(self.channel_layer.group_add)(
                self.room_group_name,
                self.channel_name
            )

            self.accept()
        except Chat.DoesNotExist as e:
            print(e)
            return</code></pre>
<p>Unfortunatly it seems impossible to specify custom headers when opening a websocket connection neither with RxJS nor with plain JavaScript / DOM API. So we need to remove this check:</p>
<pre><code class="language-python">class ChatConsumer(WebsocketConsumer):
    def connect(self):
        try:
            user = self.scope['user']
            print("=== user %s" % user)
            if str(user)=="AnonymousUser":
                print("==Not authorized")
                return
            self.room_name = self.scope['url_route']['kwargs']['room_name']
            chat = Chat.objects.get(id=self.room_name)
            self.room_group_name = 'chat_%s' % str(chat.id)
            # Join room group
            async_to_sync(self.channel_layer.group_add)(
                self.room_group_name,
                self.channel_name
            )

            self.accept()
        except Chat.DoesNotExist as e:
            print(e)
            return</code></pre>
<p>Now you should be able to go to the <strong>ChatPage</strong> without websocket issue. </p>
<div class="page-break"></div>
<p>To test the websocket, first we can use the page that we developped in tutorial <strong>day two</strong>:</p>
<p><a href="http://127.0.0.1:8000/chat/66fdac13-79ff-4d0f-a0f2-7001708f51d5/">http://127.0.0.1:8000/chat/66fdac13-79ff-4d0f-a0f2-7001708f51d5/</a></p>
<p><img src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/08/Capture-décran-2021-08-12-à-14.44.45-e1628772339538.png" alt="wbchat" /></p>
<p>We just need to setup the correct <strong>chat id</strong> in the url. We can get this <strong>chat id</strong> from our javascript console log of our <strong>Ionic application</strong>. And then if we sent a message from the web interface, we can check that this message is received from our <strong>Ionic application</strong> console log:</p>
<pre><code class="language-javascript">=== Publish message received"message sent from web interface"
chat-page.page.ts:49 WS Received message 
chat-page.page.ts:50 message sent from web interface</code></pre>
<p>We can also verify that if we go back to our <strong>HomePage</strong>, the websocket will be closed by our <strong>ngDestroy</strong> method:</p>
<pre><code class="language-javascript">=== Disconnecting websocket ====</code></pre>
<p>As final step, we need to test sending messages from our <strong>Ionic application</strong> to another user on <strong>Ionic application</strong> too. So you will need to create/register another user, and test with two users (meaning two devices) in parallel. Like if you are simulating a chat. </p>
<p>You can do this by testing on a simulator (ios or android) and on your browser (Opera, firefox,&#8230;) or you can use two different browser such as Opera and Chrome or Safari and firefox , or &#8230; You got the point&#8230;<br />
On one device this will be your first user and on the second device, this will be your second user. </p>
<blockquote>
<p>If you are not sure to understand, please refer to this screenshot:<br />
<img src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/08/Capture-décran-2021-08-12-à-15.16.40.png" alt="Example" /></p>
<p>I have registered a <strong>User1</strong> and <strong>User2</strong> for demo purpose. Then i created a chat between the two users</p>
<div class="page-break"></div>
</blockquote>
<p><img src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/08/Capture-décran-2021-08-12-à-15.20.33-e1628774473595.png" alt="ChatBetweenUsers" /></p>
<p>And now if you write a message in <strong>User1</strong> and click to send the message, you should see in your javascript console log (of both users aka browsers), the message sent:</p>
<pre><code class="language-javascript">WS Received message 
chat-page.page.ts:50 Hi Julien, how are you ? </code></pre>
<p>Using the <a href="http://127.0.0.1:8000/admin/chat/message/"><strong>Django Admin</strong></a> you should be able to see the message, meaning it has been saved correctly in the database. </p>
<p><strong>Great!!!</strong> our websocket is working perfectly between users and now we have to fix some code. </p>
<ol>
<li>When sending a message, we can reset the input box value:</li>
</ol>
<pre><code class="language-typescript"> sendMessage(){
    if (this.webSocketService.isConnected){
      //create the expected json message
      let newMessage = {
        "author" : this.myUser.id,
        "refChat" : this.chat.id,
        "message" : this.messageToSend,
        "type" : 0,
        "extraData": ""
      }
      this.webSocketService.sendMessage(newMessage)
      this.messageToSend=""
    }
  }</code></pre>
<ol>
<li>Right now we received the message as a <strong>string</strong> but we need a <strong>Message</strong> object. To do so, we just need to update our <strong>consumers.py</strong> file and pass the <strong>JSON</strong> received instead of the text message:</li>
</ol>
<pre><code class="language-python">    def receive(self, text_data):
        data_json = json.loads(text_data)
        print("===Received")
        print(data_json)
        user = self.scope['user']
        print("=== user %s"%user)
        message = data_json['message']
        # save message to database
        self.new_message(data_json)
        # Send JSON to room group
        async_to_sync(self.channel_layer.group_send)(
            self.room_group_name,
            {
                'type': 'chat_message',
                'message': data_json
            }
        )</code></pre>
<div class="page-break"></div>
<ol>
<li>
<p>Now we can modify our <strong>webSocket.messageReceived</strong> event to create a <strong>Message</strong> object from the JSON, and add it to the list of messages:</p>
<pre><code class="language-typescript">this.webSocketService.messageReceived.subscribe((message)=&gt;{
if (message){
  console.log("WS Received message ")
  console.log(message)
  let theMessage = new Message().initWithJSON(message)
  theMessage.createdAt = new Date()
  this.messages.push(theMessage)
  // scroll to bottom to see the message
  this.content.scrollToBottom(1000);
}
})</code></pre>
<p>We also initialize the <strong>createdAt</strong> to the current date, because the JSON doesn&#8217;t contains this information. </p>
</li>
<li>We can also set the <strong>isRead</strong> to True if the message received not coming from our own user:</li>
</ol>
<pre><code class="language-typescript">this.webSocketService.messageReceived.subscribe((message)=&gt;{
    if (message){
      console.log("WS Received message ")
      console.log(message)
      let theMessage = new Message().initWithJSON(message)
      theMessage.createdAt = new Date()
      this.messages.push(theMessage)
      // scroll to bottom to see the message
      this.content.scrollToBottom(1000);
      let messagesIdToUpdate = []
      if (theMessage.author!=this.myUser.id &amp;&amp; theMessage.isRead==false){
         // I just read the message need to change the isRead to true
         messagesIdToUpdate.push(theMessage.id)
         this.updateReadStatusForMessages(messagesIdToUpdate)
      }
    }
  })</code></pre>
<p>Ok we now have a functional chat between two users. Let&#8217;s see how to improve it, by adding attachment functionnalities.</p>
<h2>Improve chat by sending geolocation or pictures</h2>
<p>As improvment, we will let the user send it&#8217;s google map geolocation or an image in the chat. </p>
<p>Let&#8217;s first modify the <strong>chat-page.page.html</strong> file to add these functionnalities:</p>
<pre><code class="language-html">  &lt;ion-toolbar color="light"&gt;
   &lt;ion-row class="ion-align-items-center attachments"&gt;
     &lt;ion-col size="6"&gt;
      &lt;ion-textarea   [(ngModel)]="messageToSend" autocapitalize="sentence"
      placeholder="Type a message" spellcheck="true" color="dark" autoGrow="true"
      rows="1"&gt;&lt;/ion-textarea&gt;
    &lt;/ion-col&gt;
    &lt;ion-col size="2" &gt;
      &lt;ion-button (click)="clicGeoloc()" color="dark" shape="round" fill="clear"&gt;
        &lt;ion-icon name="location-outline"&gt;&lt;/ion-icon&gt;
      &lt;/ion-button&gt;
    &lt;/ion-col&gt;
    &lt;ion-col size="2"&gt;
      &lt;ion-button (click)="clicCamera()" color="dark" shape="round" fill="clear"&gt;
        &lt;ion-icon name="camera-outline"&gt;&lt;/ion-icon&gt;
      &lt;/ion-button&gt;
     &lt;/ion-col&gt;
     &lt;ion-col size="2"&gt;
      &lt;ion-button   (click)="sendMessage()" shape="round" fill="clear" [disabled]="messageToSend === ''"&gt;
        &lt;ion-icon name="paper-plane-outline"&gt;&lt;/ion-icon&gt;
      &lt;/ion-button&gt;
     &lt;/ion-col&gt;
   &lt;/ion-row&gt;
  &lt;/ion-toolbar&gt;</code></pre>
<p>and <strong>chat-page.page.scss</strong> file:</p>
<pre><code class="language-scss">.attachments{
    ion-icon {
        font-size: 20px;

    }
}</code></pre>
<div class="page-break"></div>
<p>Our new toolbar will render like this:</p>
<p><img src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/08/Capture-décran-2021-08-12-à-17.09.45-e1628781013911.png" alt="Toolbarattach" /></p>
<h3>Dealing with attachment : Geolocation</h3>
<p>To send our location to the other user, we will need to install the <a href="https://capacitorjs.com/docs/apis/geolocation"><strong>Capacitor Geolocation</strong></a> plugin.</p>
<pre><code class="language-shell">npm install @capacitor/geolocation
npx cap sync</code></pre>
<blockquote>
<p>Please notice you will need to configure the <strong>Info.plist</strong> file of your code project, to ask permission.</p>
<pre><code class="language-xml">&lt;key&gt;NSLocationAlwaysAndWhenInUseUsageDescription&lt;/key&gt;
&lt;string&gt;We need to access your location to send it in the chat&lt;/string&gt;
&lt;key&gt;NSLocationWhenInUseUsageDescription&lt;/key&gt;
&lt;string&gt;We need to access your location to send it in the chat&lt;/string&gt;</code></pre>
<p>This will be used to show and ask the user if he accepts to be geolocalized:</p>
</blockquote>
<p><img src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/08/Simulator-Screen-Shot-iPhone-12-2021-08-13-at-08.27.50-e1628836260466.png" alt="iosgeoloc" /></p>
<div class="page-break"></div>
<p>Then to use it, we will import it in our code:</p>
<pre><code class="language-typescript">import { Geolocation } from '@capacitor/geolocation';</code></pre>
<p>Because we can use this application on our browser too (as a progressive web app), we need to check the <strong>platform</strong> on which the user is (browser or iOS/Android):</p>
<pre><code class="language-typescript">constructor(public apiService:ApiserviceService,
    public userManager:UserManagerServiceService,
    public router: Router,
    public route:ActivatedRoute,
    private webSocketService:WebSocketServiceService,
    public platform:Platform) { </code></pre>
<p>Then we can write our <strong>clicGeoloc</strong> method: </p>
<pre><code class="language-typescript"> async clicGeoloc(){
    if (this.platform.is("capacitor")){
      await Geolocation.checkPermissions().then((status)=&gt;{
        if (status.location=="granted"){
          this.getCurrentLocation()
        }
        else{
          Geolocation.requestPermissions().then((status)=&gt;{
            if (status.location=="granted"){
              this.getCurrentLocation()
            }
          })
        }
      })
    }
    else{
      // Get location from browser 
      if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(
          position =&gt; {
            console.log("============= POSITION  ================");
            console.log(position)
            let latitude = position.coords.latitude;
            let longitude = position.coords.longitude;
            this.sendLocationMessage(latitude,longitude)
          },
          error =&gt; {
            this.apiService.showError("Geolocation unavailable !")
          }
        );
      }
    }

  }</code></pre>
<ol>
<li>We first the Platform the user is using</li>
<li>If the platform is capacitor (ios/android), then we need to check if we have the permission to request the geolocation of the user, otherwise we need to request the permission.</li>
<li>Then we can call a method to get the current location of the user.</li>
<li>If the user is on a browser (navigator), then we can use the browser <strong>WebAPI</strong> to get the current position, and then we can send our geolocated message.</li>
</ol>
<p>First let&#8217;s write the method to get the current location with Capacitor:</p>
<pre><code class="language-typescript">async getCurrentLocation(){
    const position = await Geolocation.getCurrentPosition();
    if (position) {
      let latitude = position.coords.latitude;
      let longitude = position.coords.longitude;
      this.sendLocationMessage(latitude,longitude)
    }
    else{
      this.apiService.showError("Geolocation unavailable !")
    }
  }</code></pre>
<div class="page-break"></div>
<p>The plugin will return a JSON with different information such as the latitude and longitude:</p>
<pre><code class="language-json">{"coords":
  {
    "latitude":31.046050999999999,
    "accuracy":5,
    "heading":-1,
    "altitudeAccuracy":-1,
    "altitude":0,
    "longitude":-34.851612000000003,
    "speed":-1
  },
  "timestamp":1628836078582
}</code></pre>
<p>and now let&#8217;s focus on sending a message to our chat, with the geolocation information. We will construct the same Message json as before, but this time we will use and set the <strong>type</strong> and <strong>extraData</strong> parameters:</p>
<pre><code class="language-typescript">sendLocationMessage(latitude,longitude){
    let extraData = Number(latitude).toString()+","+Number(longitude).toString()
    this.sendMessageWithArg(1,extraData)
}</code></pre>
<p>We just need to refactor a little bit our code:</p>
<pre><code class="language-typescript"> sendMessage(){
    this.sendMessageWithArg(0,"")
}

sendMessageWithArg(type,extraData){
    if (this.webSocketService.isConnected){
      //create the expected json message
      let newMessage = {
        "author" : this.myUser.id,
        "refChat" : this.chat.id,
        "message" : this.messageToSend,
        "type" : type,
        "extraData": extraData
      }
      this.webSocketService.sendMessage(newMessage)
      this.messageToSend=""
    }
}</code></pre>
<p>Our <strong>Django</strong> backend doesn&#8217;t need any modifications because we already set these values in the database when received, in our <strong>consumers.py</strong> file :</p>
<pre><code class="language-python">    def new_message(self,data):
        message = Message()
        message.refChat_id = data["refChat"]
        message.message = data["message"]
        message.author_id = data["author"]
        message.isRead = False
        message.type = data["type"]
        message.extraData = data["extraData"]
        message.save()</code></pre>
<p>And if you remember we created the <strong>Message</strong> model with:</p>
<pre><code class="language-python"> msg_type = (
        (0, "TEXT"),
        (1, "GEOLOC"),
        (2, "PHOTO"),
    )
    type = models.IntegerField(choices=msg_type, default=0)</code></pre>
<p>which now make sense. <strong>type==1</strong> means it is a geolocated message and the <strong>extraData</strong> contains the information.</p>
<div class="page-break"></div>
<p>Now we just need to update our <strong>chat-page.page.html</strong> file to check if the type is 0 (message) or 1 (geolocation):</p>
<pre><code class="language-html"> &lt;div [class]="message.author=== myUser.id ? 'messageFromMe' : 'messageFromOther'"&gt;
        &lt;span *ngIf="message.type==0"&gt;
          {{message.message}}
        &lt;/span&gt;
        &lt;span *ngIf="message.type==1" class="chatimage"&gt;
          &lt;ion-img src="assets/imgs/chatlocate.png"&gt;&lt;/ion-img&gt;
        &lt;/span&gt;
        &lt;span class="timestamp"&gt;
          {{ message.createdAt | date:'HH:mm' }}
          &lt;ion-icon name="checkmark-done-circle-outline" *ngIf="message.author === myUser.id &amp;&amp; message.isRead"&gt;&lt;/ion-icon&gt;
          &lt;ion-icon name="ellipse-outline" *ngIf="message.author === myUser.id &amp;&amp; !message.isRead"&gt;&lt;/ion-icon&gt;
        &lt;/span&gt;
      &lt;/div&gt;</code></pre>
<p>and scss:</p>
<pre><code class="language-scss">.chatimage{
    max-width: 100px;
}</code></pre>
<p>If the type is 0, we display the text message, otherwise we display an image (located in the <strong>assets/imgs/</strong> folder):</p>
<p><img src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/08/Capture-décran-2021-08-13-à-08.54.20-e1628837706976.png" alt="chatimage" /></p>
<p>Now we will add the possibility to click on the image, to launch <strong>Google Maps</strong> within a browser to show position on a map. </p>
<p>And this time we will use <strong>Capacitor Browser</strong> plugin to do so. Let&#8217;s install it:</p>
<pre><code class="language-shell">npm install @capacitor/browser
npx cap sync</code></pre>
<p>Now we use it :</p>
<pre><code class="language-typescript">import { Browser } from '@capacitor/browser';</code></pre>
<p>Modify the <strong>HTML</strong> to add the click :</p>
<pre><code class="language-html">&lt;ion-img src="assets/imgs/chatlocate.png" (click)="showLocation(message)"&gt;&lt;/ion-img&gt;</code></pre>
<p>and create the <strong>showLocation(message)</strong> method:</p>
<pre><code class="language-typescript"> async showLocation(message){
    let extraData = message.extraData
    if (extraData){
      let tokens = extraData.split(",")
      console.log(extraData)
      let lat = tokens[0]
      let lon = tokens[1]
      let urlGoogleMap = "https://maps.google.com/?q=" + lat + "," + lon
      await Browser.open({ url: urlGoogleMap });
    }

  }</code></pre>
<div class="page-break"></div>
<h3>Dealing with attachment : Picture</h3>
<p>To send picture attachment, we will use same technique as before except the <strong>type</strong> parameter will be 2, and the <strong>extraData</strong> will contain the url of our image. </p>
<p>To choose or take an image, we will use the <a href="https://capacitorjs.com/docs/apis/camera">Capacitor Camera</a> plugin, so let&#8217;s install it:</p>
<pre><code class="language-shell">npm install @capacitor/camera
npx cap sync</code></pre>
<blockquote>
<p>As before, the iOS requires the following usage description be added and filled out for your app in Info.plist: NSCameraUsageDescription, NSPhotoLibraryAddUsageDescription and NSPhotoLibraryUsageDescription.</p>
<p>For Android, the <strong>AndroidManifest.xml</strong> will require:</p>
<pre><code class="language-xml">&lt;uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/&gt;
&lt;uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /&gt;</code></pre>
</blockquote>
<p>Now we import the plugin:</p>
<pre><code class="language-typescript">import { Camera, CameraResultType } from '@capacitor/camera';</code></pre>
<p>Easy part, we can update the <strong>HTML</strong> to display the image which will be in the <strong>extraData</strong> parameter:</p>
<pre><code class="language-html"> &lt;span *ngIf="message.type==2" class="chatimage"&gt;
    &lt;ion-img [src]="message.extraData" (click)="showImage(message)"&gt;&lt;/ion-img&gt;
&lt;/span&gt;</code></pre>
<p>Now we need to create the <strong>clicCamera()</strong> method, which will let the user chooses his photo and then add the image to the chat.</p>
<pre><code class="language-typescript"> async clicCamera(){
    const image = await Camera.getPhoto({
      quality: 90,
      allowEditing: true,
      resultType: CameraResultType.Base64
    });
    if (image){
      //Send image to server
      let extraData =&lt;code&gt;data:image/${image.format}&lt;/code&gt;+";base64,"+image.base64String
       this.sendMessageWithArg(2,extraData)
    }
  }</code></pre>
<p>The trick here is to set the <strong>extraData</strong> parameter with the content of the image as a <strong>base64</strong> string !</p>
<p>Before testing our application again, we need to modify our <strong>Django Message Model</strong> and replace the:</p>
<pre><code class="language-python"> extraData = models.CharField(default='', null=True, blank=True, max_length=255)</code></pre>
<p>which is a <strong>CharField</strong> which is too short to contains a base64 string by:</p>
<pre><code class="language-python"> extraData = models.TextField(null=True, blank=True)</code></pre>
<p>Now we need to launch a <strong>Django migration</strong> using <strong>python manage.py makemigrations</strong> and <strong>python manage.py migrate</strong>:</p>
<pre><code class="language-shell">python manage.py makemigrations
Migrations for 'chat':
  chat/migrations/0002_auto_20210813_0804.py
    - Alter field extraData on message
(venv) christophesurbier@MacBook-Air-de-christophe chattuto % python manage.py migrate       
Operations to perform:
  Apply all migrations: admin, auth, chat, contenttypes, sessions
Running migrations:
  Applying chat.0002_auto_20210813_0804... OK</code></pre>
<div class="page-break"></div>
<p>And voilà ! We can try the application, select or take an image (iOS/Android only) and send it to the chat:</p>
<p><img src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/08/Capture-décran-2021-08-13-à-10.13.48-e1628842464742.png" alt="ChatImage" /></p>
<blockquote>
<p>As an exercice, you could try to create the <strong>showImage(message)</strong> method and use the <a href="https://github.com/capacitor-community/photoviewer">Capacitor PhotoViewer</a> plugin to show the image on fullscreen.</p>
</blockquote>
<p>More rapid and easy, i will use this <a href="https://www.npmjs.com/package/ngx-ionic-image-viewer">NgxIonicImageViewer</a> plugin: </p>
<pre><code class="language-shell">npm install --save ngx-ionic-image-viewer</code></pre>
<p>Then we need to modify our <strong>app.module.ts</strong> file to import it:</p>
<pre><code class="language-typescript">import { Injectable, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { HttpClient, HttpClientModule, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HTTP_INTERCEPTORS } from '@angular/common/http';
import { InAppBrowser } from '@ionic-native/in-app-browser/ngx';
import { IonicStorageModule } from '@ionic/storage-angular';
import { TokenInterceptor } from './services/token.interceptor';
import { NgxIonicImageViewerModule } from 'ngx-ionic-image-viewer';

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [
    IonicStorageModule.forRoot(),
    BrowserModule, 
    HttpClientModule,
    IonicModule.forRoot(), 
    AppRoutingModule,
    NgxIonicImageViewerModule
  ],
  providers: [
    InAppBrowser,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
    { provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true } 
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}</code></pre>
<p>then we also need to import it into our <strong>chat-page.module.ts</strong> file:</p>
<pre><code class="language-typescript">import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';

import { IonicModule } from '@ionic/angular';

import { ChatPagePageRoutingModule } from './chat-page-routing.module';

import { ChatPagePage } from './chat-page.page';
import { NgxIonicImageViewerModule } from 'ngx-ionic-image-viewer';
@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    IonicModule,
    NgxIonicImageViewerModule,
    ChatPagePageRoutingModule
  ],
  declarations: [ChatPagePage]
})
export class ChatPagePageModule {}</code></pre>
<p>and then we can use it as a directive as simple as :</p>
<pre><code class="language-html">&lt;ion-img ionImgViewer [src]="message.extraData"&gt;&lt;/ion-img&gt;</code></pre>
<blockquote>
<p>We just added the <strong>ionImgViewer</strong> directive to our image. </p>
</blockquote>
<p>Now you can click the image, it will open on fullscreen. We don&#8217;t need the <strong>showImage()</strong> anymore !</p>
<blockquote>
<p>Please notice the <strong>scrollToBottom</strong> instruction that we set after initializing the message list, will not always scroll exactly to bottom with image messages. I modified the code to use a <strong>timer</strong> to wait a little bit that the content view refreshed and then i ask for a scrollToBottom :</p>
</blockquote>
<pre><code class="language-typescript">if (!fromScrolling){
   //Move to bottom
  setTimeout(()=&gt;{
       this.content.scrollToBottom(1000);
  },200)
}</code></pre>
<blockquote>
<p>The result is a little bit better but not optimal. I&#8217;m not sure what the problem is, may be it is a <strong>ionic</strong> bug. </p>
</blockquote>
<h2>Managing user presence with online/offline</h2>
<p>In a chat application, it would be nice to know if the other user is online or not. We will add this feature.</p>
<p>First let&#8217;s modify our <strong>User</strong> model, to add this information:</p>
<pre><code class="language-python">class User(AbstractBaseUser, PermissionsMixin):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    email = models.EmailField(_('email address'), unique=True)
    first_name = models.CharField(_('first name'), max_length=30, blank=True)
    last_name = models.CharField(_('last name'), max_length=30, blank=True)
    date_joined = models.DateTimeField(_('date joined'), auto_now_add=True)
    is_active = models.BooleanField(_('active'), default=True)
    is_staff = models.BooleanField(_('active'), default=True)
    avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
    lastConnexionDate = models.DateTimeField(null=True, blank=True)
    valid = models.BooleanField(default=True)
    online = models.BooleanField(default=False)</code></pre>
<p>Then we can launch migrations: </p>
<pre><code class="language-shell">python manage.py makemigrations
python manage.py migrate</code></pre>
<p>We can update <strong>user.ts</strong> file to add the online fields:</p>
<pre><code class="language-typescript">export class User {
    fcmdevice:any;
    password:string;
    last_login:string;
    is_superuser:any;
    id:any;
    email:string;
    first_name:string;
    last_name:string;
    date_joined:Date;
    is_active:boolean;
    is_staff:boolean;
    avatar:string;
    groups:any;
    user_permissions:any;
    lastConnexionDate : Date;
    valid:boolean;
    backgroundColor : string
    online : boolean;</code></pre>
<div class="page-break"></div>
<p>Remember our <strong>setActiveAndLastDate</strong> and <strong>setInactive</strong> methods in our <strong>app.component.ts</strong> file ? We can now modify these methods to set this new <strong>online</strong> parameter: </p>
<pre><code class="language-typescript">setActiveAndLastDate(user: User) {
    if (this.apiService.networkConnected) {
      this.apiService.showLoading().then(() =&gt; {
        this.apiService.showLoading().then(() =&gt; {
          let params = {
            "online": true,
            "lastConnexionDate": new Date()
          }
          this.apiService.updateUser(user.id, params).subscribe((done) =&gt; {
            this.apiService.stopLoading()
          })
        })
      })
    }
  }

setInactive(user: User) {
    if (this.apiService.networkConnected) {
      this.apiService.showLoading().then(() =&gt; {
        let params = {
          "online": false
        }
        this.apiService.updateUser(user.id, params).subscribe((done) =&gt; {
          this.apiService.stopLoading()
        })
      })
    }
  }</code></pre>
<blockquote>
<p>This methods are called when a user enters or leaves the application, which is perfect to know if a user is online or not.</p>
</blockquote>
<p>We need to modify our <strong>ChatUserSerializer</strong> and <strong>SearchUserSerializer</strong> to return this online information:</p>
<pre><code class="language-python">class ChatUserSerializer(ModelSerializer):
    class Meta:
        model = User
        fields = ['id','first_name','last_name','online']

class SearchUserSerializer(ModelSerializer):
    class Meta:
        ref_name = "SearchUser"
        model = User
        fields = ["id","first_name","last_name","online"]</code></pre>
<p>Now if you try the application again, when getting the chat list json, you should have the information about <strong>online</strong> for each user:</p>
<pre><code class="language-json">{
    "id": "e7344a76-5c3d-42b5-99ff-d1317fa2193e",
    "fromUser": {
        "id": "91314a6f-8f49-42d1-9eef-d653c326d0d0",
        "first_name": "user1",
        "last_name": "christophe",
        "online": true
    },
    "toUser": {
        "id": "b1efd0ec-96cd-4b05-8e38-def384ba436f",
        "first_name": "user2",
        "last_name": "julien",
        "online": false
    },
    "lastMessage":
}</code></pre>
<p>Let&#8217;s modify our <strong>home-page.page.html</strong>,  <strong>search user list</strong> to display the online information:</p>
<pre><code class="language-html"> &lt;ion-item  *ngFor="let item of listOfUser"&gt;
    &lt;div  [ngClass]="item.classonline"  [ngStyle]="{'background':item.backgroundColor}"&gt;

    &lt;/div&gt;
    &lt;ion-label class="username"&gt;{{item.first_name}} {{item.last_name}}&lt;/ion-label&gt;
    &lt;ion-button slot="end" (click)="createChat(item)" expand="block" fill="clear" shape="round"&gt;
         &lt;ion-icon name="chatbubbles-outline"&gt;&lt;/ion-icon&gt;
    &lt;/ion-button&gt;
&lt;/ion-item&gt;</code></pre>
<div class="page-break"></div>
<p>and our chat list:</p>
<pre><code class="language-html">&lt;ion-item  *ngFor="let chat of chatList" (click)="goToChat(chat)"&gt;
    &lt;div [ngClass]="chat.classonline" [ngStyle]="{'background':chat.backgroundColor}"&gt;

    &lt;/div&gt;</code></pre>
<p>then we modify the <strong>home-page.page.scss</strong> file :</p>
<pre><code class="language-scss">.image-profile-online{
    width: 2rem;
    height: 2rem;
    display: block;
    border-radius: 50%;
    border: 3px solid rgb(23, 247, 23);
    margin-right: 10px;
 }
 .image-profile-offline{
    width: 2rem;
    height: 2rem;
    display: block;
    border-radius: 50%;
    border: 3px solid red;
    margin-right: 10px;
 }</code></pre>
<p><strong>online</strong> users will be displayed with a green circle and <strong>offline</strong> user with a red one.<br />
Finally we need to adapt our code to construct dynamically this <strong>classonline</strong> variable, which is very simple for the <strong>onSearchChange</strong> method: </p>
<pre><code class="language-typescript"> for (let item of data) {
    item["backgroundColor"] = this.getRandomColor()
    if (item.online){
        item["classonline"]="image-profile-online"
    }
    else {
        item["classonline"]="image-profile-offline"
    }
    this.listOfUser.push(item)
}</code></pre>
<p>whereas on the <strong>loadExistingChat()</strong> method, the code is a little bit more complex since we first need determine which user in the <strong>json Chat</strong> is not ourself:</p>
<pre><code class="language-typescript">if (theChat.fromUser.id!=this.userManager.currentUser.id){
    if (theChat.fromUser.online){
        theChat["classonline"]="image-profile-online"
    }
    else {
        theChat["classonline"]="image-profile-offline"
    }
}
else{
    //Other 
    if (theChat.toUser.online){
        theChat["classonline"]="image-profile-online"
    }
    else {
        theChat["classonline"]="image-profile-offline"
    }
}</code></pre>
<blockquote>
<p>As improvment, you could pass the online information to the <strong>ChatPage</strong> and display something in the header to show that the user is online (or not). </p>
<div class="page-break"></div>
</blockquote>
<p>When a user logs or registers to our application, we should set him as online:</p>
<pre><code class="language-typescript">this.apiService.findUserWithQuery("?email="+email).subscribe((list) =&gt; {
  if (list) {
    let count = list["count"]
    console.log("Count " + count)
    if (count == 0) {
      this.apiService.showError('Identification failed ! No account found');
    }
    else {
      let result = list["results"][0]
      console.log(result)
      this.userManager.setUser(result).then((done) =&gt; {
         let params = {
              "online": true,
               "lastConnexionDate": new Date()
          }
          this.apiService.updateUser(this.userManager.currentUser.id, params).subscribe((done) =&gt; {

          })
          // Next screen
          console.log("===Can go to next screen")
          this.router.navigateByUrl('/home', { replaceUrl: true });
      })
    }
  }</code></pre>
<pre><code class="language-typescript"> let updateParams = {
    "first_name": params.firstName,
    "last_name": params.lastName,
    "online":true,
    "lastConnexionDate": new Date()
  }

  this.apiService.updateUser(this.userManager.currentUser.id, updateParams).subscribe((done) =&gt; {
      console.log("resultat update ", done)
  })</code></pre>
<h3>How to refresh chat list home page with latest message ?</h3>
<p>We need to fix some issues. </p>
<p>If you send a message or received a message from a user and go back to the home page, the chat list will not be aware of this new message (list was loaded before), so the screen will not be updated. Worst, if you received a new chat from a user, you will not be able to see it in the list !</p>
<p>One easy solution, could be to detect each time user enters the home page and refresh the list:</p>
<pre><code class="language-typescript">ionViewDidEnter(){
    this.loadExistingChat()
  }</code></pre>
<p>Another solution would be to have a background thread getting the chat list each second (a polling system):</p>
<pre><code class="language-typescript">constructor(public apiService: ApiserviceService,
    public userManager: UserManagerServiceService,
    public router:Router) {
      this.loadExistingChat()
      this.taskBackground = setInterval(() =&gt; {
        this.launchBackgroundThread();
     }, 1000); //1secondes
  }

  ngOnInit() {
  }
  ngOnDestroy(){
    this.taskBackground.clearInterval()
  }

  ngAfterViewInit() {
    SplashScreen.hide()
  }

  launchBackgroundThread(){
    this.apiService.getChats().subscribe((list) =&gt; {
      console.log(list)
      if (list){
        let count = list["count"]
        if (count&gt;this.chatList.length){
          //New chat refresh the list
          console.log("===new chat")
          this.chatList = []
          this.parseChatList(list)
        }
      }
    })
  }</code></pre>
<p>and to avoid code duplication, we refactor to create a <strong>parseChatList</strong> method:</p>
<pre><code class="language-typescript">parseChatList(list){
    let count = list["count"]
    if (count &gt; 0) {
      //Iterate existing chat
      for (let aChat of list["results"]) {
        let theChat = new Chat().initWithJSON(aChat)
        console.log(theChat)
        theChat["backgroundColor"] = this.getRandomColor()
        if (theChat.lastMessage.length &gt; 0) {
          let lastmessage = theChat.lastMessage[0]
          let classMessage = 'messageUnread'
          if (lastmessage.isRead) {
            classMessage = "messageread"
          }
          theChat["classMessage"] = classMessage
        }
        if (theChat.fromUser.id!=this.userManager.currentUser.id){
          if (theChat.fromUser.online){
            theChat["classonline"]="image-profile-online"
          }
          else {
            theChat["classonline"]="image-profile-offline"
          }
        }
        else{
          //Other 
          if (theChat.toUser.online){
            theChat["classonline"]="image-profile-online"
          }
          else {
            theChat["classonline"]="image-profile-offline"
          }
        }
        this.chatList.push(theChat)
      }

    }
  }

  loadExistingChat() {
    if (this.apiService.networkConnected) {
      this.isSearching = true

      this.chatList = []
      this.apiService.getChats().subscribe((list) =&gt; {
        if (list) {
          this.parseChatList(list)
        }
      })
    }
  }</code></pre>
<p>With this solution if an incoming chat arrives, the list will refresh and we will be able to see it on the top of the list (because the backend orders by most recent one first).</p>
<div class="page-break"></div>
<p>Now we need to have a different approach for last message displayed. We will iterate our current chat list with the new one received, and check if last message are the same. If not, we can directly modify the chat last message element without refreshing the all list:</p>
<pre><code class="language-typescript"> launchBackgroundThread(){
    this.apiService.getChats().subscribe((list) =&gt; {
      console.log(list)
      if (list){
        let count = list["count"]
        if (count&gt;this.chatList.length){
          //New chat refresh the list
          console.log("===nouveau chat")
          this.chatList = []
          this.parseChatList(list)
        }
        else{
          //check message
          for (let aChat of list["results"]) {
            let theChat = new Chat().initWithJSON(aChat)
            for (let chatDisplayed of this.chatList){
              if (chatDisplayed.id==theChat.id){
                let lastMessageDisplayed = chatDisplayed.lastMessage[0]
                let currentLastMessage = theChat.lastMessage[0]
                if (currentLastMessage &amp;&amp; !lastMessageDisplayed){
                  chatDisplayed.lastMessage = theChat.lastMessage
                  console.log("==Replace last message",JSON.stringify(chatDisplayed.lastMessage))
                }
                else{
                  if (currentLastMessage.id!=lastMessageDisplayed.id){
                    //New incoming message replace in list
                    chatDisplayed.lastMessage = theChat.lastMessage
                    console.log("==Replace last message",JSON.stringify(chatDisplayed.lastMessage))
                  }
                }
              }
            }
          }
        }
      }
    })
  }</code></pre>
<blockquote>
<p>Another solution could be to use <strong>WebSocket</strong> again and develop a notification event system (as we have done for the chat himself). Or to use an external provider such as <a href="https://pusher.com/">Pusher.com</a> if you don&#8217;t want to spend time to develop realtime notifications. </p>
</blockquote>
<p>The source code of this tutorial can be found on my <a href="https://github.com/csurbier/chattuto/tree/main/day-nine">Github</a> repository.</p>
<div class="page-break"></div>
<h2>Questions/Answers</h2>
<ol>
<li>How to pass arguments to a <strong>ionic screen</strong> while navigating ?</li>
</ol>
<blockquote>
<p>Create a <strong>navigationExtras</strong> dictionnary with the fields/objects you want to pass then use the <strong>navigate</strong> method of the standard <strong>Angular router</strong> : </p>
</blockquote>
<pre><code class="language-typescript">  const navigationExtras: NavigationExtras = { state : {
      chat : new Chat().initWithJSON(response) }
  };
  this.router.navigate(['chat-page'], navigationExtras);</code></pre>
<ol>
<li>How to get an element value of in the <strong>Ionic Html page</strong> from your code ?</li>
</ol>
<blockquote>
<p>Tag the content in your HTML with <strong>#</strong> keyword and then use the <strong>@ViewChild</strong> angular instruction:</p>
<pre><code class="language-html">&lt;ion-content padding #content [scrollEvents]="true" (ionScroll)="checkScrolling($event)"&gt;</code></pre>
<p>and</p>
<pre><code class="language-typescript">@ViewChild('content', { static: true }) content;</code></pre>
</blockquote>
<ol>
<li>What technology can we use to have realtime notifications ?<br />
<blockquote>
<p>Websockets </p>
</blockquote>
</li>
</ol>
<p><span style="text-decoration: underline"><strong>To go further :</strong></span> </p>
<p>You can implement a new functionality : Showing in chat that other user is typing a message. You should have all the technical background to do it now. </p>
<ol>
<li>
<p>listen for events on the chat input box</p>
</li>
<li>
<p>Send a new kind of event thru the websocket (like usertyping)</p>
</li>
<li>
<p>Modify the <strong>consumers.py</strong> file to receive and transmit this new event (PS: no need to save this information in database. This is not our usual <strong>Django model Message</strong>)</p>
</li>
<li>Modify the code to subscribe to this kind of message (user is typing) and display something at the bottom of the chat list like a bubble with &#8230; as we can see in many other chat applications.
<div class="page-break"></div>
</li>
</ol>
<p>The post <a rel="nofollow" href="https://www.ionicanddjangotutorial.com/day-9-chatting-with-a-user/">Day 9 : Chatting with a user</a> appeared first on <a rel="nofollow" href="https://www.ionicanddjangotutorial.com">Ionic and Django Tutorial</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Day 8 : Searching users, creating / displaying chats</title>
		<link>https://www.ionicanddjangotutorial.com/day-8-searching-users-creating-displaying-chats/</link>
		
		<dc:creator><![CDATA[Christophe Surbier]]></dc:creator>
		<pubDate>Fri, 13 Aug 2021 15:34:06 +0000</pubDate>
				<category><![CDATA[Create a real world mobile chat application with Ionic and Django]]></category>
		<guid isPermaLink="false">https://www.ionicanddjangotutorial.com/?p=1330</guid>

					<description><![CDATA[<p>Create a chat application with Ionic 5/ Angular 12/ Capacitor 3 and Django 3 &#8211; Series &#8211; Part height Ok&#8230; <a href="https://www.ionicanddjangotutorial.com/day-8-searching-users-creating-displaying-chats/" class="more-link">Continue Reading <span class="meta-nav">&#8594;</span></a></p>
<p>The post <a rel="nofollow" href="https://www.ionicanddjangotutorial.com/day-8-searching-users-creating-displaying-chats/">Day 8 : Searching users, creating / displaying chats</a> appeared first on <a rel="nofollow" href="https://www.ionicanddjangotutorial.com">Ionic and Django Tutorial</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h1>Create a chat application with Ionic 5/ Angular 12/ Capacitor 3 and Django 3 &#8211; Series &#8211; Part height</h1>
<p>Ok no that we learned how to develop and deploy on application on a device, it&#8217;s time to build our main application goal : Chatting with users. </p>
<h1>Day 8 : Searching users, creating / displaying chats</h1>
<h2>Search user</h2>
<p>It is obvious that to chat with other <strong>Users</strong> we need to be able to find them. So we will implement a searchbar functionality on our application main page a.k.a <strong>HomePage</strong> .</p>
<p>Let&#8217;s edit our <strong>src/app/pages/home-page.page.html</strong> file to add the search bar:</p>
<pre><code class="language-html">&lt;ion-header&gt;
  &lt;ion-toolbar&gt;
    &lt;ion-title&gt;ChatTuto&lt;/ion-title&gt;
  &lt;/ion-toolbar&gt;
&lt;/ion-header&gt;

&lt;ion-content&gt;
   &lt;ion-searchbar placeholder="Search users" inputmode="text" type="text" 
    (ionChange)="onSearchChange($event)" 
    [debounce]="250"&gt;
  &lt;/ion-searchbar&gt;

&lt;/ion-content&gt;</code></pre>
<p>Each time the user will enter a caracter the event <strong>ionChange</strong> will be launched and the method <strong>onSearchChange</strong> will be called with the event value. The <strong>debounce</strong> instruction will wait a little (250 milliseconds) before calling our method. This will avoid intempestive calls while the user is typing.</p>
<p>In the <strong>src/app/pages/home-page.page.ts</strong> file, we can write the method and display the value entered:</p>
<pre><code class="language-typescript">onSearchChange(event){
    console.log(event.detail.value)
}</code></pre>
<blockquote>
<p>For rapid and simple development, i usually develop and test using <strong>ionic serve</strong> command, and used <strong>Ionic hot live deploy</strong> when i really want to test on a simulator or a device. It&#8217;s up to you. Both methods are valid.</p>
</blockquote>
<p>Now we will ask our backend to send us all the users existing in the system, having first or last name containing the text searched. Let&#8217;s continue to modify our <strong>HomePage</strong>:</p>
<pre><code class="language-typescript">import { Component, OnInit } from '@angular/core';
import { SplashScreen } from '@capacitor/splash-screen';
import { User } from 'src/app/models/user';
import { ApiserviceService } from 'src/app/services/api-service.service';
@Component({
  selector: 'app-home-page',
  templateUrl: './home-page.page.html',
  styleUrls: ['./home-page.page.scss'],
})
export class HomePagePage implements OnInit {

  listOfUser : User[] = []
  isSearching = false
  constructor(public apiService:ApiserviceService) { }

  ngOnInit() {
  }

  ngAfterViewInit() {
    SplashScreen.hide()
  }

  onSearchChange(event){
    console.log(event.detail.value)
    if (this.isSearching==false){
      if (this.apiService.networkConnected){
        this.isSearching = true
      }
      else{
        this.apiService.showNoNetwork()
      }
    }
  }
}</code></pre>
<p>First we add a <strong>isSearching</strong> variable to know that we already asked the backend for searching users and avoid launching several requests&#8230; Then we check if the network is available to search, otherwise we display a no network message. </p>
<p>Now let&#8217;s show a loader and launch a search on our backend:</p>
<pre><code class="language-typescript"> onSearchChange(event){
    console.log(event.detail.value)
    if (this.isSearching==false){
      if (this.apiService.networkConnected){
        this.isSearching = true
        this.apiService.showLoading().then(()=&gt;{
          this.apiService.searchUser(event.detail.value).subscribe((results)=&gt;{
            this.apiService.stopLoading()
            this.isSearching = false
          })
        })
      }
      else{
        this.apiService.showNoNetwork()
      }
    }
  }</code></pre>
<p>The <strong>apiService.searchUser</strong> method doesn&#8217;t exist yet and we need to create it.</p>
<blockquote>
<p>We have a <strong>findUserWithQuery</strong> methods in our <strong>ApiService</strong> but please remember that for security issues (avoiding showing other users data), this method is setup in Django to only returns your current user application. Remember the code we did:</p>
</blockquote>
<pre><code class="language-python"># Filter for connected user
def get_queryset(self):
  user = self.request.user
  queryset = User.objects.filter(pk=user.id)
  return queryset</code></pre>
<p>So we need to create a new <strong>searchUser</strong> method on backend, that will search on first and last names and will return filtered data (only the name). Then we will be able to use it into our <strong>Ionic application</strong>.</p>
<h3>SearchUser API in Django</h3>
<p>First we will edit our <strong>api/urls.py</strong> file to add the new route pattern:</p>
<pre><code class="language-python">url(r'^searchUser/$', SearchUserListView.as_view()),</code></pre>
<p>Then we will edit the <strong>api/views.py</strong> to create the <strong>SearchUserListView</strong> method:</p>
<pre><code class="language-python">class SearchUserListView(generics.ListCreateAPIView):
    permission_classes = [permissions.IsAuthenticated]
    queryset = User.objects.all()
    serializer_class = SearchUserSerializer
    search_fields = ['first_name','last_name']
    filter_backends = [DjangoFilterBackend,filters.SearchFilter,filters.OrderingFilter]</code></pre>
<p>and create the <strong>SearchUserSerializer</strong> in the <strong>api/serializers.py</strong> file:</p>
<pre><code class="language-python">class SearchUserSerializer(ModelSerializer):
    class Meta:
        ref_name = "SearchUser"
        model = User
        fields = ["id","first_name","last_name"]</code></pre>
<blockquote>
<p>In the serializer we only return <strong>id</strong>, <strong>first_name</strong>, <strong>last_name</strong> to avoid security issues and keep compliant with RGPD issues. </p>
</blockquote>
<p>Now we can test our new API with a curl method:</p>
<pre><code class="language-shell">curl --location --request GET 'http://localhost:8000/api/searchUser?search=Christ' \
--header 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjI4NDkzNjI4LCJqdGkiOiI3MDAzMDU0ZWIyY2U0Y2Q0OTUzMDM4Mjk1OTQyOGMyOSIsInVzZXJfaWQiOiIzY2RlM2Y3ZS0yNjFlLTRlYmItODQzNy1mYTRjMjdkMzViZjAifQ.sciuIti7Ce8Bci9lnJ5k7aGWkk67Q66UvPVL292m1qI'</code></pre>
<blockquote>
<p>Please remember we need to be authenticated with a JWt access token.</p>
</blockquote>
<p>And see the response:</p>
<pre><code class="language-json">{
    "count": 1,
    "next": null,
    "previous": null,
    "results": [
        {
            "first_name": "Christophe",
            "last_name": "Surbier",
            "id":"3cde3f7e-261e-4ebb-8437-fa4c27d35bf0"
        }
    ]
}</code></pre>
<p>Ok nice the <strong>API</strong> is working and sending back results, except at this time, there is only one user in the backend who is myself and we are not going to chat with ourselves ! So we need to modify our code to filter and exclude our own user from the result list. </p>
<p>Let&#8217;s modify our <strong>api/views.py</strong> file:</p>
<pre><code class="language-python">class SearchUserListView(generics.ListCreateAPIView):
    permission_classes = [permissions.IsAuthenticated]
    queryset = User.objects.all()
    serializer_class = SearchUserSerializer
    search_fields = ['first_name','last_name']
    filter_backends = [DjangoFilterBackend,filters.SearchFilter,filters.OrderingFilter]
    # Filter for connected user
    def get_queryset(self):
        user = self.request.user
        queryset = User.objects.exclude(pk=user.id)
        return queryset</code></pre>
<p>If we call again the API, then the answer will have no data:</p>
<pre><code class="language-json">{
    "count": 0,
    "next": null,
    "previous": null,
    "results": []
}</code></pre>
<p>This is perfect, except the fact that we would like some tests data for our application. </p>
<p>Let&#8217;s fix this.</p>
<h3>Generate Fake user data using Django</h3>
<p>To generate fake data, we will install the <strong>ModelBakery</strong> and <strong>Faker</strong> libraries:</p>
<pre><code class="language-shell">pip install model_bakery
pip install Faker</code></pre>
<p>Then we will create a <strong>createFakeData.py</strong> file at the root of our <strong>chatttuo</strong> project:</p>
<pre><code class="language-python">import os

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "chattuto.settings")

import django

django.setup()

from faker import factory, Faker
from chat.models import *
from model_bakery.recipe import Recipe, foreign_key

fake = Faker()

for k in range(100):
    user = Recipe(User,
                  first_name=fake.first_name(),
                  last_name=fake.last_name(),
                  email=fake.email(),
                  is_active=True,
                    createdAt=fake.future_datetime(end_date="+30d", tzinfo=None),
                    updatedAt=fake.future_datetime(end_date="+30d", tzinfo=None), )
    user.make()</code></pre>
<p>The script is quite simple, we ask the <strong>Faker</strong> library to create 100 users, and we specify which type of fields are our <strong>first_name</strong>, <strong>last_name</strong>, &#8230; To generate realistic values.</p>
<p>To run the script just launch the command:</p>
<pre><code>python createFakeData.py</code></pre>
<blockquote>
<p>Remember to have your environment variables set (database connexion, &#8230;)</p>
</blockquote>
<p>And voilà, you should have 100 new users in your backend, which you can verify easily with the <a href="http://127.0.0.1:8000/admin/chat/user/"><strong>Django Admin</strong></a> </p>
<div class="page-break"></div>
<h3>Search : display user list results</h3>
<p>Ok now we go back to our <strong>Ionic</strong> application and implement the <strong>searchUser</strong> method.</p>
<p>Let&#8217;s add it to our <strong>src/app/services/api-service.service.ts</strong> file.</p>
<p>First we need to declare our new url : </p>
<pre><code class="language-typescript"> this.getSearchUserUrl = this.virtualHostName + this.apiPrefix + "/searchUser/"</code></pre>
<p>and then implement the method:</p>
<pre><code class="language-typescript"> searchUser(searchTerm) {
    const options = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json'
      })
    };

    return Observable.create(observer =&gt; {
      this.http.get(this.getSearchUserUrl+"?search="+searchTerm, options)
        .pipe(retry(1))
        .subscribe(res =&gt; {
          observer.next(res);
          observer.complete();
        }, error =&gt; {
          observer.next();
          observer.complete();
          console.log(error);// Error getting the data
        });
    });
  }</code></pre>
<p>Now we can modify our method <strong>onSearchChange</strong> on <strong>HomePage</strong> to display the results in the console log: </p>
<pre><code class="language-typescript">onSearchChange(event){
    console.log(event.detail.value)
    if (this.isSearching==false){
      if (this.apiService.networkConnected){
        this.isSearching = true
        this.apiService.showLoading().then(()=&gt;{
          this.apiService.searchUser(event.detail.value).subscribe((results)=&gt;{
            this.apiService.stopLoading()
            this.isSearching = false
            console.log(results)
          })
        })
      }
      else{
        this.apiService.showNoNetwork()
      }
    }
  }</code></pre>
<p>In which i could see (for my fake data generated):</p>
<pre><code class="language-json">{
    "count": 10,
    "next": null,
    "previous": null,
    "results": [
        {
            "id": "60e40b98-527e-409e-89ce-d517af15a0eb",
            "first_name": "David",
            "last_name": "Medina"
        },
        {
            "id": "e326b45e-2560-4e05-a942-6d26319fba7a",
            "first_name": "Amanda",
            "last_name": "Hill"
        },
        {
            "id": "de7b3dde-ab8a-42ee-8b27-7618262fd84a",
            "first_name": "Wendy",
            "last_name": "Davis"
        },
        {
            "id": "4250b946-e0f3-4f5e-b653-2fb96c46fab2",
            "first_name": "Amanda",
            "last_name": "Long"
        },
        {
            "id": "447b8a41-d17d-44d2-b781-1f34b0abb164",
            "first_name": "Brian",
            "last_name": "Adams"
        },
        {
            "id": "a1db0d0a-210b-4b7d-bcc8-9e6d09ebcaed",
            "first_name": "Joseph",
            "last_name": "Daniel"
        },
        {
            "id": "9e311652-7cf6-473d-bf43-4fef39514d81",
            "first_name": "Yolanda",
            "last_name": "Norris"
        },
        {
            "id": "098c54ec-61d3-48c9-abd5-af7f4bbe6460",
            "first_name": "Stephanie",
            "last_name": "Miranda"
        },
        {
            "id": "6e72d0f0-12c2-4001-8f13-c5c450b1c639",
            "first_name": "Michelle",
            "last_name": "Davis"
        },
        {
            "id": "2cf8bcce-0c83-4af9-a27a-146a3e1b50df",
            "first_name": "Caroline",
            "last_name": "Davis"
        }
    ]
}</code></pre>
<blockquote>
<p>Obviously values should be different for you since they are randomly generated. </p>
</blockquote>
<p>Now to avoid launching a request to the API without something to search (if the user click on the searchbar cancel button), we can check there is a value by verifying the length of the search term.</p>
<pre><code class="language-typescript">if (this.isSearching==false &amp;&amp; event.detail.value.length&gt;0){
      if (this.apiService.networkConnected){
        this.isSearching = true
        this.apiService.showLoading().then(()=&gt;{
          this.apiService.searchUser(event.detail.value).subscribe((results)=&gt;{
            this.apiService.stopLoading()
            this.isSearching = false
            console.log(results)
          })
        })
      }
      else{
        this.apiService.showNoNetwork()
      }
    }</code></pre>
<p>Finally, we will iterate the result list sended back by the API, and will add each user to our own <strong>listOfUser</strong> variable: </p>
<pre><code class="language-typescript">import { Component, OnInit } from '@angular/core';
import { SplashScreen } from '@capacitor/splash-screen';
import { User } from 'src/app/models/user';
import { ApiserviceService } from 'src/app/services/api-service.service';
@Component({
  selector: 'app-home-page',
  templateUrl: './home-page.page.html',
  styleUrls: ['./home-page.page.scss'],
})
export class HomePagePage implements OnInit {

  listOfUser : User[] = []
  isSearching = false

  constructor(public apiService:ApiserviceService) { }

  ngOnInit() {
  }

  ngAfterViewInit() {
    SplashScreen.hide()
  }

  onSearchChange(event){
    if (this.isSearching==false &amp;&amp; event.detail.value.length&gt;0){
      if (this.apiService.networkConnected){
        this.isSearching = true
        this.apiService.showLoading().then(()=&gt;{
          this.apiService.searchUser(event.detail.value).subscribe((results)=&gt;{
            this.apiService.stopLoading()
            this.isSearching = false
            console.log(results)
            let count = results["count"]
            if (count&gt;0){
              let data = results["results"]
              this.listOfUser = []
              for (let item of data){
                this.listOfUser.push(item)
              }
            }
          })
        })
      }
      else{
        this.apiService.showNoNetwork()
      }
    }
    else{
      this.listOfUser = []
    }
  }
}</code></pre>
<p>If nothing is searched, we reset the list to an empty one. </p>
<p>Let&#8217;s edit the <strong>src/app/pages/home-page/home-page.page.html</strong> to display the value of our list:</p>
<pre><code class="language-html">&lt;ion-header&gt;
  &lt;ion-toolbar&gt;
    &lt;ion-title&gt;ChatTuto&lt;/ion-title&gt;
  &lt;/ion-toolbar&gt;
&lt;/ion-header&gt;

&lt;ion-content&gt;
   &lt;ion-searchbar placeholder="Search users" inputmode="text" type="text" 
    (ionChange)="onSearchChange($event)" 
    [debounce]="250"&gt;
  &lt;/ion-searchbar&gt;
  &lt;ion-list mode="ios" no-lines message-list&gt; 
      &lt;ion-item  *ngFor="let item of listOfUser"&gt;
        &lt;ion-label&gt;{{item.first_name}} {{item.last_name}}&lt;/ion-label&gt;
      &lt;/ion-item&gt;
   &lt;/ion-list&gt;
&lt;/ion-content&gt;</code></pre>
<div class="page-break"></div>
<p>You can try the application and search for something, you should see your results : </p>
<p><img src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/08/Capture-décran-2021-08-09-à-15.13.09-e1628514833542.png" alt="SearchResult" /></p>
<p>Let&#8217;s modify the code again to add some style :</p>
<pre><code class="language-html">&lt;ion-header&gt;
  &lt;ion-toolbar&gt;
    &lt;ion-title&gt;ChatTuto&lt;/ion-title&gt;
  &lt;/ion-toolbar&gt;
&lt;/ion-header&gt;

&lt;ion-content&gt;
   &lt;ion-searchbar placeholder="Search users" inputmode="text" type="text" 
    (ionChange)="onSearchChange($event)" 
    [debounce]="250"&gt;
  &lt;/ion-searchbar&gt;
  &lt;ion-list mode="ios" no-lines message-list&gt; 
      &lt;ion-item  *ngFor="let item of listOfUser"&gt;
        &lt;div class="image-profile" [ngStyle]="{'background':item.backgroundColor}"&gt;

        &lt;/div&gt;
        &lt;ion-label class="username"&gt;{{item.first_name}} {{item.last_name}}&lt;/ion-label&gt;
      &lt;/ion-item&gt;
   &lt;/ion-list&gt;
&lt;/ion-content&gt;</code></pre>
<p>Then edit the <strong>src/app/pages/home-page/home-page.page.scss</strong> to add our custom css:</p>
<pre><code class="language-scss">.image-profile {
    width: 2rem;
    height: 2rem;
    display: block;
    border-radius: 50%;
    border: 3px solid green;
    margin-right: 10px;
 }

 .username{
     font-weight: bold;
 }</code></pre>
<p>and finally we modify our result list, to add a random color for our user:</p>
<pre><code class="language-typescript"> import { Component, OnInit } from '@angular/core';
import { SplashScreen } from '@capacitor/splash-screen';
import { User } from 'src/app/models/user';
import { ApiserviceService } from 'src/app/services/api-service.service';
@Component({
  selector: 'app-home-page',
  templateUrl: './home-page.page.html',
  styleUrls: ['./home-page.page.scss'],
})
export class HomePagePage implements OnInit {

  listOfUser : User[] = []
  isSearching = false
  chatList : any; 

  constructor(public apiService:ApiserviceService) { }

  ngOnInit() {
  }

  ngAfterViewInit() {
    SplashScreen.hide()
  }

  getRandomColor() {
    var color = Math.floor(0x1000000 * Math.random()).toString(16);
    return "#" + ("000000" + color).slice(-6);
  }

  onSearchChange(event){
    if (this.isSearching==false &amp;&amp; event.detail.value.length&gt;0){
      if (this.apiService.networkConnected){
        this.isSearching = true
        this.apiService.showLoading().then(()=&gt;{
          this.apiService.searchUser(event.detail.value).subscribe((results)=&gt;{
            this.apiService.stopLoading()
            this.isSearching = false
            console.log(results)
            let count = results["count"]
            if (count&gt;0){
              let data = results["results"]
              this.listOfUser = []
              for (let item of data){
                item["backgroundColor"]= this.getRandomColor()
                this.listOfUser.push(item)
              }
            }
          })
        })
      }
      else{
        this.apiService.showNoNetwork()
      }
    }
    else{
      this.listOfUser = []
    }
  }
}</code></pre>
<p>Now list of results should look like this:</p>
<p><img src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/08/Capture-décran-2021-08-09-à-15.26.15-e1628515621864.png" alt="ColorResult" /></p>
<blockquote>
<p>As improvment we could use the <strong>avatar</strong> image of our Django User model, which means we will need to modify our <strong>RegisterPage</strong> to let the <strong>User</strong> choose or take a picture. Let&#8217;s keep things simple for now !</p>
</blockquote>
<div class="page-break"></div>
<h3>Add a button to create a chat</h3>
<p>Since we want to chat with a user in the list, we should add a button to do it, let&#8217;s add it to our html:</p>
<pre><code class="language-html">&lt;ion-header&gt;
  &lt;ion-toolbar&gt;
    &lt;ion-title&gt;ChatTuto&lt;/ion-title&gt;
  &lt;/ion-toolbar&gt;
&lt;/ion-header&gt;

&lt;ion-content&gt;
   &lt;ion-searchbar placeholder="Search users" inputmode="text" type="text" 
    (ionChange)="onSearchChange($event)" 
    [debounce]="250"&gt;
  &lt;/ion-searchbar&gt;
  &lt;ion-list mode="ios" no-lines message-list&gt; 
      &lt;ion-item  *ngFor="let item of listOfUser"&gt;
        &lt;div class="image-profile" [ngStyle]="{'background':item.backgroundColor}"&gt;

        &lt;/div&gt;
        &lt;ion-label class="username"&gt;{{item.first_name}} {{item.last_name}}&lt;/ion-label&gt;
        &lt;ion-button slot="end" (click)="createChat(item)" expand="block" fill="clear" shape="round"&gt;
          &lt;ion-icon name="chatbubbles-outline"&gt;&lt;/ion-icon&gt;
        &lt;/ion-button&gt;
      &lt;/ion-item&gt;
   &lt;/ion-list&gt;
&lt;/ion-content&gt;</code></pre>
<h2>Create a Chat</h2>
<p>The method <strong>createChat(item)</strong> will call our <strong>API</strong> to create a chat between our current user and the user selected in the list. </p>
<p>Because a <strong>chat</strong> can already exists between the two users, we will need to verify if it&#8217;s already exists and send it back or create a new chat and send it back. </p>
<p>Let&#8217;s create this new <strong>API</strong> endpoint</p>
<h3>Create a Chat with Django</h3>
<p>First add a new url endpoint into our <strong>api/urls.py</strong> file:</p>
<pre><code class="language-python"> url(r'^createChat/$', CreateChatView.as_view()),</code></pre>
<p>Then create the method into our <strong>api/views.py</strong> file:</p>
<pre><code class="language-python">class CreateChatView(APIView):
   def post(self, request, format=None):
       try:
           refUser = request.data["refUser"]
           chatWithUser = request.data["chatWithUser"]
           chat, created = Chat.objects.filter(Q(fromUser_id=refUser, toUser_id=chatWithUser) | Q(fromUser_id=chatWithUser, toUser_id=refUser)).get_or_create(fromUser_id=refUser, toUser_id=chatWithUser)
           print("Chat %s created %d"%(chat,created))
           serializer = ChatSerializer(chat)
           return Response(serializer.data, status=status.HTTP_201_CREATED)
       except Exception as e:
           logger.error(e)
           return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)</code></pre>
<blockquote>
<p>Don&#8217;t forget the correct libraries import:</p>
<pre><code class="language-python">from django.contrib.auth.models import User
from django.db.models import Q
from rest_framework import generics, permissions
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status</code></pre>
</blockquote>
<p>First the method will expect two parameters in a POST request : <strong>refUser</strong> and <strong>chatWithUser</strong></p>
<p>Then the method will check if a chat already exists, otherwise will create it:</p>
<pre><code class="language-python">chat, created = Chat.objects.filter(Q(fromUser_id=refUser, toUser_id=chatWithUser) | Q(fromUser_id=chatWithUser, toUser_id=refUser)).get_or_create(fromUser_id=refUser, toUser_id=chatWithUser)</code></pre>
<blockquote>
<p>Don&#8217;t forget the <strong>chat</strong> can have been created by yourself (<strong>refUser</strong>) or by the other user (<strong>chatWithUser</strong>), so we need to make a request with both possibilities (the <strong>Q</strong> parameters )</p>
</blockquote>
<p>Then with our <strong>Chat</strong> objet, we can create a <strong>ChatSerializer</strong> and send it back as our API response.</p>
<div class="page-break"></div>
<h3>Implement the chat creation from Ionic</h3>
<p>As usual, we need to declare the new url:</p>
<pre><code class="language-typescript">this.getCreateChatUrl = this.virtualHostName + this.apiPrefix + "/createChat/"</code></pre>
<p>Then we can create the method <strong>createChat</strong> in our <strong>ApiService</strong> :</p>
<pre><code class="language-typescript"> createChat(refUser,withUser) {
    const options = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json'
      })
    };

    console.log("URL " + this.getCreateChatUrl)

    let params = {
      "refUser":refUser,
      "chatWithUser":withUser
    }

    console.log(params)
    return Observable.create(observer =&gt; {
      this.http.post(this.getCreateChatUrl, params, options)
        .pipe(retry(1))
        .subscribe(res =&gt; {
          this.networkConnected = true
          observer.next(res);
          observer.complete();
        }, error =&gt; {
          observer.next(false);
          observer.complete();
          console.log(error);// Error getting the data
        });
    });

  }</code></pre>
<p>And then we can create our <strong>createChat</strong> method in our <strong>HomePage</strong>:</p>
<pre><code class="language-typescript">createChat(userToChat){
    if (this.apiService.networkConnected){
      this.apiService.showLoading().then(()=&gt;{
        this.apiService.createChat(this.userManager.currentUser.id,userToChat.id).subscribe((response)=&gt;{
          this.apiService.stopLoading()
          console.log(response)
        })
      })
    }
    else{
      this.apiService.showNoNetwork()
    }
  }</code></pre>
<blockquote>
<p>The <strong>userManager</strong> is injected into our <strong>HomePage</strong> constructor and contains the current user value :</p>
</blockquote>
<pre><code class="language-typescript">constructor(public apiService:ApiserviceService,
    public userManager:UserManagerServiceService) { }</code></pre>
<p>And we should see the <strong>chat</strong> created or retrieved if already existing in our javascript console log: </p>
<pre><code class="language-json">{
    "id": "26fd40d1-fdf1-42bc-a7d9-d6b11f5d3654",
    "createdAt": "2021-08-09T14:20:44.765814Z",
    "updatedAt": "2021-08-09T14:20:44.765882Z",
    "fromUser": "3cde3f7e-261e-4ebb-8437-fa4c27d35bf0",
    "toUser": "fffee82d-924a-4da4-b407-4784dbaf1005"
}</code></pre>
<p>As this step, we should go to a <strong>chat with user</strong> page which doesn&#8217;t exist yet. We will see this soon. </p>
<div class="page-break"></div>
<h2>Displaying chat list</h2>
<p>But first let&#8217;s focus on chat list. Now that we are able to search for a user and create a chat with this user, it should be useful to display current chat list on our <strong>HomePage</strong> !</p>
<p>To have more than one chat in the list, we can use our application to create multiple chat with multiple users. </p>
<h3>Managing Chat API in Django backend</h3>
<p>In the <strong>API</strong>, we already have an endpoint to get existing chats:</p>
<pre><code class="language-shell">curl --location --request GET 'http://localhost:8000/api/chat/' \
--header 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjI4NTgwNTEwLCJqdGkiOiI0YjRhYzBmNGUxNGI0MzZlYjA5NWJkMzFkODg5NDUyZiIsInVzZXJfaWQiOiIzY2RlM2Y3ZS0yNjFlLTRlYmItODQzNy1mYTRjMjdkMzViZjAifQ.nE7fkZw0rt1xw0o945wxYo8XWoDvULKhMFhZjgmtDEs'</code></pre>
<p>Response:</p>
<pre><code class="language-json">{
    "count": 6,
    "next": null,
    "previous": null,
    "results": [
        {
            "id": "53713a91-e936-4169-9fef-4bc870a0babc",
            "createdAt": "2021-03-08T08:11:30.677821Z",
            "updatedAt": "2021-03-08T08:11:30.677921Z",
            "fromUser": "9052a080-24d8-4a0e-8d9a-742213c0bf91",
            "toUser": "b4db6ca7-21e0-4c83-b56b-f5c86c4cbc43"
        },
        {
            "id": "26fd40d1-fdf1-42bc-a7d9-d6b11f5d3654",
            "createdAt": "2021-08-09T14:20:44.765814Z",
            "updatedAt": "2021-08-09T14:20:44.765882Z",
            "fromUser": "3cde3f7e-261e-4ebb-8437-fa4c27d35bf0",
            "toUser": "fffee82d-924a-4da4-b407-4784dbaf1005"
        },
        {
            "id": "467229f1-2033-4879-adab-d9272da8b43a",
            "createdAt": "2021-08-09T14:37:07.916113Z",
            "updatedAt": "2021-08-09T14:37:07.916162Z",
            "fromUser": "3cde3f7e-261e-4ebb-8437-fa4c27d35bf0",
            "toUser": "60e40b98-527e-409e-89ce-d517af15a0eb"
        },
        {
            "id": "71fd852a-d8eb-4b2a-b5d1-d95f19762852",
            "createdAt": "2021-08-10T06:58:41.563718Z",
            "updatedAt": "2021-08-10T06:58:41.563831Z",
            "fromUser": "3cde3f7e-261e-4ebb-8437-fa4c27d35bf0",
            "toUser": "aa9ea63b-c3e9-41d3-9d54-68d09b87f634"
        },
        {
            "id": "2a43948a-3842-4db3-9692-978c22b3d6ff",
            "createdAt": "2021-08-10T06:58:44.301552Z",
            "updatedAt": "2021-08-10T06:58:44.301660Z",
            "fromUser": "3cde3f7e-261e-4ebb-8437-fa4c27d35bf0",
            "toUser": "a1db0d0a-210b-4b7d-bcc8-9e6d09ebcaed"
        },
        {
            "id": "33e21813-e5ed-47c3-a1a7-056251fa3ddf",
            "createdAt": "2021-08-10T06:58:46.357281Z",
            "updatedAt": "2021-08-10T06:58:46.357388Z",
            "fromUser": "3cde3f7e-261e-4ebb-8437-fa4c27d35bf0",
            "toUser": "be61aeae-768e-47e2-b99b-9e11c730e633"
        }
    ]
}</code></pre>
<p>But this <strong>API</strong> returns all existing chats not only those involving our current user application. We need to modify the code to filter data. </p>
<p>Let&#8217;s edit the <strong>api/views.py</strong> file :</p>
<pre><code class="language-python">class ChatListView(generics.ListCreateAPIView):
    permission_classes = [permissions.IsAuthenticated]
    queryset = Chat.objects.all()
    serializer_class = ChatSerializer
    filterset_fields = ['id']
    filter_backends = [DjangoFilterBackend,filters.SearchFilter,filters.OrderingFilter]

    def get_queryset(self):
        user = self.request.user
        queryset = Chat.objects.filter(Q(fromUser_id=user.id)|Q(toUser_id=user.id)).select_related("fromUser").select_related("toUser")
        return queryset</code></pre>
<p>We added the <strong>get_queryset</strong> method, to override the default queryset, and we filter the list with the <strong>fromUser</strong> or <strong>toUser</strong> fields having our current user id.</p>
<p>Now the list is filtered for our user. But the information returns in the JSON are not so useful. We just have user identifiers. Let&#8217;s modify the <strong>ChatSerializer</strong> to add more relevant information about the users.</p>
<p>Edit the <strong>api/serializers.py</strong> file: </p>
<pre><code class="language-python">class ChatUserSerializer(ModelSerializer):
    class Meta:
        model = User
        fields = ['id','first_name','last_name']

class ChatSerializer(ModelSerializer):
    fromUser = ChatUserSerializer()
    toUser = ChatUserSerializer()
    class Meta:
        model = Chat
        fields = '__all__'</code></pre>
<p>First we create a new <strong>ChatUserSerializer</strong> with restricted information (we just want id, first name and last name).</p>
<blockquote>
<p>We don&#8217;t use the existing <strong>UserSerializer</strong> to avoid returns all user fields information, which could be a security issue. </p>
</blockquote>
<p>Then we modify the <strong>ChatSerializer</strong>, to specify that the fields <strong>fromUser</strong>, <strong>toUser</strong> needs to use the new <strong>ChatUserSerializer</strong>.<br />
Now if we look at our API call, it is much more better:</p>
<pre><code class="language-json">{
    "count": 5,
    "next": null,
    "previous": null,
    "results": [
        {
            "id": "26fd40d1-fdf1-42bc-a7d9-d6b11f5d3654",
            "fromUser": {
                "id": "3cde3f7e-261e-4ebb-8437-fa4c27d35bf0",
                "first_name": "Christophe",
                "last_name": "Surbier"
            },
            "toUser": {
                "id": "fffee82d-924a-4da4-b407-4784dbaf1005",
                "first_name": "Julie",
                "last_name": "Wright"
            },
            "createdAt": "2021-08-09T14:20:44.765814Z",
            "updatedAt": "2021-08-09T14:20:44.765882Z"
        },
        {
            "id": "467229f1-2033-4879-adab-d9272da8b43a",
            "fromUser": {
                "id": "3cde3f7e-261e-4ebb-8437-fa4c27d35bf0",
                "first_name": "Christophe",
                "last_name": "Surbier"
            },
            "toUser": {
                "id": "60e40b98-527e-409e-89ce-d517af15a0eb",
                "first_name": "David",
                "last_name": "Medina"
            },
            "createdAt": "2021-08-09T14:37:07.916113Z",
            "updatedAt": "2021-08-09T14:37:07.916162Z"
        },
        {
            "id": "71fd852a-d8eb-4b2a-b5d1-d95f19762852",
            "fromUser": {
                "id": "3cde3f7e-261e-4ebb-8437-fa4c27d35bf0",
                "first_name": "Christophe",
                "last_name": "Surbier"
            },
            "toUser": {
                "id": "aa9ea63b-c3e9-41d3-9d54-68d09b87f634",
                "first_name": "Paul",
                "last_name": "Walker"
            },
            "createdAt": "2021-08-10T06:58:41.563718Z",
            "updatedAt": "2021-08-10T06:58:41.563831Z"
        },
        {
            "id": "2a43948a-3842-4db3-9692-978c22b3d6ff",
            "fromUser": {
                "id": "3cde3f7e-261e-4ebb-8437-fa4c27d35bf0",
                "first_name": "Christophe",
                "last_name": "Surbier"
            },
            "toUser": {
                "id": "a1db0d0a-210b-4b7d-bcc8-9e6d09ebcaed",
                "first_name": "Joseph",
                "last_name": "Daniel"
            },
            "createdAt": "2021-08-10T06:58:44.301552Z",
            "updatedAt": "2021-08-10T06:58:44.301660Z"
        },
        {
            "id": "33e21813-e5ed-47c3-a1a7-056251fa3ddf",
            "fromUser": {
                "id": "3cde3f7e-261e-4ebb-8437-fa4c27d35bf0",
                "first_name": "Christophe",
                "last_name": "Surbier"
            },
            "toUser": {
                "id": "be61aeae-768e-47e2-b99b-9e11c730e633",
                "first_name": "Jennifer",
                "last_name": "Patrick"
            },
            "createdAt": "2021-08-10T06:58:46.357281Z",
            "updatedAt": "2021-08-10T06:58:46.357388Z"
        }
    ]
}</code></pre>
<p>We have our user information in our <strong>toUser</strong> or <strong>fromUser</strong> dictionnaries. </p>
<p>Final step, usually in <strong>Chat</strong> application, it is usual to display the last message sent in the chat.<br />
We need to find and add this information into our JSON.</p>
<p>Let&#8217;s again modify our <strong>ChatSerializer</strong> to include this last message information:</p>
<pre><code class="language-python">class ChatSerializer(ModelSerializer):
    fromUser = ChatUserSerializer()
    toUser = ChatUserSerializer()
    lastMessage = serializers.SerializerMethodField()

    class Meta:
        model = Chat
        fields = '__all__'

    def get_lastMessage(self, obj):
        # get last message between users
        try:
            messages = Message.objects.filter(refChat=obj.id).select_related("refChat").select_related("author").order_by('-createdAt')[:1]
            return MessageSerializer(messages,many=True).data
        except Exception as e:
            print(e)
            return None</code></pre>
<p>We add a <strong>lastMessage</strong> field:</p>
<pre><code class="language-python">lastMessage = serializers.SerializerMethodField()</code></pre>
<p>and we write the method to get the last message for this chat:</p>
<pre><code class="language-python">def get_lastMessage(self, obj):
       # get last message between users
       try:
           messages = Message.objects.filter(refChat=obj.id).select_related("refChat").select_related("author").order_by('-createdAt')[:1]
           return MessageSerializer(messages,many=True).data
       except Exception as e:
           print(e)
           return None</code></pre>
<div class="page-break"></div>
<p>To test our code, we need to add a message to an existing chat. Because we can&#8217;t do this yet with our <strong>Ionic</strong> application, we can use the <a href="http://127.0.0.1:8000/admin/chat/message/add/"><strong>Django Admin</strong></a> and then check in our return API json that we have the <strong>lastMessage</strong> information:</p>
<pre><code class="language-json"> {
            "id": "33e21813-e5ed-47c3-a1a7-056251fa3ddf",
            "fromUser": {
                "id": "3cde3f7e-261e-4ebb-8437-fa4c27d35bf0",
                "first_name": "Christophe",
                "last_name": "Surbier"
            },
            "toUser": {
                "id": "be61aeae-768e-47e2-b99b-9e11c730e633",
                "first_name": "Jennifer",
                "last_name": "Patrick"
            },
            "lastMessage": [
                {
                    "id": "c6c14089-b8ae-40c6-b5a1-eec11eb6bc7d",
                    "message": "my last message",
                    "type": 0,
                    "extraData": null,
                    "isRead": false,
                    "createdAt": "2021-08-10T07:26:56.960389Z",
                    "updatedAt": "2021-08-10T07:26:56.960470Z",
                    "refChat": "33e21813-e5ed-47c3-a1a7-056251fa3ddf",
                    "author": "3cde3f7e-261e-4ebb-8437-fa4c27d35bf0"
                }
            ],
            "createdAt": "2021-08-10T06:58:46.357281Z",
            "updatedAt": "2021-08-10T06:58:46.357388Z"
        }</code></pre>
<p>Ok now with all that backend code ready, we can focus on the <strong>Ionic</strong> application to display the existing chat list.</p>
<h3>Managing chat list in Ionic</h3>
<p>Let&#8217;s first add our new API in our <strong>api-service.service.ts</strong> file </p>
<pre><code class="language-typescript"> this.getChatUrl = this.virtualHostName + this.apiPrefix + "/chat/"</code></pre>
<p>and create a <strong>getChat</strong> method:</p>
<pre><code class="language-typescript">getChats() {
    const options = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json'
      })
    };
    return Observable.create(observer =&gt; {
      this.http.get(this.getChatUrl, options)
        .pipe(retry(1))
        .subscribe(res =&gt; {
          this.networkConnected = true
          observer.next(res);
          observer.complete();
        }, error =&gt; {
          observer.next(false);
          observer.complete();
          console.log(error);// Error getting the data
        });
    });
  }</code></pre>
<p>Then we will edit our <strong>HomePage</strong> to load our chat list</p>
<pre><code class="language-typescript">export class HomePagePage implements OnInit {

  listOfUser : User[] = []
  isSearching = false
  chatList : any; 

  constructor(public apiService:ApiserviceService,
    public userManager:UserManagerServiceService) {
      this.loadExistingChat()
  }</code></pre>
<p>The method for loading chat will check the network and then call our new API method:</p>
<pre><code class="language-typescript">loadExistingChat(){
    if (this.apiService.networkConnected){
      this.isSearching = true

        this.apiService.getChats().subscribe((list)=&gt;{
          console.log(list)
        })
    }
  }</code></pre>
<p>The <strong>list</strong> variable should contains our list (if exists). We can check the <strong>count</strong> value in the returned JSON and iterate it if we have values, to put chats in our <strong>chatList</strong> array:</p>
<pre><code class="language-typescript"> loadExistingChat(){
    if (this.apiService.networkConnected){
      this.isSearching = true

        this.chatList = []
        this.apiService.getChats().subscribe((list)=&gt;{

          if (list){
            let count = list["count"]
            if (count&gt;0){
              //Iterate existing chat
              for (let aChat of list["results"]){
                console.log(aChat)
                this.chatList.push(aChat)
              }
            }
          }

      })
    }
  }</code></pre>
<p>And see in our javascript console logs: </p>
<pre><code class="language-javscript">{id: "467229f1-2033-4879-adab-d9272da8b43a", fromUser: {…}, toUser: {…}, lastMessage: Array(0), createdAt: "2021-08-09T14:37:07.916113Z", …}
{id: "71fd852a-d8eb-4b2a-b5d1-d95f19762852", fromUser: {…}, toUser: {…}, lastMessage: Array(0), createdAt: "2021-08-10T06:58:41.563718Z", …}
{id: "2a43948a-3842-4db3-9692-978c22b3d6ff", fromUser: {…}, toUser: {…}, lastMessage: Array(0), createdAt: "2021-08-10T06:58:44.301552Z", …}
{id: "26fd40d1-fdf1-42bc-a7d9-d6b11f5d3654", fromUser: {…}, toUser: {…}, lastMessage: Array(0), createdAt: "2021-08-09T14:20:44.765814Z", …}
{id: "33e21813-e5ed-47c3-a1a7-056251fa3ddf", fromUser: {…}, toUser: {…}, lastMessage: Array(1), createdAt: "2021-08-10T06:58:46.357281Z", …}</code></pre>
<p>Now we need to display the chat but be careful, our current user could be in the <strong>fromUser</strong> OR <strong>toUser</strong> variable depending on who created the chat. So we don&#8217;t want to display our own user in the list but instead we want to display the other user of the chat. So we need to do modify the code above to take care of this.</p>
<p>And before we will create a new class <strong>chat.ts</strong> in our <strong>models</strong> directory to deal with typescript object instead of json:</p>
<pre><code class="language-typescript">import { User } from "./user";

export class Chat {
    id:string;
    fromUser:User;
    toUser:User;
    lastMessage : any;
    createdAt : Date;
    updateAt : Date;

    constructor() {

    }

    initWithJSON(json) : Chat{
      for (var key in json) {
          if (key=="fromUser" || key=="toUser"){
            let aUser = new User().initWithJSON(json[key])
            this[key]=aUser
          }
          else {
            this[key] = json[key];
          }
      }
      return this;
    }
}</code></pre>
<p>The <strong>initWithJSON</strong> method will iterate the json and for keys <strong>fromUser</strong>, <strong>toUser</strong> will create a <strong>User</strong> object too.</p>
<div class="page-break"></div>
<p>We can modifiy our method <strong>loadExistingChat</strong> to use this class:</p>
<pre><code class="language-typescript">loadExistingChat(){
    if (this.apiService.networkConnected){
      this.isSearching = true

        this.chatList = []
        this.apiService.getChats().subscribe((list)=&gt;{
          if (list){
            let count = list["count"]
            if (count&gt;0){
              //Iterate existing chat
              for (let aChat of list["results"]){
                let theChat = new Chat().initWithJSON(aChat)
                console.log(theChat)
                theChat["backgroundColor"]= this.getRandomColor()
                this.chatList.push(theChat)
              }
            }
          }
      })
    }
  }</code></pre>
<blockquote>
<p>We also added an extra variable <strong>backgroundColor</strong> to have same behaviour as our search and display a random color for the user.</p>
</blockquote>
<p>Now it&#8217;s time to modify our <strong>home-page.page.html</strong> file to display list:</p>
<pre><code class="language-html">  &lt;!-- Chat list --&gt;
   &lt;ion-list-header&gt;
    &lt;ion-label&gt;Chats&lt;/ion-label&gt;
  &lt;/ion-list-header&gt;
   &lt;ion-list mode="ios" no-lines message-list&gt; 
    &lt;ion-item  *ngFor="let chat of chatList"&gt;
      &lt;div class="image-profile" [ngStyle]="{'background':chat.backgroundColor}"&gt;

      &lt;/div&gt;
      &lt;ion-label class="username" *ngIf="chat.fromUser.id!=userManager.currentUser.id"&gt;{{chat.fromUser.first_name}} {{chat.fromUser.last_name}}&lt;/ion-label&gt;
      &lt;ion-label class="username" *ngIf="chat.toUser.id!=userManager.currentUser.id"&gt;{{chat.toUser.first_name}} {{chat.toUser.last_name}}&lt;/ion-label&gt;
    &lt;/ion-item&gt;
 &lt;/ion-list&gt;</code></pre>
<p>We iterate the <strong>chatList</strong> variable and to know which user to display, we need to check the identifier between our <strong>Ionic current user</strong> and the fromUser or toUser.</p>
<p>The <strong>chat</strong> list should look like this:</p>
<p><img src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/08/Capture-décran-2021-08-10-à-11.37.58-e1628588916180.png" alt="ChatList" /></p>
<div class="page-break"></div>
<p>Now we need to display the <strong>lastMessage</strong> received and the date of the message. Before modifying the <strong>Html</strong> again, a little remember of the <strong>JSON</strong> structure for a <strong>Chat</strong>:</p>
<pre><code class="language-json"> {
            "id": "33e21813-e5ed-47c3-a1a7-056251fa3ddf",
            "fromUser": {
                "id": "3cde3f7e-261e-4ebb-8437-fa4c27d35bf0",
                "first_name": "Christophe",
                "last_name": "Surbier"
            },
            "toUser": {
                "id": "be61aeae-768e-47e2-b99b-9e11c730e633",
                "first_name": "Jennifer",
                "last_name": "Patrick"
            },
            "lastMessage": [
                {
                    "id": "c6c14089-b8ae-40c6-b5a1-eec11eb6bc7d",
                    "message": "mon dernier message",
                    "type": 0,
                    "extraData": null,
                    "isRead": false,
                    "createdAt": "2021-08-10T07:26:56.960389Z",
                    "updatedAt": "2021-08-10T07:26:56.960470Z",
                    "refChat": "33e21813-e5ed-47c3-a1a7-056251fa3ddf",
                    "author": "3cde3f7e-261e-4ebb-8437-fa4c27d35bf0"
                }
            ],
            "createdAt": "2021-08-10T06:58:46.357281Z",
            "updatedAt": "2021-08-10T06:58:46.357388Z"
        }</code></pre>
<p>The <strong>lastMessage</strong> is an array which contains only one element: <strong>the last message received</strong>. If there is no message yet the array is empty.</p>
<p>The <strong>isRead</strong> parameter is used to know if the message has been read or not.</p>
<p>The <strong>author</strong> parameter is the user identifier of who sent the message.</p>
<p>Based on that here is the modified <strong>HTML</strong>:</p>
<pre><code class="language-html"> &lt;!-- Chat list --&gt;
  &lt;ion-list-header&gt;
    &lt;ion-label&gt;Chats&lt;/ion-label&gt;
  &lt;/ion-list-header&gt;
   &lt;ion-list mode="ios" no-lines message-list&gt; 
    &lt;ion-item  *ngFor="let chat of chatList"&gt;
      &lt;div class="image-profile" [ngStyle]="{'background':chat.backgroundColor}"&gt;

      &lt;/div&gt;
      &lt;ion-label class="username" *ngIf="chat.fromUser.id!=userManager.currentUser.id"&gt;
        &lt;ion-row&gt;
          &lt;ion-col size="10"&gt;
            &lt;h2&gt;{{chat.fromUser.first_name}} {{chat.fromUser.last_name}}&lt;/h2&gt;
            &lt;p *ngIf="chat.lastMessage.length&gt;0"&gt;
              {{chat.lastMessage[0].message}} 
            &lt;/p&gt;
          &lt;/ion-col&gt;
          &lt;ion-col size="2" *ngIf="chat.lastMessage.length&gt;0"&gt;
              &lt;span class="timestamp" *ngIf="chat.lastMessage[0].isRead"&gt;{{ chat.lastMessage[0].updatedAt | date: 'HH:mm' }}&lt;/span&gt;
              &lt;ion-badge *ngIf="!chat.lastMessage[0].isRead" color="primary"&gt;
              {{ chat.lastMessage[0].updatedAt | date: 'HH:mm' }}
              &lt;/ion-badge&gt;
          &lt;/ion-col&gt;
        &lt;/ion-row&gt;
      &lt;/ion-label&gt;
      &lt;ion-label class="username" *ngIf="chat.toUser.id!=userManager.currentUser.id"&gt;
        &lt;ion-row&gt;
          &lt;ion-col size="10"&gt;
            &lt;h2&gt;{{chat.toUser.first_name}} {{chat.toUser.last_name}}&lt;/h2&gt;
            &lt;p *ngIf="chat.lastMessage.length&gt;0"&gt;
              {{chat.lastMessage[0].message}} 
            &lt;/p&gt;
          &lt;/ion-col&gt;
          &lt;ion-col size="2" *ngIf="chat.lastMessage.length&gt;0"&gt;
            &lt;span class="timestamp" *ngIf="chat.lastMessage[0].isRead"&gt;{{ chat.lastMessage[0].updatedAt | date: 'HH:mm' }}&lt;/span&gt;
            &lt;ion-badge *ngIf="!chat.lastMessage[0].isRead" color="primary"&gt;
              {{ chat.lastMessage[0].updatedAt | date: 'HH:mm' }}
            &lt;/ion-badge&gt;
          &lt;/ion-col&gt;
        &lt;/ion-row&gt;
      &lt;/ion-label&gt;
    &lt;/ion-item&gt;
 &lt;/ion-list&gt;</code></pre>
<p>And the result: </p>
<p><img src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/08/Capture-décran-2021-08-10-à-12.02.14-e1628590395118.png" alt="ChatWtithMessage" /></p>
<p>Ok we can improve again the view by showing the last message at the top of the list. To do that, we can order our chat list queryset using an <strong>order_by</strong> instruction:</p>
<pre><code class="language-python">class ChatListView(generics.ListCreateAPIView):
    permission_classes = [permissions.IsAuthenticated]
    queryset = Chat.objects.all()
    serializer_class = ChatSerializer
    filterset_fields = ['id']
    filter_backends = [DjangoFilterBackend,filters.SearchFilter,filters.OrderingFilter]

    def get_queryset(self):
        user = self.request.user
        queryset = Chat.objects.filter(Q(fromUser_id=user.id)|Q(toUser_id=user.id)).order_by("-updatedAt").select_related("fromUser").select_related("toUser")
        return queryset</code></pre>
<p>and display the last message in bold and red if not already read. Let&#8217;s add our <strong>home-page.page.scss</strong> to add two variables:</p>
<pre><code class="language-scss"> .messageUnread{
     p{
        font-weight: bold;
        color: red;
     }
 }

 .messageread{
    p{
       font-weight: normal;
       color: black;
    }</code></pre>
<div class="page-break"></div>
<p>Then we modify our <strong>loadExistingChat</strong> method to check the status of the <strong>isRead</strong> variable:</p>
<pre><code class="language-typescript">loadExistingChat(){
    if (this.apiService.networkConnected){
      this.isSearching = true

        this.chatList = []
        this.apiService.getChats().subscribe((list)=&gt;{
          if (list){
            let count = list["count"]
            if (count&gt;0){
              //Iterate existing chat
              for (let aChat of list["results"]){
                let theChat = new Chat().initWithJSON(aChat)
                console.log(theChat)
                theChat["backgroundColor"]= this.getRandomColor()
                if (theChat.lastMessage.length&gt;0){
                  let lastmessage = theChat.lastMessage[0]
                  let classMessage = 'messageUnread'
                  if (lastmessage.isRead){
                    classMessage="messageread"
                  }
                  theChat["classMessage"]=classMessage
                }
                this.chatList.push(theChat)
              }
            }
          }
      })
    }
  }</code></pre>
<p>Based on the <strong>isRead</strong> value with set the appropriate CSS class. And then we modify our css to set the correct class to our <strong>ion-row</strong> element: </p>
<pre><code class="language-html">  &lt;!-- Chat list --&gt;
   &lt;ion-list-header&gt;
    &lt;ion-label&gt;Chats&lt;/ion-label&gt;
  &lt;/ion-list-header&gt;
   &lt;ion-list mode="ios" no-lines message-list&gt; 
    &lt;ion-item  *ngFor="let chat of chatList"&gt;
      &lt;div class="image-profile" [ngStyle]="{'background':chat.backgroundColor}"&gt;

      &lt;/div&gt;
      &lt;ion-label class="username" *ngIf="chat.fromUser.id!=userManager.currentUser.id"&gt;
        &lt;ion-row [ngClass]="chat.classMessage" &gt;
          &lt;ion-col size="10"&gt;
            &lt;h2&gt;{{chat.fromUser.first_name}} {{chat.fromUser.last_name}}&lt;/h2&gt;
            &lt;p *ngIf="chat.lastMessage.length&gt;0"&gt;
              {{chat.lastMessage[0].message}} 
            &lt;/p&gt;
          &lt;/ion-col&gt;
           &lt;ion-col size="2" *ngIf="chat.lastMessage.length&gt;0"&gt;
              &lt;span class="timestamp" *ngIf="chat.lastMessage[0].isRead"&gt;{{ chat.lastMessage[0].updatedAt | date: 'HH:mm' }}&lt;/span&gt;
              &lt;ion-badge *ngIf="!chat.lastMessage[0].isRead" color="primary"&gt;
              {{ chat.lastMessage[0].updatedAt | date: 'HH:mm' }}
              &lt;/ion-badge&gt;
          &lt;/ion-col&gt;
        &lt;/ion-row&gt;
      &lt;/ion-label&gt;
      &lt;ion-label class="username" *ngIf="chat.toUser.id!=userManager.currentUser.id"&gt;
        &lt;ion-row [ngClass]="chat.classMessage" &gt;
          &lt;ion-col size="10"&gt;
            &lt;h2&gt;{{chat.toUser.first_name}} {{chat.toUser.last_name}}&lt;/h2&gt;
            &lt;p *ngIf="chat.lastMessage.length&gt;0"&gt;
              {{chat.lastMessage[0].message}} 
            &lt;/p&gt;
          &lt;/ion-col&gt;
          &lt;ion-col size="2" *ngIf="chat.lastMessage.length&gt;0"&gt;
            &lt;span class="timestamp" *ngIf="chat.lastMessage[0].isRead"&gt;{{ chat.lastMessage[0].updatedAt | date: 'HH:mm' }}&lt;/span&gt;
            &lt;ion-badge *ngIf="!chat.lastMessage[0].isRead" color="primary"&gt;
              {{ chat.lastMessage[0].updatedAt | date: 'HH:mm' }}
            &lt;/ion-badge&gt;
           &lt;/ion-col&gt;
        &lt;/ion-row&gt;
      &lt;/ion-label&gt;
    &lt;/ion-item&gt;
 &lt;/ion-list&gt;</code></pre>
<p>And voila, the message and date should be bold and red. To check our code is working, you can use the <strong>Django Admin</strong> again, find the message and set the <strong>isRead</strong> value to True. Then you can reload your <strong>Ionic page</strong> and verify that bold and red color has disappeared. </p>
<div class="page-break"></div>
<blockquote>
<p>Of course you can still use the search bar to search users. </p>
</blockquote>
<p>Before to conclude, you should have notice that when searching a user and displaying the result list, the search bar is loosing focus and we need to click on it again. This is quite annoying. Let&#8217;s fix this:</p>
<pre><code class="language-html"> &lt;ion-searchbar #searchbar placeholder="Search users" inputmode="text" type="text" 
    (ionChange)="onSearchChange($event)" 
    [debounce]="250"&gt;
  &lt;/ion-searchbar&gt;</code></pre>
<p>First we modify our <strong>searchBar</strong> html to tag it : <strong>#searchbar</strong></p>
<p>Then from our typescript code, we will get a reference to this searchbar element and at the end of the search, we will set the focus on it again:</p>
<pre><code class="language-typescript">export class HomePagePage implements OnInit {
  @ViewChild('searchbar', { static: true }) searchbarElement;</code></pre>
<p>and </p>
<pre><code class="language-typescript"> onSearchChange(event) {
    if (this.isSearching == false &amp;&amp; event.detail.value.length &gt; 0) {
      if (this.apiService.networkConnected) {
        this.isSearching = true
        this.apiService.showLoading().then(() =&gt; {
          console.log("===Call api with ", event.detail.value)
          this.apiService.searchUser(event.detail.value).subscribe((results) =&gt; {
            this.apiService.stopLoading()
            this.isSearching = false
            console.log(results)
            let count = results["count"]
            if (count &gt; 0) {
              let data = results["results"]
              this.listOfUser = []
              for (let item of data) {
                item["backgroundColor"] = this.getRandomColor()
                this.listOfUser.push(item)
              }
              //Focus on searchbar again
              this.searchbarElement.setFocus();
            }
          })
        })
      }
      else {
        this.apiService.showNoNetwork()
      }
    }
    else {
      this.listOfUser = []
      this.isSearching = false
    }
  }</code></pre>
<p>The main instruction is :</p>
<pre><code class="language-typescript">this.searchbarElement.setFocus();</code></pre>
<p>We will stop here and in next day tutorial, we will learn how to chat with a User.</p>
<p>The source code for this tutorial can be found on my <a href="https://github.com/csurbier/chattuto/tree/main/day-height">Github</a></p>
<div class="page-break"></div>
<h2>Questions/Answers</h2>
<ol>
<li>In a Django Rest framework serializer, how to specify which fields to include in the response<br />
<blockquote>
<p>Use the <strong>fields</strong> instruction and list the fields.<br />
fields = [&quot;id&quot;,&quot;first_name&quot;,&quot;last_name&quot;]</p>
</blockquote>
</li>
<li>How to generate fake data with Django<br />
<blockquote>
<p>Use the Faker library</p>
</blockquote>
</li>
<li>Which Ionic file should we modify to modify the stylesheet of a page ?<br />
<blockquote>
<p>The <strong>scss</strong> file of the page</p>
</blockquote>
</li>
<li>How to add custom fields to a serializers ?<br />
<blockquote>
<p>Add a new field of type serializers.SerializerMethodField() and write the method get_<something></p>
</blockquote>
</li>
</ol>
<pre><code class="language-python">    myCustomField  = serializers.SerializerMethodField()

    def get_myCustomField(self,obj):
        return "MyValue"</code></pre>
<ol>
<li>How to get an HTML element reference from typescript ?<br />
<blockquote>
<p>Tag the element in the Html and use the <strong>@ViewChild</strong> instruction in the typescript code.</p>
</blockquote>
</li>
</ol>
<div class="page-break"></div>
<p>The post <a rel="nofollow" href="https://www.ionicanddjangotutorial.com/day-8-searching-users-creating-displaying-chats/">Day 8 : Searching users, creating / displaying chats</a> appeared first on <a rel="nofollow" href="https://www.ionicanddjangotutorial.com">Ionic and Django Tutorial</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Day 7 : Deploy on simulator or mobile device</title>
		<link>https://www.ionicanddjangotutorial.com/day-7-deploy-on-simulator-or-mobile-device/</link>
		
		<dc:creator><![CDATA[Christophe Surbier]]></dc:creator>
		<pubDate>Fri, 13 Aug 2021 15:33:43 +0000</pubDate>
				<category><![CDATA[Create a real world mobile chat application with Ionic and Django]]></category>
		<guid isPermaLink="false">https://www.ionicanddjangotutorial.com/?p=1328</guid>

					<description><![CDATA[<p>Create a chat application with Ionic 5/ Angular 12/ Capacitor 3 and Django 3 &#8211; Series &#8211; Part seven In&#8230; <a href="https://www.ionicanddjangotutorial.com/day-7-deploy-on-simulator-or-mobile-device/" class="more-link">Continue Reading <span class="meta-nav">&#8594;</span></a></p>
<p>The post <a rel="nofollow" href="https://www.ionicanddjangotutorial.com/day-7-deploy-on-simulator-or-mobile-device/">Day 7 : Deploy on simulator or mobile device</a> appeared first on <a rel="nofollow" href="https://www.ionicanddjangotutorial.com">Ionic and Django Tutorial</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h1>Create a chat application with Ionic 5/ Angular 12/ Capacitor 3 and Django 3 &#8211; Series &#8211; Part seven</h1>
<p>In previous tutorials, we learned how to register or login a user, to redirect to an home page and to protect the application using Angular guards. </p>
<p>However we made all our tests using a browser since <strong>Ionic</strong> can be used to build progressive web app application (running on the web). </p>
<p>Now we will learn how to use <a href="https://capacitorjs.com/">Capacitor</a> to deploy our code on a real mobile device. </p>
<h1>Day 7 : Deploy on simulator or mobile device</h1>
<p><a href="https://capacitorjs.com/">Capacitor</a>, which is developped by the <strong>Ionic team</strong>, is the new tool to build native apps with Web technologies (such as <strong>Ionic</strong>). Before <strong>Capacitor</strong> the most used framework was <strong>Cordova</strong> but this is the past. </p>
<p>With <strong>Capacitor</strong> you can access and use native mobile code within your web application (using native <a href="https://capacitorjs.com/docs/apis">Plugins</a>  and thru javascript). There are some official <strong>Capacitor plugins</strong> but there are also plugins created by the community, and it&#8217;s even possible to create it&#8217;s own plugin (if you know how to develop nativley for iOS or Android). </p>
<h2>Use capacitor to build ios and android application</h2>
<p>To iniatialize <strong>Capacitor</strong> with a <strong>Ionic project</strong> you can just type:</p>
<pre><code class="language-shell">ionic integrations enable capacitor</code></pre>
<p>But this is useless because since we started from a fresh Ionic project, we already configure our <strong>Ionic project</strong> to use Capacitor. </p>
<blockquote>
<p>In tutorial 4, we created the project with the <strong>&#8211;capacitor</strong> option : </p>
<p>ionic start ChatTuto sidemenu &#8211;capacitor &#8211;project-id=chattuto &#8211;package-id=com.idevotion.chattuto</p>
</blockquote>
<p>We also installed the <strong>iOS</strong> package with the command:  </p>
<pre><code class="language-shell">npm install @capacitor/ios</code></pre>
<p>Now let&#8217;s add the <strong>Android</strong> package:</p>
<pre><code class="language-shell">npm install @capacitor/android@3.0.0</code></pre>
<blockquote>
<p>When i started this tutorial, the <strong>Capacitor</strong> version was 3.0.0 so we need to specify this version, otherwise some conflict issues/error will occured because <strong>Capacitor</strong> has moved to version 3.1.1 when writing this tutorial 7.</p>
</blockquote>
<p>If you want to update the full project with latest version of <strong>Capacitor</strong> it&#8217;s possible but quite annoying. You need to uninstall previous package (ios, android) or conflict will raise, then install latest version of plugins used (or conflict will raise) and install latest version of <strong>Capacitor</strong> client and core libraries.</p>
<p>Then you can install again ios and Android packages: </p>
<pre><code class="language-shell">npm uninstall @capacitor/ios
npm uninstall @capacitor/android
npm install @capacitor/{network,splash-screen,status-bar,storage,keyboard,app,haptics}@latest
npm install @capacitor/cli@latest
npm install @capacitor/core@latest
npm install @capacitor/ios@latest
npm install @capacitor/android@latest</code></pre>
<p>Then to generate the <strong>iOS</strong> and <strong>Android</strong> projects you can launch the command:</p>
<pre><code class="language-shell">npx cap add ios
npx cap add android</code></pre>
<p><strong>Capacitor</strong> will create an <strong>ios</strong> directory in which you will find the <strong>iOS xcode project</strong>, and an <strong>android</strong> directory in which you will find the <strong>Android studio</strong> project.</p>
<p>To launch the desired project, just type:</p>
<pre><code class="language-shell">npx cap open ios</code></pre>
<p>or</p>
<pre><code class="language-shell">npx cap open android</code></pre>
<p>And the desired IDE (Xcode or Android studio) will open.</p>
<blockquote>
<p>Please notice that you need a <strong>Mac</strong> to do <strong>iOS</strong> developement.</p>
</blockquote>
<h3>Update Capacitor with latest <strong>Ionic</strong> code source</h3>
<p>To build the application, launch the command:</p>
<pre><code class="language-shell">ionic build --prod</code></pre>
<blockquote>
<p>When building with <strong>&#8211;prod</strong> option, <strong>Ionic</strong> will make more verification on your code and do lot of optimizations to reduce the bundle size of your app. Sometimes some development error can occurs that didn&#8217;t happenned with <strong>ionic serve</strong> command. </p>
</blockquote>
<div class="page-break"></div>
<blockquote>
<p>As example, i had the error <strong>Property &#8216;submitAttempt&#8217; does not exist on type &#8216;LoginPagePage&#8217;.</strong> which is true since i didn&#8217;t declare this variable in my code. I removed this parameter from my HTML page and build again to solve the issue.</p>
</blockquote>
<p>Then if compilation is successfull, you can update your <strong>iOS/Android</strong> IDE with the latest code with the command:</p>
<pre><code class="language-shell">npx cap copy</code></pre>
<p>or just specify the platform you want to update:</p>
<pre><code class="language-shell">npx cap copy ios
npx cap copy android</code></pre>
<blockquote>
<p>If you install a new <strong>Capacitor</strong> library (we will see this soon), then you need to launch a</p>
</blockquote>
<pre><code class="language-shell">npx cap sync </code></pre>
<p>or</p>
<pre><code class="language-shell">npx cap sync ios
npx cap sync android</code></pre>
<p>The command is <strong>sync</strong> and not <strong>copy</strong>. </p>
<h3>Running project in simulator or device</h3>
<p>To run and test our application on a simulator or a device, we just need to open the desired IDE (Xcode or Android) and proceed as we will do with native development.</p>
<p>For <strong>iOS</strong>, i just launch Xcode</p>
<pre><code class="language-shell">npx cap open ios</code></pre>
<p>then select an <strong>iOS simulator</strong> and click the <strong>Run</strong> button. After compiling, the simulator launchs and i&#8217;m able to see the <strong>LoginPage</strong> of your application.</p>
<p><img src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/08/Simulator-Screen-Shot-iPhone-12-2021-08-06-at-07.54.58-e1628229563330.png" alt="LoginPage" /></p>
<p>Everything is working perfectly, meaning you can login (or register) and see the <strong>HomePage</strong></p>
<blockquote>
<p>You can do the same with <strong>Android studio</strong></p>
</blockquote>
<p>Another command is also possible if you want to run the project without opening Xcode/Android studio. Just launch the command</p>
<pre><code class="language-shell">npx cap run ios</code></pre>
<p>or </p>
<pre><code class="language-shell">npx cap run android</code></pre>
<p>And <strong>Capacitor</strong> will update project with your latest ionic code, and then will launch automatically Xcode or Android studio and will ask you on which device (or simulator) you want to run the application, before running it.</p>
<div class="page-break"></div>
<h2>Generate application icons and splashscreen</h2>
<p>When using <strong>Capacitor</strong>, the application icon and splashscreen will be the default Capacitor one. But of course, we can change it easily. To do that we need to create a <strong>resources</strong> directory into our <strong>Chattuto</strong> root folder:</p>
<pre><code class="language-shell">mkdir resources</code></pre>
<p>And then inside this folder, we need to put 2 files:</p>
<ol>
<li>icon.png : should be an image of 1024*1024 pixels with your logo</li>
<li>splash.png : should be an image of 2732*2732 pixels with your splashcreen</li>
</ol>
<p>Then we will install a library to generate images:</p>
<pre><code class="language-shell">npm install cordova-res --save-dev</code></pre>
<p>Then create a <strong>scripts</strong> folder still inside our <strong>chattuto</strong> root folder:</p>
<pre><code class="language-shell">mkdir scripts</code></pre>
<p>and create a <strong>resources.js</strong> file with the following code:</p>
<pre><code class="language-typescript">const fs = require('fs');

const SOURCE_IOS_ICON = 'resources/ios/icon/';
const SOURCE_IOS_SPLASH = 'resources/ios/splash/';

const TARGET_IOS_ICON = 'ios/App/App/Assets.xcassets/AppIcon.appiconset/';
const TARGET_IOS_SPLASH = 'ios/App/App/Assets.xcassets/Splash.imageset/';

const SOURCE_ANDROID_ICON = 'resources/android/icon/';
const SOURCE_ANDROID_SPLASH = 'resources/android/splash/';

const TARGET_ANDROID_ICON = 'android/app/src/main/res/';
const TARGET_ANDROID_SPLASH = 'android/app/src/main/res/';

const IOS_ICONS = [
  { source: 'icon-20.png', target: 'AppIcon-20x20@1x.png' },
  { source: 'icon-20@2x.png', target: 'AppIcon-20x20@2x.png' },
  { source: 'icon-20@2x.png', target: 'AppIcon-20x20@2x-1.png' },
  { source: 'icon-20@3x.png', target: 'AppIcon-20x20@3x.png' },
  { source: 'icon-29.png', target: 'AppIcon-29x29@1x.png' },
  { source: 'icon-29@2x.png', target: 'AppIcon-29x29@2x.png' },
  { source: 'icon-29@2x.png', target: 'AppIcon-29x29@2x-1.png' },
  { source: 'icon-29@3x.png', target: 'AppIcon-29x29@3x.png' },
  { source: 'icon-40.png', target: 'AppIcon-40x40@1x.png' },
  { source: 'icon-40@2x.png', target: 'AppIcon-40x40@2x.png' },
  { source: 'icon-40@2x.png', target: 'AppIcon-40x40@2x-1.png' },
  { source: 'icon-40@3x.png', target: 'AppIcon-40x40@3x.png' },
  { source: 'icon-60@2x.png', target: 'AppIcon-60x60@2x.png' },
  { source: 'icon-60@3x.png', target: 'AppIcon-60x60@3x.png' },
  { source: 'icon-76.png', target: 'AppIcon-76x76@1x.png' },
  { source: 'icon-76@2x.png', target: 'AppIcon-76x76@2x.png' },
  { source: 'icon-83.5@2x.png', target: 'AppIcon-83.5x83.5@2x.png' },
  { source: 'icon-1024.png', target: 'AppIcon-512@2x.png' }
];
const IOS_SPLASHES = [
  { source: 'Default-Portrait@~ipadpro.png', target: 'splash-2732x2732.png' },
  { source: 'Default-Portrait@~ipadpro.png', target: 'splash-2732x2732-1.png' },
  { source: 'Default-Portrait@~ipadpro.png', target: 'splash-2732x2732-2.png' }
];

const ANDROID_ICONS = [
  { source: 'drawable-ldpi-icon.png', target: 'drawable-hdpi-icon.png' },
  { source: 'drawable-mdpi-icon.png', target: 'mipmap-mdpi/ic_launcher.png' },
  { source: 'drawable-mdpi-icon.png', target: 'mipmap-mdpi/ic_launcher_round.png' },
  { source: 'drawable-mdpi-icon.png', target: 'mipmap-mdpi/ic_launcher_foreground.png' },
  { source: 'drawable-hdpi-icon.png', target: 'mipmap-hdpi/ic_launcher.png' },
  { source: 'drawable-hdpi-icon.png', target: 'mipmap-hdpi/ic_launcher_round.png' },
  { source: 'drawable-hdpi-icon.png', target: 'mipmap-hdpi/ic_launcher_foreground.png' },
  { source: 'drawable-xhdpi-icon.png', target: 'mipmap-xhdpi/ic_launcher.png' },
  { source: 'drawable-xhdpi-icon.png', target: 'mipmap-xhdpi/ic_launcher_round.png' },
  { source: 'drawable-xhdpi-icon.png', target: 'mipmap-xhdpi/ic_launcher_foreground.png' },
  { source: 'drawable-xxhdpi-icon.png', target: 'mipmap-xxhdpi/ic_launcher.png' },
  { source: 'drawable-xxhdpi-icon.png', target: 'mipmap-xxhdpi/ic_launcher_round.png' },
  { source: 'drawable-xxhdpi-icon.png', target: 'mipmap-xxhdpi/ic_launcher_foreground.png' },
  { source: 'drawable-xxxhdpi-icon.png', target: 'mipmap-xxxhdpi/ic_launcher.png' },
  { source: 'drawable-xxxhdpi-icon.png', target: 'mipmap-xxxhdpi/ic_launcher_round.png' },
  { source: 'drawable-xxxhdpi-icon.png', target: 'mipmap-xxxhdpi/ic_launcher_foreground.png' }
];
const ANDROID_SPLASHES = [
  { source: 'drawable-land-mdpi-screen.png', target: 'drawable/splash.png' }, 
  { source: 'drawable-land-mdpi-screen.png', target: 'drawable-land-mdpi/splash.png' },
  { source: 'drawable-land-hdpi-screen.png', target: 'drawable-land-hdpi/splash.png' },
  { source: 'drawable-land-xhdpi-screen.png', target: 'drawable-land-xhdpi/splash.png' },
  { source: 'drawable-land-xxhdpi-screen.png', target: 'drawable-land-xxhdpi/splash.png' },
  { source: 'drawable-land-xxxhdpi-screen.png', target: 'drawable-land-xxxhdpi/splash.png' },
  { source: 'drawable-port-mdpi-screen.png', target: 'drawable-port-mdpi/splash.png' },
  { source: 'drawable-port-hdpi-screen.png', target: 'drawable-port-hdpi/splash.png' },
  { source: 'drawable-port-xhdpi-screen.png', target: 'drawable-port-xhdpi/splash.png' },
  { source: 'drawable-port-xxhdpi-screen.png', target: 'drawable-port-xxhdpi/splash.png' },
  { source: 'drawable-port-xxxhdpi-screen.png', target: 'drawable-port-xxxhdpi/splash.png' }
];

function copyImages(sourcePath, targetPath, images) {
  for (const icon of images) {
    let source = sourcePath + icon.source;
    let target = targetPath + icon.target;
    fs.copyFile(source, target, err =&gt; {
      if (err) throw err;
      console.log(&lt;code&gt;${source} &gt;&gt; ${target}&lt;/code&gt;);
    });
  }
}

copyImages(SOURCE_IOS_ICON, TARGET_IOS_ICON, IOS_ICONS);
copyImages(SOURCE_IOS_SPLASH, TARGET_IOS_SPLASH, IOS_SPLASHES);

copyImages(SOURCE_ANDROID_ICON, TARGET_ANDROID_ICON, ANDROID_ICONS);
copyImages(SOURCE_ANDROID_SPLASH, TARGET_ANDROID_SPLASH, ANDROID_SPLASHES);</code></pre>
<blockquote>
<p>(c) information : This script comes from this <a href="https://dalezak.medium.com/generate-app-icon-and-splash-screen-images-for-ionic-framework-using-capacitor-e1f8c6ef0fd4">tutorial</a></p>
<p>Then we can add &quot;resources&quot;: &quot;cordova-res ios &amp;&amp; cordova-res android &amp;&amp; node scripts/resources.js&quot; to scripts in package.json : </p>
</blockquote>
<pre><code class="language-json">"scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e",
    "resources": "cordova-res ios &amp;&amp; cordova-res android &amp;&amp; node scripts/resources.js"
  },</code></pre>
<p>And now each time we want to generate our application icons and splashcreen (for different devices screen sizes), we can launch the command:</p>
<pre><code class="language-shell">npm run resources</code></pre>
<p>Now if run again our application, the application icon should have change on your mobile/simulator home screen, and the splashscreen should be the new one. </p>
<div class="page-break"></div>
<h2>Add Splashscreen</h2>
<p>To manage Splashscreen displays, we can install the <strong>Capacitor Splashscreen</strong> plugin</p>
<pre><code class="language-shell">npm install @capacitor/splash-screen
npx cap sync</code></pre>
<blockquote>
<p>Please notice the <strong>sync</strong> keywork since we installed a new plugin.</p>
</blockquote>
<p>Now we will modify the <strong>capacitor.config.ts</strong> file (in <strong>chattuto</strong> folder), to configure some default options:</p>
<pre><code class="language-typescript">import { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  appId: 'com.idevotion.chattuto',
  appName: 'ChatTuto',
  webDir: 'www',
  bundledWebRuntime: false,
  "plugins": {
    "SplashScreen": {
      "launchAutoHide": false,
      "androidScaleType": "CENTER_CROP",
      "androidSpinnerStyle": "large",
      "iosSpinnerStyle": "large",
      "spinnerColor": "#ffffff",
      "showSpinner": true,
      "splashFullScreen": true,
      "splashImmersive": true
    }
  }
};

export default config;</code></pre>
<p>You can learn more about available options in the <a href="https://capacitorjs.com/docs/apis/splash-screen"><strong>Capacitor documentation</strong></a> but one of the most important option is : </p>
<pre><code class="language-json"> "launchAutoHide": false,</code></pre>
<p>By default <strong>Capacitor</strong> will automatically hide the Splashscreen after an amount of time. Using this option let&#8217;s us manage the show/hide of the <strong>Splashscreen</strong> from the code. </p>
<p>In our code, we can have 2 options : the user is unauthenticated (application goes to login page) or is authenticated and the application goes to the <strong>HomePage</strong>. </p>
<p>Let&#8217;s edit these two files, to manage the <strong>Splashscreen</strong> and hide it when our page is ready. It&#8217;s quite easy, first we need to import the <strong>Splashscreen</strong> library</p>
<pre><code class="language-typescript">import { SplashScreen } from '@capacitor/splash-screen';</code></pre>
<p>and then when our view is ready we can hide this splashscreen.</p>
<pre><code class="language-typescript"> ngAfterViewInit() {
    SplashScreen.hide()
  }</code></pre>
<p>To test our code, we need to build our project</p>
<pre><code class="language-shell">ionic build --prod</code></pre>
<p>and then launch again (i&#8217;m testing on iOS)</p>
<pre><code class="language-shell">npx cap copy ios
npx cap run ios</code></pre>
<p>To verify it is working fine, comment the line:</p>
<pre><code> //SplashScreen.hide()</code></pre>
<p>build and run again, and the splashscreen will never hide.</p>
<p>Voila now you know how to manage the <strong>Splashscreen</strong>. Because sometimes it can be useful to let the splashscreen showned while doing some heavy tasks. </p>
<blockquote>
<p>But don&#8217;t remember a good application should show the page as quick as possible and avoid showing a splashscreen for a long time. </p>
<p>This is also the main reason why i&#8217;m hiding the Splashscreen as soon as possible. Because default <strong>Capacitor</strong> time for hiding automatically <strong>Splashscreen</strong> can be 3 seconds so user will wait and see Splashscreeen during 3 seconds even if the app is ready and could be showned within 1 second. </p>
</blockquote>
<div class="page-break"></div>
<h2>Manage Pause or Resume (Background/Foreground) status</h2>
<p>It can be really helpful to know if the application is going to foreground or background state. Foreground means that the user launchs the application, and background that he leaves the application.</p>
<p>To know and observe our application state, we can install the <a href="https://capacitorjs.com/docs/apis/app">Capacitor App</a> plugin.</p>
<pre><code class="language-shell">npm install @capacitor/app
npx cap sync</code></pre>
<p>Now to use it, edit the <strong>app.component.ts</strong> file and import the library</p>
<pre><code class="language-typescript">import { App } from '@capacitor/app';</code></pre>
<p>Then we can create a method to watch the events of the application:</p>
<pre><code class="language-typescript">import { Component } from '@angular/core';
import { App } from '@capacitor/app';

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.scss'],
})
export class AppComponent {

  constructor() {
    this.initApplication()
  }

 initApplication(){

      App.addListener('appStateChange', ({ isActive }) =&gt; {
        console.log('App state changed. Is active?', isActive);
        if (isActive){
          console.log("Application is foreground")
        }
        else{
          console.log("Application is background ")
        }
      });
  }
}</code></pre>
<p>the <strong>isActive</strong> variable tells us if the application is active (meaning foreground) or no (background).</p>
<p>Run the application again and click on the <strong>Home</strong> button of the simulator or your iphone device and you will see in Xcode logs:</p>
<pre><code class="language-javascript">[log] - App state changed. Is active? false
&#x26a1;  [log] - Application is background </code></pre>
<p>Now click again the icon of your application and you will see:</p>
<pre><code class="language-javascript">[log] - App state changed. Is active? true
&#x26a1;  [log] - Application is foreground</code></pre>
<blockquote>
<p>Of course when launching the application for the first time or when the application has been killed by the device os, and launched again, no trace will be displayed because the plugin will be initialize from start and <strong>appState</strong> didn&#8217;t change. </p>
</blockquote>
<p>The same behaviour is also possible using <strong>Platform</strong> and the events <strong>resume</strong> and <strong>pause</strong> which comes from <strong>Ionic</strong>. Let&#8217;s modify our <strong>app.component.ts</strong> file with the following code:</p>
<pre><code class="language-typescript">import { Component } from '@angular/core';
import { App } from '@capacitor/app';
import { Platform } from '@ionic/angular';

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.scss'],
})
export class AppComponent {

  constructor(public platform:Platform) {
    this.initApplication()
  }

  initApplication(){

     //Subscribe on resume
     this.platform.resume.subscribe(() =&gt; {
      console.log("foreground ")
     })

    // Quit app
    this.platform.pause.subscribe(() =&gt; {
      console.log("goes to background ")

    });

      // App.addListener('appStateChange', ({ isActive }) =&gt; {
      //   console.log('App state changed. Is active?', isActive);
      //   if (isActive){
      //     console.log("Application is foreground")
      //   }
      //   else{
      //     console.log("Application is background ")
      //   }
      // });
  }
}</code></pre>
<p>Try again the application and same as before you should see the different states. </p>
<blockquote>
<p>It&#8217;s up to you to decice which method you would like to use. </p>
</blockquote>
<h3>Update User based on Foreground/Background state</h3>
<p>You may wonder why it should be useful to know when the application is in foreground or background. As concrete example, let&#8217;s go back to our <strong>User</strong> model definion in our backend. We have the fields:</p>
<pre><code class="language-python">is_active = models.BooleanField(_('active'), default=True)
lastConnexionDate = models.DateTimeField(null=True, blank=True)</code></pre>
<p>So now by managing application state, we can update these fields to set correct values. </p>
<p>When application launchs the user is active and we can update the <strong>lastConnexionDate</strong> and when the user leaves the application, he is not active anymore.</p>
<p>Here is the updated code of our <strong>app.component.ts</strong> file :</p>
<pre><code class="language-typescript">import { Component } from '@angular/core';
import { App } from '@capacitor/app';
import { Platform } from '@ionic/angular';
import { User } from './models/user';
import { ApiserviceService } from './services/api-service.service';
import { UserManagerServiceService } from './services/user-manager-service.service';

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.scss'],
})
export class AppComponent {

   constructor(public platform:Platform,
    public apiService:ApiserviceService,
    public userService: UserManagerServiceService) {
    // When application launchs for first time
    this.userService.getUser().then((user:User)=&gt;{
        if (user){
          this.setActiveAndLastDate(user)
        }
    })
    this.initApplication()
  }

  initApplication(){
      // App already launches and state change 
      App.addListener('appStateChange', ({ isActive }) =&gt; {
        if (isActive){
          console.log("Application is foreground")
          this.userService.getUser().then((user:User)=&gt;{
            if (user){
              this.setActiveAndLastDate(user)
            }
          })

        }
        else{
          console.log("Application is background ")
          this.userService.getUser().then((user:User)=&gt;{
            if (user){
              this.setInactive(user)
            }
          })
        }
      });
  }

  setActiveAndLastDate(user:User){

  }

  setInactive(user:User){

  }
}</code></pre>
<p>Now we need to write the code of the methods <strong>setActiveAndLastDate</strong> and <strong>setInactive</strong> methods:</p>
<pre><code class="language-typescript"> setActiveAndLastDate(user: User) {
    if (this.apiService.networkConnected) {
      this.apiService.showLoading().then(() =&gt; {
        this.apiService.showLoading().then(() =&gt; {
          let params = {
            "is_active": true,
            "lastConnexionDate": new Date()
          }
          this.apiService.updateUser(user.id, params).subscribe((done) =&gt; {
            this.apiService.stopLoading()
          })
        })
      })
    }
  }

  setInactive(user: User) {
    if (this.apiService.networkConnected) {
      this.apiService.showLoading().then(() =&gt; {
        let params = {
          "is_active": false
        }
        this.apiService.updateUser(user.id, params).subscribe((done) =&gt; {
          this.apiService.stopLoading()
        })
      })
    }
  }</code></pre>
<p>First we check if the network is connected using our <strong>ApiService networkConnected</strong> variable. And if so, we update the <strong>User</strong> with our <strong>updateUser</strong> methods. </p>
<blockquote>
<p>Warning : Modifying the <strong>is_active</strong> field for a <strong>Django user</strong> is really a bad idea, because <strong>Django</strong> will consider that the User is not active anymore (as we asked) and so the User will not be able to login again to the application&#8230; If you want to manage the online/offline presence of the <strong>User</strong> as we will learn later, we should add a new field to <strong>Django User</strong> and not using the <strong>is_active</strong> one. </p>
<p>Please comment the <strong>setInactive</strong> method calls to avoid error with logins. </p>
</blockquote>
<div class="page-break"></div>
<h3>Manage Network events</h3>
<p>The <strong>apiService.networkConnected</strong> is a variable managed by our <strong>ApiService</strong> based on <strong>Network</strong> events. We can study this method now that we know <strong>Capacitor</strong> better. At the beginning of our tutorial, when we created the <strong>Ionic application</strong>, we have installed the <a href="https://capacitorjs.com/docs/apis/network">Capacitor Network</a> library. </p>
<p>When the <strong>ApiService</strong> initialize it calls a <strong>initializeNetworkEvents()</strong> method :</p>
<pre><code class="language-typescript">constructor(public http: HttpClient,
    public loadingController: LoadingController,
    public alertCtrl: AlertController,
    public platform: Platform) {

    this.initializeNetworkEvents();
    this.initProvider(domainConfig.virtual_host, domainConfig.client, "api")
    this.http = http
  }</code></pre>
<p>which is really simple and use the <strong>Capacitor network</strong> plugin to check the status of the network, and update the <strong>networkConnected</strong> variable based on the status received. </p>
<pre><code class="language-typescript"> public async initializeNetworkEvents() {
    console.log("======== Initialise Network Events ======")
    if (this.platform.is("capacitor")) {
      let status = await Network.getStatus();
      if (status["connected"] == false) {
        this.networkConnected = false
        this.updateNetworkStatus(ConnectionStatus.Offline);
      }
      else {
        this.networkConnected = true;
        this.updateNetworkStatus(ConnectionStatus.Online);
      }
      let handler = Network.addListener('networkStatusChange', (status) =&gt; {
        console.log("Network status changed", status);
        if (status["connected"] == false) {
          this.networkConnected = false
          this.updateNetworkStatus(ConnectionStatus.Offline);
        }
        else {
          this.networkConnected = true;
          this.updateNetworkStatus(ConnectionStatus.Online);
        }
      });
    }
    else {
      if (navigator.onLine) {
        this.updateNetworkStatus(ConnectionStatus.Online);
      }
      else {
        this.updateNetworkStatus(ConnectionStatus.Offline);
      }
    }
  }

  private async updateNetworkStatus(status: ConnectionStatus) {
    this.status.next(status);
    this.networkConnected = status == ConnectionStatus.Offline ? false : true;
    console.log("networkConnected " + this.networkConnected)
  }</code></pre>
<div class="page-break"></div>
<h2>Learn to code/debug with hot live reload !</h2>
<p>We learned how to deploy our code to a simulator or a device but on each modification we had to build and copy the code again and again. When developping and testing on a browser (with Ionic serve), on each modification, Ionic detect the change and reload the browser automatically. This behaviour is super useful to develop and debug rapidly. </p>
<p>What about <strong>Capacitor</strong> ? Should it be great if we could develop and debug at same time on our Simulator / Device and/or in our Browser ? </p>
<p>In Fact it is possible with the &#8211;live-reload option. This command is really magic and will improve a lot your development time. </p>
<pre><code class="language-shell">ionic capacitor run ios -l --external</code></pre>
<blockquote>
<p>or <strong>ionic capacitor run android -l &#8211;external</strong> if you want to test on an android mobile device. </p>
</blockquote>
<p>The <strong>-l</strong> option is a shortcut for &#8211;live-reload.</p>
<p>The <strong>&#8211;external</strong> is for using all network interfaces available</p>
<blockquote>
<p>If you are testing on a real device, please be sure that your device is connected and using the same WIFI hotspot than your computer, otherwise the device will be unable to find your ionic server.</p>
</blockquote>
<p>When running <strong>Ionic</strong> with that command, you will be able to test your application:</p>
<ol>
<li>On browser </li>
<li>On iOS</li>
<li>On Android</li>
</ol>
<p>At the same time ! Yes really&#8230; </p>
<blockquote>
<p>You will need a Mac with lot of memory to launch Xcode and Android Studio at the same time (16 Go at least).</p>
</blockquote>
<p>And if you modify your <strong>Ionic</strong> code, <strong>Browser</strong>, <strong>iOS device/simulator</strong> and <strong>Android device/simulator</strong> will refresh automatically.</p>
<p>Wow really really great feature. </p>
<blockquote>
<p>Please notice that sometimes application will not refresh in XCode or Android Studio. Just stop your app running and click the button of your IDE to run it again.</p>
</blockquote>
<p>The source code for this tutorial is available on my <a href="https://github.com/csurbier/chattuto/tree/main/day-seven">GitHub</a> repository.</p>
<div class="page-break"></div>
<h2>Questions/Answers</h2>
<ol>
<li>What is the framework to deploy <strong>Ionic</strong> application on a real device ?<br />
<blockquote>
<p>Capacitor</p>
</blockquote>
</li>
<li>If we want to access native functionality what should we used ?<br />
<blockquote>
<p>Existing Capacitor plugins (<a href="https://capacitorjs.com/docs/apis">https://capacitorjs.com/docs/apis</a>) or Community plugin (<a href="https://capacitorjs.com/docs/plugins/community">https://capacitorjs.com/docs/plugins/community</a>) or even develop our own native plugin (<a href="https://capacitorjs.com/docs/plugins/creating-plugins">https://capacitorjs.com/docs/plugins/creating-plugins</a>)</p>
</blockquote>
</li>
<li>What is the command to update our code in the native project (ios and android) (many answers possible)<br />
<blockquote>
<p>npx cap copy or npx cap sync or npx cap copy ios or npx cap copy android or npx cap sync ios or npx cap sync android </p>
</blockquote>
</li>
<li>With <strong>Ionic</strong> coupled to <strong>Capacitor</strong> it is possible to develop and test a PWA (progressive web app) and native application at the same time.  True or False ?<br />
<blockquote>
<p>True </p>
</blockquote>
</li>
</ol>
<div class="page-break"></div>
<p>The post <a rel="nofollow" href="https://www.ionicanddjangotutorial.com/day-7-deploy-on-simulator-or-mobile-device/">Day 7 : Deploy on simulator or mobile device</a> appeared first on <a rel="nofollow" href="https://www.ionicanddjangotutorial.com">Ionic and Django Tutorial</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Day 6 : Improve signin/signup process and application workflow</title>
		<link>https://www.ionicanddjangotutorial.com/day-6-improve-signin-signup-process-and-application-workflow/</link>
		
		<dc:creator><![CDATA[Christophe Surbier]]></dc:creator>
		<pubDate>Fri, 13 Aug 2021 15:33:19 +0000</pubDate>
				<category><![CDATA[Create a real world mobile chat application with Ionic and Django]]></category>
		<guid isPermaLink="false">https://www.ionicanddjangotutorial.com/?p=1326</guid>

					<description><![CDATA[<p>Create a chat application with Ionic 5/ Angular 12/ Capacitor 3 and Django 3 &#8211; Series &#8211; Part six Day&#8230; <a href="https://www.ionicanddjangotutorial.com/day-6-improve-signin-signup-process-and-application-workflow/" class="more-link">Continue Reading <span class="meta-nav">&#8594;</span></a></p>
<p>The post <a rel="nofollow" href="https://www.ionicanddjangotutorial.com/day-6-improve-signin-signup-process-and-application-workflow/">Day 6 : Improve signin/signup process and application workflow</a> appeared first on <a rel="nofollow" href="https://www.ionicanddjangotutorial.com">Ionic and Django Tutorial</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h1>Create a chat application with Ionic 5/ Angular 12/ Capacitor 3 and Django 3 &#8211; Series &#8211; Part six</h1>
<h1>Day 6 : Improve signin/signup process and application workflow</h1>
<p>On previous tutorial, we learned how to register or signin to our application and made some modifications to pass the JWT Token to methods. Now we will learn how to improve the code and use Angular guards, to automatically add the jWT access token to our requests. </p>
<p>But first, we will remove the menu generated by Ionic template, which displays on the left side when navigating on web mode.</p>
<p>Let&#8217;s edit the <strong>src/app/app.component.html</strong> file to remove the <strong>ion-split-plane</strong> component and just keep the following code:</p>
<pre><code class="language-html">&lt;ion-app&gt;
    &lt;ion-router-outlet id="main-content"&gt;&lt;/ion-router-outlet&gt;
&lt;/ion-app&gt;</code></pre>
<p>Now we can edit the <strong>src/app/app.component.ts</strong> file and remove the variables <strong>appPages</strong> and <strong>label</strong>:</p>
<pre><code class="language-typescript">import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.scss'],
})
export class AppComponent {

  constructor() {}
}</code></pre>
<p>With these modifications, if you refresh your browser (<a href="http://localhost:8100/login-page">http://localhost:8100/login-page</a>), the login page or register page should take the full width of your browser with no menu on the left side. </p>
<h2>Add JWT access token automatically with Angular Http interceptors</h2>
<p>Angular provides a nice component called <a href="https://angular.io/api/common/http/HttpInterceptor">HttpInterceptor</a> which intercepts any <strong>HttpRequest</strong> or <strong>HttpResponse</strong> to let us do some treatment before passing the request to the next usual process. </p>
<p>For example, we can edit the <strong>src/app/app.module.ts</strong> file and add the following code :</p>
<pre><code class="language-typescript">@Injectable()
export class AngularInterceptor implements HttpInterceptor {

  constructor(){
  }

  intercept(req: HttpRequest&lt;any&gt;, next: HttpHandler): Observable&lt;HttpEvent&lt;any&gt;&gt; {
    let defaultTimeout = 10000;

      return next.handle(req).pipe(timeout(defaultTimeout),
        retryWhen(err=&gt;{
          let retries = 1;
          return err.pipe(
            delay(500),
            map(error=&gt;{
              if (retries++ ===3){
                throw error
              }
              return error;
            })
          )
        }),catchError(err=&gt;{
          console.log(err)
          return EMPTY
        }), finalize(()=&gt;{

        })
      )
    };    
}</code></pre>
<p>To declare an <strong>HttpInterceptor</strong> and to use it we need to add the following code:</p>
<pre><code class="language-typescript"> { provide: HTTP_INTERCEPTORS, useClass: AngularInterceptor, multi: true } </code></pre>
<div class="page-break"></div>
<p>in our <strong>providers</strong> array list. Here is the full code of the  <strong>src/app/app.module.ts</strong> file:</p>
<pre><code class="language-typescript"> import { Injectable, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { HttpClient, HttpClientModule, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HTTP_INTERCEPTORS } from '@angular/common/http';
import { InAppBrowser } from '@ionic-native/in-app-browser/ngx';
import { IonicStorageModule } from '@ionic/storage-angular';
import { Observable, EMPTY } from 'rxjs';
import { timeout, retryWhen, delay, map, catchError, finalize } from 'rxjs/operators';

@Injectable()
export class AngularInterceptor implements HttpInterceptor {

  constructor(){
  }

  intercept(req: HttpRequest&lt;any&gt;, next: HttpHandler): Observable&lt;HttpEvent&lt;any&gt;&gt; {
    let defaultTimeout = 10000;

      return next.handle(req).pipe(timeout(defaultTimeout),
        retryWhen(err=&gt;{
          let retries = 1;
          return err.pipe(
            delay(500),
            map(error=&gt;{
              if (retries++ ===3){
                throw error
              }
              return error;
            })
          )
        }),catchError(err=&gt;{
          console.log(err)
          return EMPTY
        }), finalize(()=&gt;{

        })
      )
    };    
}

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [
    IonicStorageModule.forRoot(),
    BrowserModule, 
    HttpClientModule,
    IonicModule.forRoot(), 
    AppRoutingModule
  ],
  providers: [
    InAppBrowser,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
    { provide: HTTP_INTERCEPTORS, useClass: AngularInterceptor, multi: true } 
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}</code></pre>
<p>This <strong>HttpInterceptor</strong> will be called at the start of every http request to add a default timeout of 10 secondes for the request, and if the request failed it will try again 3 times with 500ms between each request. Then after 3 times the request will failed.</p>
<p>It&#8217;s quite simple to understand the mecanism. </p>
<div class="page-break"></div>
<p>So now we will create a new file  <strong>src/app/services/token.interceptor.ts</strong> file to manage our <strong>JWT token</strong> with the following code: </p>
<pre><code class="language-typescript"> import { UserManagerServiceService } from './user-manager-service.service';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';

import { BehaviorSubject, Observable, throwError } from 'rxjs';
import { catchError, filter, switchMap, take } from 'rxjs/operators';
import { AuthenticationService } from './authentication.service';

@Injectable()
export class TokenInterceptor implements HttpInterceptor {
  private refreshingInProgress: boolean;
  private accessTokenSubject: BehaviorSubject&lt;string&gt; = new BehaviorSubject&lt;string&gt;(null);

  constructor(private authService: AuthenticationService,
                private userManager:UserManagerServiceService,
              private router: Router) {}

  intercept(req: HttpRequest&lt;any&gt;, next: HttpHandler): Observable&lt;HttpEvent&lt;any&gt;&gt; {
    const accessToken = this.authService.token;
    //console.log("=== Intercept token ")
    // console.log(accessToken)
    return next.handle(this.addAuthorizationHeader(req, accessToken)).pipe(
      catchError(err =&gt; {
        // in case of 401 http error
        console.log("====MODIF ERROR url:"+req.url)
        console.log("====STATUS:"+err.status)
        if (req.url.indexOf("/devices")&gt;0){
          return throwError(err);
        }
        if (req.url.indexOf("/refresh")&gt;0){
          //REFRESHTOKEN KO 
          console.log("==== 401 LOGOUT CAR ERROR")
          // otherwise logout and redirect to login page
          return this.logoutAndRedirect(err);
        }
        if (err instanceof HttpErrorResponse &amp;&amp; err.status === 401) {
          // get refresh tokens

          console.log("===401 ask for a new TOKEN using the refresh token ============")
          const refreshToken = this.authService.refresh;

          // if there are tokens then send refresh token request
          if (refreshToken &amp;&amp; accessToken) {
            return this.refreshToken(req, next);
          }
          console.log("No refresh or access token. Logout user ",req.url)
          // otherwise logout and redirect to login page
          return this.logoutAndRedirect(err);
        }

        // in case of 403 http error (refresh token failed)
        if (err instanceof HttpErrorResponse &amp;&amp; err.status === 403) {
          // logout and redirect to login page
          console.log("Error 403 LOGOuT ",req.url)
          return this.logoutAndRedirect(err);
        }
        // if error has status neither 401 nor 403 then just return this error
        return throwError(err);
      })
    );
  }

  private addAuthorizationHeader(request: HttpRequest&lt;any&gt;, token: string): HttpRequest&lt;any&gt; {
    if (token) {
        let bearer = &lt;code&gt;Bearer ${token}&lt;/code&gt;;
      ///  console.log("=== Clone request et add header avec token ",bearer)

      return request.clone({setHeaders: {Authorization: bearer}});
    }
    return request;
  }

  private logoutAndRedirect(err): Observable&lt;HttpEvent&lt;any&gt;&gt; {
    console.log("==== LOGOUT")
    this.userManager.logoutUser().then(()=&gt;{

    })
    this.router.navigateByUrl('/login');
    return throwError(err);
  }

  private refreshToken(request: HttpRequest&lt;any&gt;, next: HttpHandler): Observable&lt;HttpEvent&lt;any&gt;&gt; {
    if (!this.refreshingInProgress) {
      this.refreshingInProgress = true;
      this.accessTokenSubject.next(null);

      return this.authService.refreshToken().pipe(
        switchMap((res) =&gt; {
          this.refreshingInProgress = false;
          this.authService.token = res["access"]
          this.authService.refresh = res["refresh"]
          console.log("==== REFRESH SET VALUES")
          this.accessTokenSubject.next(res["access"]);
          // repeat failed request with new token
          return next.handle(this.addAuthorizationHeader(request, res["access"]));
        }), catchError((err, caught) =&gt; {
            console.log("=========== ERROR REFRESH TOKEN")
           // return throwError(err);
            return this.logoutAndRedirect(err);
          })
      );
    } else {
      // wait while getting new token
      return this.accessTokenSubject.pipe(
        filter(token =&gt; token !== null),
        take(1),
        switchMap(token =&gt; {
          // repeat failed request with new token
          return next.handle(this.addAuthorizationHeader(request, token));
        }));
    }
  }
}</code></pre>
<p>and then edit our <strong>app.module.ts</strong> to use this new HttpInterceptor class:</p>
<pre><code class="language-typescript">  { provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true } </code></pre>
<blockquote>
<p>We can remove the <strong>HttpInterceptor</strong> added as example. </p>
</blockquote>
<div class="page-break"></div>
<p>Full code should be now:</p>
<pre><code class="language-typescript">import { Injectable, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { HttpClient, HttpClientModule, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HTTP_INTERCEPTORS } from '@angular/common/http';
import { InAppBrowser } from '@ionic-native/in-app-browser/ngx';
import { IonicStorageModule } from '@ionic/storage-angular';
import { TokenInterceptor } from './services/token.interceptor';

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [
    IonicStorageModule.forRoot(),
    BrowserModule, 
    HttpClientModule,
    IonicModule.forRoot(), 
    AppRoutingModule
  ],
  providers: [
    InAppBrowser,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
    { provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true } 
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}</code></pre>
<p>Ok it&#8217;s a lot of code. The <strong>TokenInterceptor</strong> class is quite complex (you can read more about it in this <a href="https://valor-software.com/articles/json-web-token-authorization-with-access-and-refresh-tokens-in-angular-application-with-node-js-server.html">article</a>, which is used and adapted to our Ionic application).</p>
<div class="page-break"></div>
<p>First our interceptor will automatically add the <strong>JWT token</strong> (that it gets from our <strong>AuthenticationService</strong>) to each request:</p>
<pre><code class="language-typescript">  const accessToken = this.authService.token;
  return next.handle(this.addAuthorizationHeader(req, accessToken))</code></pre>
<p>using this method:</p>
<pre><code class="language-typescript">  private addAuthorizationHeader(request: HttpRequest&lt;any&gt;, token: string): HttpRequest&lt;any&gt; {
    if (token) {
        let bearer = &lt;code&gt;Bearer ${token}&lt;/code&gt;;
      ///  console.log("=== Clone request et add header avec token ",bearer)

      return request.clone({setHeaders: {Authorization: bearer}});
    }
    return request;
  }</code></pre>
<p>And then using a pipe will analyze the <strong>HttpResponse</strong> to check Http status code. </p>
<p>If the status code is 401 it means the token has expired and will try to refresh it using the <strong>JWT refresh token</strong> except if the status 401 is from the request with url path <strong>/refresh</strong> which means our <strong>JWT refresh</strong> token has expired too. In that case we have no choice to logout the user, to ask him again it&#8217;s credentials.</p>
<p>If the status code is 403, it means credentials are not valid anymore and again we have no choice to logout the user, to ask him again it&#8217;s credentials.</p>
<blockquote>
<p>If you don&#8217;t understand the full code, it&#8217;s ok you can just used it as it is in your application. You just need to know it will manage <strong>JWT authentication</strong> for you. </p>
</blockquote>
<p>Now we can modify the code to remove the <strong>accessToken</strong> parameters we passed to our <strong>User ApiService methods</strong>:</p>
<p><strong>src/app/services/api-service.service.ts</strong> file : </p>
<pre><code class="language-typescript">  // Create a user 
  createUser(modelToCreate) {
    // model JSON
    const options = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json'
      })
    };
    console.log("URL " + this.getUserUrl)
    return this.http.post(this.getUserUrl, modelToCreate, options).pipe(retry(1))
  }

  //Find user based on parameters
  findUserWithQuery(query) {
    let url = this.getUserUrl + query;
    return this.findUser(url)
  }

  private findUser(url) {
    const options = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json'
      })
    };

    return Observable.create(observer =&gt; {
      this.http.get(url, options)
        .pipe(retry(1))
        .subscribe(res =&gt; {
          observer.next(res);
          observer.complete();
        }, error =&gt; {
          observer.next();
          observer.complete();
          console.log(error);// Error getting the data
        });
    });
  }

  getUserDetails(id) {
    const options = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json'
      })
    };
    return Observable.create(observer =&gt; {
      this.http.get(this.getUserUrl + id + "/", options)
        .pipe(retry(1))
        .subscribe(res =&gt; {
          this.networkConnected = true
          observer.next(res);
          observer.complete();
        }, error =&gt; {
          observer.next(false);
          observer.complete();
          console.log(error);// Error getting the data
        });
    });
  }

  updateUser(id, patchParams) {
    const options = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json'
      })
    };
    return Observable.create(observer =&gt; {
      this.http.patch(this.getUserUrl + id + "/", patchParams, options)
        .pipe(retry(1))
        .subscribe(res =&gt; {
          this.networkConnected = true
          observer.next(true);
          observer.complete();
        }, error =&gt; {
          observer.next(false);
          observer.complete();
          console.log(error);// Error getting the data
        });
    });
  }

  putUser(object) {
    const options = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json'

      })
    };
    return Observable.create(observer =&gt; {
      this.http.put(this.getUserUrl + object.id + "/", object, options)
        .pipe(retry(1))
        .subscribe(res =&gt; {
          this.networkConnected = true
          observer.next(true);
          observer.complete();
        }, error =&gt; {
          observer.next(false);
          observer.complete();
          console.log(error);// Error getting the data
        });
    });
  }

  deleteUser(id) {
    const options = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json'
      })
    };
    return this.http.delete(this.getUserUrl + id + "/", options).pipe(retry(1))
  }

}</code></pre>
<p>and adapt our <strong>Login</strong> and <strong>Register</strong> pages, to remove the <strong>accessToken</strong> parameter from the call:</p>
<pre><code class="language-typescript">this.apiService.findUserWithQuery("?email="+email).subscribe((list) =&gt; {</code></pre>
<pre><code class="language-typescript">this.apiService.updateUser(this.userManager.currentUser.id, updateParams).subscribe((done) =&gt; {</code></pre>
<p>If you run again your <strong>Ionic</strong> application and try to login or register again, it should work which means our HttpInterceptor is working fine and added the token to our requests.</p>
<div class="page-break"></div>
<h2>Auto login with Angular guards</h2>
<p>Once registered or logged in, we would like to go to our main page named home. Let&#8217;s create it:</p>
<pre><code class="language-shell">ionic g page HomePage
mv src/app/home-page src/app/pages </code></pre>
<p>and let&#8217;s modify the <strong>app.routing.module.ts</strong> file as we learned to reflect the path change:</p>
<pre><code class="language-typescript">,
  {
    path: 'home-page',
    loadChildren: () =&gt; import('./pages/home-page/home-page.module').then( m =&gt; m.HomePagePageModule)
  },</code></pre>
<p>At this stage, our declared routes should be :</p>
<pre><code class="language-typescript">const routes: Routes = [
  {
    path: '',
    redirectTo: 'login-page',
    pathMatch: 'full'
  },
  {
    path: 'register-page',
    loadChildren: () =&gt; import('./pages/register-page/register-page.module').then( m =&gt; m.RegisterPagePageModule)
  },
  {
    path: 'login-page',
    loadChildren: () =&gt; import('./pages/login-page/login-page.module').then( m =&gt; m.LoginPagePageModule)
  },
  {
    path: 'home-page',
    loadChildren: () =&gt; import('./pages/home-page/home-page.module').then( m =&gt; m.HomePagePageModule)
  },

];</code></pre>
<p><strong>login-page</strong>, <strong>register-page</strong> and <strong>home-page</strong> which correspond to the url <strong>/login-page/</strong>, <strong>/register-page/</strong> and <strong>/home-page/</strong>.</p>
<p>To change the urls path and remove the <strong>-page</strong>, we could rewrite our routes to:</p>
<pre><code class="language-typescript">const routes: Routes = [
  {
    path: '',
    redirectTo: 'login',
    pathMatch: 'full'
  },
  {
    path: 'register',
    loadChildren: () =&gt; import('./pages/register-page/register-page.module').then( m =&gt; m.RegisterPagePageModule)
  },
  {
    path: 'login',
    loadChildren: () =&gt; import('./pages/login-page/login-page.module').then( m =&gt; m.LoginPagePageModule)
  },
  {
    path: 'home',
    loadChildren: () =&gt; import('./pages/home-page/home-page.module').then( m =&gt; m.HomePagePageModule)
  },

];</code></pre>
<p>Ok now as you should have notice, each time you will launch <strong>Ionic</strong>, you will be redirected to the <strong>Login</strong> page because of our default route :</p>
<pre><code class="language-typescript"> {
    path: '',
    redirectTo: 'login',
    pathMatch: 'full'
  },</code></pre>
<p>But if the user is already login or registered, we should go to the <strong>HomePage</strong>. To do that, we will use another <strong>Angular</strong> functionality which is <a href="https://angular.io/guide/router#preventing-unauthorized-access"><strong>Guards</strong></a>.</p>
<p>We will use the <strong>canLoad</strong> instruction. Let&#8217;s create a new directory and ask <strong>Ionic</strong> to create our class:</p>
<pre><code class="language-shell">mkdir src/app/guards
ionic g guard guards/autoLogin --implements CanLoad</code></pre>
<p>The class will be called <strong>AutoLoginGuard</strong>. Let&#8217;s replace the generated code with our own code implementation:</p>
<div class="page-break"></div>
<pre><code class="language-typescript">import { Injectable } from '@angular/core';
import { CanLoad, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthenticationService } from '../services/authentication.service';
import { filter, map, take } from 'rxjs/operators';
import { User } from '../models/user';
import { UserManagerServiceService } from 'src/app/services/user-manager-service.service';

@Injectable({
  providedIn: 'root'
})
export class AutoLoginGuard implements CanLoad {
  constructor(private authService: AuthenticationService, private router: Router,private userManager:UserManagerServiceService) {

   }

  canLoad(): Observable&lt;boolean&gt; {    
    return this.authService.isAuthenticated.pipe(
      filter(val =&gt; val !== null), // Filter out initial Behaviour subject value
      take(1), // Otherwise the Observable doesn't complete!
      map(isAuthenticated =&gt; {
        console.log('Found previous token ?, automatic login '+isAuthenticated);
        if (isAuthenticated) {
          //Load user 
          this.userManager.getUser().then((user:User)=&gt;{
           if (user){
              this.router.navigateByUrl('/home', { replaceUrl: true });
            }
            else{
              console.log("=== No user")
              this.authService.isAuthenticated.next(false);
              this.router.navigateByUrl('/login', { replaceUrl: true });
              return true;
            }
          })
        } else {          
          return true;
        }
      })
    );
  }
}</code></pre>
<p>In the <strong>canLoad</strong> method, we use our <strong>AuthenticationService</strong> to check if we are authenticated and if we can found a user, and if so we can redirect to our <strong>HomePage</strong> otherwise the user is not authenticated and will be redirected to the login page.</p>
<p>We just need to modify our routes, to add this <strong>canLoad</strong> instruction:</p>
<pre><code class="language-typescript">import { NgModule } from '@angular/core';
import { PreloadAllModules, RouterModule, Routes } from '@angular/router';
import { AutoLoginGuard } from './guards/auto-login.guard';

const routes: Routes = [
  {
    path: '',
    redirectTo: 'login',
    pathMatch: 'full'
  },
  {
    path: 'register',
    canLoad: [AutoLoginGuard],
    loadChildren: () =&gt; import('./pages/register-page/register-page.module').then( m =&gt; m.RegisterPagePageModule)
  },
  {
    path: 'login',
    canLoad: [AutoLoginGuard],
    loadChildren: () =&gt; import('./pages/login-page/login-page.module').then( m =&gt; m.LoginPagePageModule)
  },
  {
    path: 'home',
    loadChildren: () =&gt; import('./pages/home-page/home-page.module').then( m =&gt; m.HomePagePageModule)
  },

];

@NgModule({
  imports: [
    RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })
  ],
  exports: [RouterModule]
})
export class AppRoutingModule {}</code></pre>
<p>And voila, if you run your <strong>Ionic</strong> application again </p>
<pre><code class="language-shell">ionic serve</code></pre>
<p>You should be redirected to the <strong>HomePage</strong> automatically (if you had register or login before).</p>
<h3>Cleaning Ionic cache in the browser</h3>
<p>To verify the behaviour is correct, we can cleaned our application cache using the <strong>developer tools</strong> of the browser:<br />
<img src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/08/Capture-décran-2021-08-05-à-15.32.14.png" alt="CleanIonic" /></p>
<p>Just click the <strong>Clear site data</strong> button, and then refresh the page or go to the default Ionic url (<a href="http://localhost:8100/">http://localhost:8100/</a>) and this time, you should see the <strong>LoginPage</strong>.</p>
<p>You could try to login again, and once again call the default ionic url (<a href="http://localhost:8100/">http://localhost:8100/</a>), you should be redirected to the <strong>HomePage</strong></p>
<blockquote>
<p>Now that we have an <strong>HomePage</strong> we could modify our <strong>LoginPage</strong> and <strong>RegisterPage</strong> to redirect to this page using the <strong>router.navigateByUrl</strong> method: </p>
<pre><code class="language-typescript">// Next screen
console.log("===Can go to next screen")
this.router.navigateByUrl('/home', { replaceUrl: true });</code></pre>
</blockquote>
<h2>Create and protect our application Home page</h2>
<p>We just saw how to automatically redirect the user to the <strong>HomePage</strong> once authenticated. However we didn&#8217;t protect the <strong>HomePage</strong> from unathorized access. Once again clean your application cache as we learned previously, and just go to the url : <a href="http://localhost:8100/home">http://localhost:8100/home</a>.</p>
<p>The <strong>HomePage</strong> will appear even if the user is not registered or logged in (we just clean the cache remember.)</p>
<p>To avoid this behaviour and protect all the pages of our application, we will create another Angular Guard class named <strong>AuthGuard</strong> :</p>
<pre><code class="language-shell">ionic g guard guards/auto --implements CanLoad</code></pre>
<p>and modify our <strong>app-routing.module.ts</strong> to add the <strong>canLoad</strong> instruction to our <strong>HomePage</strong>:</p>
<pre><code class="language-typescript">import { NgModule } from '@angular/core';
import { PreloadAllModules, RouterModule, Routes } from '@angular/router';
import { AutoLoginGuard } from './guards/auto-login.guard';
import { AutoGuard } from './guards/auto.guard';

const routes: Routes = [
  {
    path: '',
    redirectTo: 'login',
    pathMatch: 'full'
  },
  {
    path: 'register',
    canLoad: [AutoLoginGuard],
    loadChildren: () =&gt; import('./pages/register-page/register-page.module').then( m =&gt; m.RegisterPagePageModule)
  },
  {
    path: 'login',
    canLoad: [AutoLoginGuard],
    loadChildren: () =&gt; import('./pages/login-page/login-page.module').then( m =&gt; m.LoginPagePageModule)
  },
  {
    path: 'home',
    canLoad:[AutoGuard],
    loadChildren: () =&gt; import('./pages/home-page/home-page.module').then( m =&gt; m.HomePagePageModule)
  },

];

@NgModule({
  imports: [
    RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })
  ],
  exports: [RouterModule]
})
export class AppRoutingModule {}</code></pre>
<p>Now we can edit our <strong>auto.guard.ts</strong> file to check if the user is authenticated or not:</p>
<pre><code class="language-typescript">import { AuthenticationService } from './../services/authentication.service';
import { Injectable, NgZone } from '@angular/core';
import { CanLoad, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { filter, map, take } from 'rxjs/operators';
@Injectable({
  providedIn: 'root'
})
export class AutoGuard implements CanLoad {
  constructor(private authService: AuthenticationService,private router: Router) { }

  canLoad(): Observable&lt;boolean&gt; {    
    return this.authService.isAuthenticated.pipe(
      filter(val =&gt; val !== null), // Filter out initial Behaviour subject value
      take(1), // Otherwise the Observable doesn't complete!
      map(isAuthenticated =&gt; {
        if (isAuthenticated) {  
          return true;
        } else {          
          this.router.navigateByUrl('/login', { replaceUrl: true })
          return false;
        }
      })
    );
  }
}</code></pre>
<p>This code will check if the user is authenticated and if not will redirect it to the <strong>LoginPage</strong>.</p>
<p>Try again to call the url <a href="http://localhost:8100/home">http://localhost:8100/home</a> and you will see the <strong>LoginPage</strong> redirection. </p>
<p>The source code for this tutorial is available on my <a href="https://github.com/csurbier/chattuto/tree/main/day-six">GitHub</a> repository.</p>
<div class="page-break"></div>
<h2>Questions / Answers</h2>
<ol>
<li>
<p>How to add custom code behaviour to HTTP request or response ?</p>
<blockquote>
<p>Use Angular Http Interceptor </p>
</blockquote>
</li>
<li>How to add specific code behaviour to be called before Ionic routes to urls ?<br />
<blockquote>
<p>Use Angular guards such as <strong>canLoad</strong> </p>
</blockquote>
</li>
</ol>
<div class="page-break"></div>
<p>The post <a rel="nofollow" href="https://www.ionicanddjangotutorial.com/day-6-improve-signin-signup-process-and-application-workflow/">Day 6 : Improve signin/signup process and application workflow</a> appeared first on <a rel="nofollow" href="https://www.ionicanddjangotutorial.com">Ionic and Django Tutorial</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Day 5 : Registering &#8211; Sign in users in our Ionic application</title>
		<link>https://www.ionicanddjangotutorial.com/day-5-registering-sign-in-users-in-our-ionic-application/</link>
		
		<dc:creator><![CDATA[Christophe Surbier]]></dc:creator>
		<pubDate>Fri, 13 Aug 2021 15:32:57 +0000</pubDate>
				<category><![CDATA[Create a real world mobile chat application with Ionic and Django]]></category>
		<guid isPermaLink="false">https://www.ionicanddjangotutorial.com/?p=1324</guid>

					<description><![CDATA[<p>Create a chat application with Ionic 5/ Angular 12/ Capacitor 3 and Django 3 &#8211; Series &#8211; Part five Day&#8230; <a href="https://www.ionicanddjangotutorial.com/day-5-registering-sign-in-users-in-our-ionic-application/" class="more-link">Continue Reading <span class="meta-nav">&#8594;</span></a></p>
<p>The post <a rel="nofollow" href="https://www.ionicanddjangotutorial.com/day-5-registering-sign-in-users-in-our-ionic-application/">Day 5 : Registering &#8211; Sign in users in our Ionic application</a> appeared first on <a rel="nofollow" href="https://www.ionicanddjangotutorial.com">Ionic and Django Tutorial</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h1>Create a chat application with Ionic 5/ Angular 12/ Capacitor 3 and Django 3 &#8211; Series &#8211; Part five</h1>
<h1>Day 5 : Registering &#8211; Sign in users in our Ionic application</h1>
<p>In previous day, we saw how to create a register page and how to register a user using our backend API. </p>
<h2>Updating user profil</h2>
<p>Now to complete the registration process, we need to update the created user with all the fields filled in the form (last name, first name), and need to get a <strong>JWT access token</strong>.</p>
<p>First we will create another service class to deal with some common methods to manage a user:</p>
<pre><code class="language-shell">ionic g service UserManagerService</code></pre>
<p>and then we will move the created service in the <strong>services</strong> directory:</p>
<pre><code class="language-shell">mv src/app/user-manager-service.service.* src/app/services</code></pre>
<p>We will create an new file in the directory <strong>src/app/models/</strong> named <strong>user.ts</strong> and will write the following code:</p>
<pre><code class="language-typescript">export class User {
    fcmdevice:any;
    password:string;
    last_login:string;
    is_superuser:any;
    id:any;
    email:string;
    first_name:string;
    last_name:string;
    date_joined:Date;
    is_active:boolean;
    is_staff:boolean;
    avatar:string;
    groups:any;
    user_permissions:any;
    lastConnexionDate : Date;
    valid:boolean;

    constructor() {

    }

    initWithJSON(json) : User{
      for (var key in json) {
          this[key] = json[key];
      }
      return this;
    }
}</code></pre>
<p>This code is the <strong>User</strong> typescript object (similar to our <strong>User</strong> class in Django), which will be more easy to manipulate rather than dealing with JSON object.</p>
<p>To store objects (such as our <strong>User</strong>) on the disk, we will install <strong>Ionic storage</strong> library:</p>
<pre><code class="language-shell">npm install @ionic/storage-angular</code></pre>
<p>and then add it in our <strong>/src/app/app.module.ts</strong> file: </p>
<pre><code class="language-typescript">import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { InAppBrowser } from '@ionic-native/in-app-browser/ngx';
import { IonicStorageModule } from '@ionic/storage-angular';

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [
    IonicStorageModule.forRoot(),
    BrowserModule, 
    HttpClientModule,
    IonicModule.forRoot(), 
    AppRoutingModule
  ],
  providers: [
    InAppBrowser,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}</code></pre>
<p>Now in our new <strong>src/app/services/user-manager-service.service.ts</strong> file we will add the following code:</p>
<pre><code class="language-typescript">import { Injectable } from '@angular/core';
import { Platform } from '@ionic/angular';
import { User } from '../models/user';
import { ApiserviceService } from './api-service.service';
import { Storage } from '@ionic/storage-angular';

@Injectable({
  providedIn: 'root'
})
export class UserManagerServiceService  {

  currentUser: User;

  constructor(public storage: Storage, 
    public apiService: ApiserviceService,
     public platform: Platform) {
    console.log('-------- INIT UserManagerProviderService Provider --------- ');
    this.initStorage()
  }

  async initStorage() {
    console.log("==== INIT STORAGE ==========")
    // If using a custom driver:
    // await this.storage.defineDriver(MyCustomDriver)
    await this.storage.create();
  }

  saveUser() {
    this.storage.set(this.apiService.appName + '_currentUser', this.currentUser);
  }

  setUser(user) {
    return new Promise(resolve =&gt; {
        let aUser = new User().initWithJSON(user);
        this.currentUser = aUser;
        console.log('User ID ' + aUser.id + ' email ' + aUser.email);
       this.storage.set(this.apiService.appName + '_currentUser', user).then(()=&gt;{
          resolve(true)
        })
     });
  }

  getUser() {
    return new Promise(resolve =&gt; {
      this.storage.get(this.apiService.appName + '_currentUser').then((result) =&gt; {
        console.log(result)
        if (result) {
          let aUser = new User().initWithJSON(result);
          this.currentUser = aUser;
          resolve(this.currentUser)
        }
        else {
          this.currentUser=null
          resolve(null)
        }
      });
    });
  }
}</code></pre>
<p>Basically, we added methods to save a user on the disk, to get a previously saved user from the disk, and a method to set the user object in memory. </p>
<div class="page-break"></div>
<p>Before moving forward and write the code to finish the registration process, we need to create another service to deals with authentication and <strong>JWT token</strong>. </p>
<pre><code class="language-shell">ionic g service Authentication 
mv src/app/authentication.service.* src/app/services </code></pre>
<p>Then we can edit our <strong>src/app/services/authentication.service.ts</strong> file to add methods to load existing JWT access token from disk, login or logout the user, and refresh the <strong>JWT access token</strong> :</p>
<pre><code class="language-typescript">import { Injectable } from '@angular/core';
import { BehaviorSubject, from, Observable, Subject } from 'rxjs';
import { ApiserviceService } from './api-service.service';

const TOKEN_KEY = 'access';
const REFRESH_TOKEN_KEY = 'refresh';

@Injectable({
  providedIn: 'root'
})
export class AuthenticationService {
  // Init with null to filter out the first value in a guard!
  isAuthenticated: BehaviorSubject&lt;boolean&gt; = new BehaviorSubject&lt;boolean&gt;(null);
  token = '';
  refresh=''
  constructor(private apiService:ApiserviceService) {
    this.loadToken();
  }

  loadToken() {
    this.apiService.getLocalData(TOKEN_KEY).then((value:string)=&gt;{
      if (value){
        console.log('set token: ', value);
        this.token = value["access"];
        this.apiService.getLocalData(REFRESH_TOKEN_KEY).then((value:string)=&gt;{
          this.refresh=value["refresh"]
          this.isAuthenticated.next(true);
        })       
      }
      else{
        this.isAuthenticated.next(false);
      }
    })

  }

  //Get a JWT access token from provided credentials
  login(params){
    return new Promise(async resolve =&gt; {
    this.apiService.login(params).subscribe((resultat)=&gt;{
       if (resultat) {
         console.log("Login result ",resultat)
          let accessToken = resultat["access"]
          let refreshToken = resultat["refresh"]
          this.apiService.setLocalData("access",{"access":accessToken})
          this.apiService.setLocalData("refresh",{"refresh":refreshToken})
          this.token = accessToken;
          this.refresh=refreshToken
          this.isAuthenticated.next(true);

         resolve(true)
        }
      else{
        resolve(false)
      }
      })
    })
  }

  logout(): Promise&lt;void&gt; {
    this.isAuthenticated.next(false);
    return new Promise(async resolve =&gt; {
       this.apiService.removeLocalData(TOKEN_KEY).then(()=&gt;{
          this.apiService.removeLocalData(REFRESH_TOKEN_KEY).then(()=&gt;{
            resolve()
          }
        )
       });
    })
  }

  refreshToken(){
    return this.apiService.refreshToken(this.refresh)
  }
}</code></pre>
<p>Ok so now we are be able to modify our <strong>src/pages/register-page/register-page.page.ts</strong> file to include these new methods.</p>
<p>First we will modify the constructor of our class to include the new services <strong>UserManagerServiceService</strong> and <strong>AuthenticationService</strong> created:</p>
<pre><code class="language-typescript">constructor(
    public loadingController: LoadingController,
    public router: Router,
    public platform: Platform,
    public alertController: AlertController,
    public apiService: ApiserviceService,
    public formBuilder: FormBuilder,
    public inAppBrowser: InAppBrowser,
    public userManager:UserManagerServiceService,
    public authentificationService:AuthenticationService) {

    this.userForm = formBuilder.group({
      firstName: ['', Validators.compose([Validators.minLength(3), Validators.required])],
      lastName: ['', Validators.compose([Validators.minLength(3), Validators.required])],
      email: ['', Validators.compose([Validators.required, Validators.pattern('^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$')])],
      password: ['', Validators.compose([
        Validators.minLength(8),
        Validators.required,
        Validators.pattern('(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[€$@$!%*?&amp;])[A-Za-z\d$@€$!%*?&amp;].{8,}')
      ])],
      confirmpassword: ['', Validators.compose([
        Validators.minLength(8),
        Validators.required,
        Validators.pattern('(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[€$@$!%*?&amp;])[A-Za-z\d$@€$!%*?&amp;].{8,}')
      ])],
    });

  }</code></pre>
<p>And now we can modify our <strong>createAccount</strong> method to complete the process:</p>
<pre><code class="language-typescript">createAccount(params) {
    if (this.apiService.networkConnected) {
      this.apiService.registerUser(params).subscribe((resultat) =&gt; {
        let status = resultat["status"];
        console.log(status)
        if (status == "OK") {
          this.apiService.stopLoading();
          //User created 
          let data = resultat["data"]
          console.log(data)

          //Set user on memory and save on disk
          this.userManager.setUser(data)

          let paramsToLogin = {
            "email": params.email,
            "password": params.password,

          }

          // Log the user to get the JWT access token 
          this.authentificationService.login(paramsToLogin).then((resultat) =&gt; {
            if (resultat) {
              //Need to update first_name and lastname and lastConnexionDate
              let updateParams = {
                "first_name": params.firstName,
                "last_name": params.lastName,
                "lastConnexionDate": new Date()
              }
              this.apiService.updateUser(this.userManager.currentUser.id, updateParams).subscribe((done) =&gt; {
                console.log("resultat update ", done)
              })

              this.userManager.saveUser()
              // Next screen
              console.log("===Can go to next screen")
            }
            else {
              this.apiService.stopLoading();
              this.apiService.showError("An error occured with credentials")
            }
          })
        }
        else {
          this.apiService.stopLoading();
          let error = resultat["error"]
          console.log(error)
          if (error.status == 400) {
            this.apiService.showError('An account already exists for this email. Please login');
          }
          else {
            this.apiService.showError("An error occured")
          }
      }
      })
    }
    else {
      this.apiService.stopLoading();
      this.apiService.showNoNetwork()
    }
  }
</code></pre>
<p>Once the user has been created, we can now save the results on disk:</p>
<pre><code class="language-typescript">//Set user on memory and save on disk
this.userManager.setUser(data)</code></pre>
<p>Then we can use the credentials to login and get the <strong>JWT access token</strong> from the backend, and then if we have a results it means we have the access token, so we can update the user with it&#8217;s missing fields (last name, first name, last connexion date), then we can save again the user value to the disk, and then we should be able to continue and go to the main page of our application (which doesn&#8217;t exist yet). This is the purpose of this piece of code:</p>
<pre><code class="language-typescript"> let paramsToLogin = {
            "email": params.email,
            "password": params.password,
          }

// Log the user to get the JWT access token 
this.authentificationService.login(paramsToLogin).then((resultat) =&gt; {
    if (resultat) {
        //Need to update first_name and lastname and lastConnexionDate
        let updateParams = {
                "first_name": params.firstName,
                "last_name": params.lastName,
                "lastConnexionDate": new Date()
              }

        this.apiService.updateUser(this.userManager.currentUser.id, updateParams).subscribe((done) =&gt; {
            console.log("resultat update ", done)
        })

        this.userManager.saveUser()

        // Next screen
        console.log("===Can go to next screen")
    }
    else {
            this.apiService.stopLoading();
            this.apiService.showError("An error occured with credentials")
        }
    })</code></pre>
<blockquote>
<p>Please notice that the <strong>else</strong> code should not exists because we are in the registration process, so credentials can&#8217;t be incorrect because we just created the user account with these credentials.</p>
</blockquote>
<p>To complete our job, we need to create the following methods which don&#8217;t exists yet in our <strong>ApiService</strong>:</p>
<ol>
<li>this.apiService.login(params)</li>
<li>this.apiService.updateUser(this.userManager.currentUser.id, updateParams)</li>
</ol>
<p>And because we need to be authenticated to call our backend API endoints <strong>User</strong>, <strong>Chat</strong>, <strong>Message</strong>, we will also need to manage the <strong>JWT access token</strong> and add it automatically to each request we make (in the <strong>Http header</strong> of the request).</p>
<div class="page-break"></div>
<h3>login method</h3>
<p>The <strong>login</strong> method will call the <strong>/jwt/create/</strong> API endpoint with provided credentials to get the <strong>JWT access and refresh tokens</strong>. </p>
<p>Edit the  <strong>/src/app/services/api-service.service.ts</strong> file to add the following code:</p>
<pre><code class="language-typescript"> login(params) {
    const options = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json'
      })
    };

    console.log("URL " + this.getLoginUrl)
    console.log(params)
    return new Observable(observer =&gt; {
      this.http.post(this.getLoginUrl, params, options).subscribe(
        (val) =&gt; {
          observer.next(val)
          observer.complete()
        },
        response =&gt; {
          console.log("POST call in error", response);
          observer.next()
          observer.complete()
        },
        () =&gt; {

        });
    })
  }</code></pre>
<blockquote>
<p>If the API call succeed, we should receive a JSON containing the access and refresh tokens as we learned in day two of these tutorials.</p>
</blockquote>
<pre><code class="language-shell">{refresh: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90e…Q1In0.E8lFvotzMA2G5-ciJqUIyRVnNot3tmwrZJlts_dJfuw", access: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90e…DUifQ.lqMuEyd1J5IetaJmlvFgjcEunB7NcjxoUc2E5zdr7A4"}</code></pre>
<h3>update user method</h3>
<p>The <strong>updateUser</strong> method will call the HTTP Patch method of our <strong>User</strong> endpoint to set first name, last name and lastConnexionDate. </p>
<pre><code class="language-typescript"> updateUser(id, patchParams) {
    const options = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json'
      })
    };
    return Observable.create(observer =&gt; {
      // At this point make a request to your backend to make a real check!
      this.http.patch(this.getUserUrl + id + "/", patchParams, options)
        .pipe(retry(1))
        .subscribe(res =&gt; {
          this.networkConnected = true
          observer.next(true);
          observer.complete();
        }, error =&gt; {
          observer.next(false);
          observer.complete();
          console.log(error);// Error getting the data
        });
    });
  }</code></pre>
<blockquote>
<p>If we wanted to update the full <strong>User</strong> object (all fields) we should have made a PUT request and not a Patch one.</p>
</blockquote>
<p>Now you can run again the ionic application:</p>
<pre><code class="language-shell">ionic serve</code></pre>
<p>and try to register. Then if you look at the javascript console, you should see the <strong>JWT access and refresh tokens</strong> but the update of the user will fail with an HTTP 401 error code.</p>
<pre><code class="language-shell">PATCH http://127.0.0.1:8000/api/user/59d99e58-2a32-430a-b20d-96386f0514af/ 401 (Unauthorized)</code></pre>
<p>As we learned to call our <strong>API endpoints</strong> we need to be authenticated and add the <strong>JWT token</strong> in the header of the request. </p>
<div class="page-break"></div>
<p>One of the simple solution could be to pass the <strong>JWT Token</strong> to our <strong>updateUser</strong> method </p>
<pre><code class="language-typescript">this.apiService.updateUser(this.userManager.currentUser.id, updateParams,this.authentificationService.token).subscribe((done) =&gt; {
                console.log("resultat update ", done)
})</code></pre>
<p>and modify the <strong>updateUser</strong> method to add it in the headers of the request:</p>
<pre><code class="language-typescript">updateUser(id, patchParams,accessToken) {
    const options = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json',
        'Authorization': 'Bearer '+accessToken
      })
    };
    return Observable.create(observer =&gt; {
      // At this point make a request to your backend to make a real check!
      this.http.patch(this.getUserUrl + id + "/", patchParams, options)
        .pipe(retry(1))
        .subscribe(res =&gt; {
          this.networkConnected = true
          observer.next(true);
          observer.complete();
        }, error =&gt; {
          observer.next(false);
          observer.complete();
          console.log(error);// Error getting the data
        });
    });
  }</code></pre>
<p>Now if we try to register again, everything should be fine and our <strong>User</strong> should be updated with specified values (which you can check using the Django admin).</p>
<blockquote>
<p>Passing the <strong>JWT token</strong> to the method is not really the solution because tokens can expire and we need a more generic way to deal with this problem. We will learn how to do soon but first let&#8217;s write the login page code.</p>
</blockquote>
<h2>Login user</h2>
<p>We will create the login page and move the code to our <strong>src/app/pages</strong> directory:</p>
<pre><code class="language-shell">ionic g page LoginPage
mv src/app/login-page src/app/pages</code></pre>
<p>Then we will edit the <strong>src/app/app-routing.module.ts</strong> file to reflect the directory change :</p>
<pre><code class="language-typescript">import { NgModule } from '@angular/core';
import { PreloadAllModules, RouterModule, Routes } from '@angular/router';

const routes: Routes = [
  {
    path: 'register-page',
    loadChildren: () =&gt; import('./pages/register-page/register-page.module').then( m =&gt; m.RegisterPagePageModule)
  },
  {
    path: 'login-page',
    loadChildren: () =&gt; import('./pages/login-page/login-page.module').then( m =&gt; m.LoginPagePageModule)
  },

];

@NgModule({
  imports: [
    RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })
  ],
  exports: [RouterModule]
})
export class AppRoutingModule {}</code></pre>
<p>As we have done for our register page, we will have a form in which the user can enters it&#8217;s email and password. Furthermore we will add a link to redirect to the register page, if the user has no account.</p>
<p>Let&#8217;s edit the <strong>login-page.module.ts</strong> to include the Angular <strong>ReactiveFormsModule</strong>:</p>
<pre><code class="language-typescript">import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { ReactiveFormsModule } from '@angular/forms';

import { IonicModule } from '@ionic/angular';

import { LoginPagePageRoutingModule } from './login-page-routing.module';

import { LoginPagePage } from './login-page.page';

@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    ReactiveFormsModule,
    IonicModule,
    LoginPagePageRoutingModule
  ],
  declarations: [LoginPagePage]
})
export class LoginPagePageModule {}</code></pre>
<p>Then we can write our <strong>login-page.page.html</strong> code:</p>
<pre><code class="language-typescript">&lt;ion-content color="medium" padding padding-24&gt;
  &lt;div class="auth-container"&gt;
    &lt;div class="form"&gt;
      &lt;div class="form-title"&gt;
        Sign In 
      &lt;/div&gt;
      &lt;form [formGroup]="userForm"&gt;
      &lt;ion-list lines="none" mode="ios" login-form&gt;
        &lt;ion-item mode="ios"&gt;
          &lt;ion-icon class="sw-email-solid" slot="start"&gt;&lt;/ion-icon&gt;
          &lt;ion-input mode="ios" type="email" placeholder="Email"  name="email" formControlName="email" [class.invalid]="!userForm.controls.email.valid &amp;&amp; (userForm.controls.email.dirty || submitAttempt)"&gt;&lt;/ion-input&gt;
        &lt;/ion-item&gt;
         &lt;ion-item *ngIf="!userForm.controls.email.valid  &amp;&amp; (userForm.controls.email.dirty || submitAttempt)"&gt;
              &lt;p&gt;An email is required
              &lt;/p&gt;
          &lt;/ion-item&gt;

        &lt;ion-item mode="ios"&gt;
          &lt;ion-icon class="sw-lock-sold" slot="start"&gt;&lt;/ion-icon&gt;
          &lt;ion-input mode="ios" type="password"  placeholder="Password" formControlName="password" [class.invalid]="!userForm.controls.password.valid &amp;&amp; (userForm.controls.password.dirty || submitAttempt)"&gt;&lt;/ion-input&gt;
        &lt;/ion-item&gt;
         &lt;ion-item *ngIf="!userForm.controls.password.valid  &amp;&amp; (userForm.controls.password.dirty || submitAttempt)"&gt;
              &lt;p&gt;min. 8 characters required with lowercase, uppercase, number and special character&lt;/p&gt;
          &lt;/ion-item&gt;
      &lt;/ion-list&gt;
      &lt;div class="buttons-forgot" text-right&gt;

        &lt;ion-button mode="ios" expand="block" color="danger" [disabled]="!userForm.valid" (click)="login()"&gt;
          Sign in
        &lt;/ion-button&gt;
      &lt;/div&gt;

      &lt;/form&gt;
      &lt;p class="instruction" (click)="cgu()"&gt;By signin, you will accept our terms and condition.&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/ion-content&gt;
&lt;ion-footer no-border mode="ios" auth&gt;
  &lt;ion-toolbar color="medium" mode="ios" text-center&gt;
    &lt;div class="option-auth"&gt;
      &lt;p&gt;Not yet registered?&lt;/p&gt;
      &lt;ion-button fill="clear" expand="block" routerLink="/register-page" mode="ios" color="danger"&gt;Sign up&lt;/ion-button&gt;
    &lt;/div&gt;
  &lt;/ion-toolbar&gt;
&lt;/ion-footer&gt;</code></pre>
<p>And write the associated scss <strong>login-page.page-scss</strong>:</p>
<pre><code class="language-css">.buttons-forgot {
    ion-button {
        &amp;.forgot-password {
            font-size: 15px;
            margin: 0;
            min-height: unset;
            height: 34px;
            margin-bottom: 12px;
        }
    }
}

.option-auth{
    display: flex;
    flex-direction: column;
    align-items: center;
}
p {
    font-size: 0.8em;
    color: red;
}</code></pre>
<p>The Login page is really simple. It just displays two inputs box for email and password, a clickable text to see the terms &amp; conditions, a button to login and a button to redirect to the <strong>Register</strong> page. </p>
<p>Let&#8217;s write our <strong>src/app/pages/login.page/login-page.page.ts</strong> file :</p>
<pre><code class="language-typescript">import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { LoadingController, Platform, AlertController } from '@ionic/angular';
import { InAppBrowser } from '@ionic-native/in-app-browser/ngx';
import { AuthenticationService } from 'src/app/services/authentication.service';
import { UserManagerServiceService } from 'src/app/services/user-manager-service.service';
import { ApiserviceService } from 'src/app/services/api-service.service';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

@Component({
  selector: 'app-login-page',
  templateUrl: './login-page.page.html',
  styleUrls: ['./login-page.page.scss'],
})
export class LoginPagePage implements OnInit {

  public userForm: FormGroup;

  loading: any;
  constructor(
    public loadingController: LoadingController,
    public router: Router,
    public platform: Platform,
    public formBuilder: FormBuilder,
    public userManager: UserManagerServiceService,
    public alertController: AlertController,
    public apiService: ApiserviceService,
    public authentificationService: AuthenticationService,
    public inAppBrowser: InAppBrowser) {

    this.userForm = formBuilder.group({
      email: ['', Validators.compose([Validators.required, Validators.pattern('^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$')])],
      password: ['', Validators.compose([
        Validators.minLength(8),
        Validators.required,
        Validators.pattern('(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[€$@$!%*?&amp;])[A-Za-z\d$@€$!%*?&amp;].{8,}')
      ])]
    });
  }

  ngAfterViewInit() {

  }

  login() {

    let email = this.userForm.value["email"]
    let password = this.userForm.value["password"]

    //chek si le pseudo est disponible
    if (this.apiService.networkConnected) {
      this.apiService.showLoading();
      // Check email
      // Check email
      let params = {
        "email": email,
        "password": password,
      }
      this.authentificationService.login(params).then((resultat) =&gt; {
        this.apiService.stopLoading();
        if (resultat) {
          this.apiService.findUserWithQuery("?email="+email,this.authentificationService.token).subscribe((list) =&gt; {
         {
            if (list) {
              let count = list["count"]
              console.log("Count " + count)
              if (count == 0) {
                this.apiService.showError('Identification failed ! No account found');
              }
              else {
                let result = list["results"][0]
                console.log(result)
                this.userManager.setUser(result).then((done) =&gt; {
                   // Next screen
                  console.log("===Can go to next screen")
                })

              }
            }
            else {
              this.apiService.showError("An error occured with credentials, no account found")
            }

          })
        }
        else {
          this.apiService.showError("An error occured with credentials")
        }
      })
    }
    else {
      this.apiService.showNoNetwork();
    }

  }

  cgu(){
    let url = "https://policies.google.com/terms"
    let target = "_blank"
    this.inAppBrowser.create(url, target, "location=no,zoom=no")
  }

  ngOnInit() {
  }
}</code></pre>
<p>Like for the <strong>Register</strong> page, we configure our <strong>Angular form</strong> to manage the email and the password:</p>
<pre><code class="language-typescript"> this.userForm = formBuilder.group({
      email: ['', Validators.compose([Validators.required, Validators.pattern('^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$')])],
      password: ['', Validators.compose([
        Validators.minLength(8),
        Validators.required,
        Validators.pattern('(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[€$@$!%*?&amp;])[A-Za-z\d$@€$!%*?&amp;].{8,}')
      ])]
    });</code></pre>
<p><strong>Email</strong> is required and the input should be an email format. </p>
<p><strong>Password</strong> is required and should have 8 caracters minimum, with number and special caracter.</p>
<p>The <strong>login()</strong> method will check the credentials of the user using our <strong>AuthentificationService</strong>:</p>
<pre><code class="language-typescript"> let params = {
        "email": email,
        "password": password,
      }
      this.authentificationService.login(params).then((resultat) =&gt; {</code></pre>
<p>And if credentials are correct, we will retrieve the user using a new method which we need to create:</p>
<pre><code class="language-typescript"> this.apiService.findUserWithQuery("?email="+email,this.authentificationService.token).subscribe((list) =&gt; {</code></pre>
<p>As we did before, for simplication and demo purpose, we will pass the <strong>JWT access token</strong> to the method.<br />
If the method has a result, we can set our <strong>User</strong> and then go to next screen otherwise we need to display an error message.</p>
<div class="page-break"></div>
<h3>Adding methods to deal with <strong>User</strong> in our <strong>APIService</strong></h3>
<p>In  <strong>Register</strong> process, we added an <strong>updateUser</strong> method. Now let&#8217;s write all CRUD methods to deal with <strong>User</strong>.</p>
<p>Let&#8217;s add the following methods to our <strong>src/app/services/api-service.service.ts</strong>:</p>
<pre><code class="language-typescript"> // Create a user 
  createUser(modelToCreate,accessToken) {
    // model JSON
    const options = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json',
        'Authorization': 'Bearer '+accessToken
      })
    };
    console.log("URL " + this.getUserUrl)
    return this.http.post(this.getUserUrl, modelToCreate, options).pipe(retry(1))
  }

  //Find user based on parameters
  findUserWithQuery(query,accessToken) {
    let url = this.getUserUrl + query;
    return this.findUser(url,accessToken)
  }

  private findUser(url,accessToken) {
    const options = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json',
        'Authorization': 'Bearer '+accessToken
      })
    };

    return Observable.create(observer =&gt; {
      this.http.get(url, options)
        .pipe(retry(1))
        .subscribe(res =&gt; {
          observer.next(res);
          observer.complete();
        }, error =&gt; {
          observer.next();
          observer.complete();
          console.log(error);// Error getting the data
        });
    });
  }

  getUserDetails(id,accessToken) {
    const options = {
      headers: new HttpHeaders({
        'Authorization': 'Bearer '+accessToken,
        'Content-Type': 'application/json'
      })
    };
    return Observable.create(observer =&gt; {
      this.http.get(this.getUserUrl + id + "/", options)
        .pipe(retry(1))
        .subscribe(res =&gt; {
          this.networkConnected = true
          observer.next(res);
          observer.complete();
        }, error =&gt; {
          observer.next(false);
          observer.complete();
          console.log(error);// Error getting the data
        });
    });
  }

  updateUser(id, patchParams,accessToken) {
    const options = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json',
        'Authorization': 'Bearer '+accessToken
      })
    };
    return Observable.create(observer =&gt; {
      this.http.patch(this.getUserUrl + id + "/", patchParams, options)
        .pipe(retry(1))
        .subscribe(res =&gt; {
          this.networkConnected = true
          observer.next(true);
          observer.complete();
        }, error =&gt; {
          observer.next(false);
          observer.complete();
          console.log(error);// Error getting the data
        });
    });
  }

  putUser(object,accessToken) {
    const options = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json',
        'Authorization': 'Bearer '+accessToken
      })
    };
    return Observable.create(observer =&gt; {
      this.http.put(this.getUserUrl + object.id + "/", object, options)
        .pipe(retry(1))
        .subscribe(res =&gt; {
          this.networkConnected = true
          observer.next(true);
          observer.complete();
        }, error =&gt; {
          observer.next(false);
          observer.complete();
          console.log(error);// Error getting the data
        });
    });
  }

  deleteUser(id,accessToken) {
    const options = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json',
        'Authorization': 'Bearer '+accessToken
      })
    };
    return this.http.delete(this.getUserUrl + id + "/", options).pipe(retry(1))
  }
</code></pre>
<div class="page-break"></div>
<h3>Redirect user to login page</h3>
<p>Now we will add as default route our login page. Edit the <strong>src/app/app-routing.module.ts</strong> file and add this path :</p>
<pre><code class="language-typescript"> {
    path: '',
    redirectTo: 'login-page',
    pathMatch: 'full'
  },</code></pre>
<p>With this instruction, every request with no relative path will be redirected to the <strong>/login-page</strong> url. So if you try to run ionic again:</p>
<pre><code class="language-shell">ionic serve</code></pre>
<p>You browser will launch at <strong><a href="http://localhost:8100/">http://localhost:8100/</a></strong> and will be automatically redirected to the login page <strong><a href="http://localhost:8100/login-page">http://localhost:8100/login-page</a></strong></p>
<p><img src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/08/loginpage-e1627822486338.png" alt="LoginPage" /></p>
<p>Now you should be able to login with the account created with the <strong>Register</strong> page and see the successful login in the console log</p>
<pre><code class="language-javascript">Login result  {refresh: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90e…YwIn0.URQGnnZ9g3Ms8QdCC3JPT0ChKX1X-WL85XgyQAj9jKs", access: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90e…jAifQ.v2kCSHwK9TAIhUSzrBbYGgG4nZTg5wWu3SMxs9peCFA"}
login-page.page.ts:67 Count 1
login-page.page.ts:73 {id: "3cde3f7e-261e-4ebb-8437-fa4c27d35bf0", password: "pbkdf2_sha256$216000$adrKF75lkxvH$kxQL19HuzE4Qsp+e3SPsZYSxkJrP7wFW6TercA9K7hc=", is_superuser: false, email: "christophe@idevotion.fr", first_name: "Christophe", …}
user-manager-service.service.ts:38 User ID 3cde3f7e-261e-4ebb-8437-fa4c27d35bf0 email christophe@idevotion.fr
login-page.page.ts:76 ===Can go to next screen</code></pre>
<p>The source code of this tutorial could be found on my <a href="https://github.com/csurbier/chattuto/tree/main/day-five">Github</a> repository.</p>
<div class="page-break"></div>
<h2>Questions / Answers</h2>
<ol>
<li>
<p>Which Angular library can we use to manage forms easily ?</p>
<blockquote>
<p>Reactive forms </p>
</blockquote>
</li>
<li>
<p>What should we add to an HTTP request to be authenticated ?</p>
<blockquote>
<p>A JWT access token </p>
</blockquote>
</li>
<li>How to do that ?<br />
<blockquote>
<p>Add a <strong>&#8216;Authorization&#8217;: &#8216;Bearer &#8216;+accessToken</strong> instruction in the HTTP headers of the request</p>
</blockquote>
</li>
<li>Which file is used in <strong>Ionic</strong> to manage the authorized urls ?<br />
<blockquote>
<p><strong>app-routing.module.ts</strong> file</p>
</blockquote>
</li>
</ol>
<div class="page-break"></div>
<p>The post <a rel="nofollow" href="https://www.ionicanddjangotutorial.com/day-5-registering-sign-in-users-in-our-ionic-application/">Day 5 : Registering &#8211; Sign in users in our Ionic application</a> appeared first on <a rel="nofollow" href="https://www.ionicanddjangotutorial.com">Ionic and Django Tutorial</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Day 4 : Ionic application</title>
		<link>https://www.ionicanddjangotutorial.com/day-4-ionic-application/</link>
		
		<dc:creator><![CDATA[Christophe Surbier]]></dc:creator>
		<pubDate>Fri, 13 Aug 2021 15:32:35 +0000</pubDate>
				<category><![CDATA[Create a real world mobile chat application with Ionic and Django]]></category>
		<guid isPermaLink="false">https://www.ionicanddjangotutorial.com/?p=1322</guid>

					<description><![CDATA[<p>Create a chat application with Ionic 5/ Angular 12/ Capacitor 3 and Django 3 &#8211; Series &#8211; Part four Day&#8230; <a href="https://www.ionicanddjangotutorial.com/day-4-ionic-application/" class="more-link">Continue Reading <span class="meta-nav">&#8594;</span></a></p>
<p>The post <a rel="nofollow" href="https://www.ionicanddjangotutorial.com/day-4-ionic-application/">Day 4 : Ionic application</a> appeared first on <a rel="nofollow" href="https://www.ionicanddjangotutorial.com">Ionic and Django Tutorial</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h1>Create a chat application with Ionic 5/ Angular 12/ Capacitor 3 and Django 3 &#8211; Series &#8211; Part four</h1>
<h1>Day 4 : Ionic application</h1>
<h2>Create our Ionic project skeleton</h2>
<p>To create our Ionic application project just open a terminal and type: </p>
<pre><code class="language-shell">ionic start ChatTuto sidemenu --capacitor --project-id=chattuto --package-id=com.idevotion.chattuto</code></pre>
<p>And pick the <strong>Angular</strong> framework. Then since we will deploy our application to iOS and Android , we will add these platforms with Capacitor</p>
<pre><code class="language-shell">npm install @capacitor/ios</code></pre>
<blockquote>
<p>I will suppose that you have already setup your environment for using Ionic. If not you will find the <a href="https://ionicframework.com/docs/intro/environment">how to do it here</a>.</p>
</blockquote>
<p>The source are generated into a folder named <strong>chattuto</strong> so we can go inside this folder</p>
<pre><code class="language-shell">cd chattuto</code></pre>
<p>Now we will create some empty folders into the <strong>src/app</strong> directory to better organised our code.</p>
<pre><code class="language-shell">mkdir src/app/pages src/app/models src/app/services</code></pre>
<table>
<thead>
<tr>
<th>Directory</th>
<th>Definition</th>
</tr>
</thead>
<tbody>
<tr>
<td>pages</td>
<td>Will contain our application pages (views)</td>
</tr>
<tr>
<td>models</td>
<td>Will contain our entity models</td>
</tr>
<tr>
<td>services</td>
<td>Will contain our services</td>
</tr>
</tbody>
</table>
<h2>Adding a service to dialog with our Backend</h2>
<p>Because we will need to dialog with our Backend to get or send data, we will add a service file for this with some other useful methods. </p>
<pre><code class="language-shell">ionic g service ApiService
mv src/app/api-service.service.* src/app/services </code></pre>
<p>And we will move the generated file into our <strong>services</strong> directory. Next we will install two Capacitor libraries to deal with Network and Storage:</p>
<pre><code class="language-shell">npm install @capacitor/storage @capacitor/network</code></pre>
<p>Then we will write some common and useful methods into our <strong>api-service.service.ts</strong> file. </p>
<blockquote>
<p>You can refer to comment in the code to understand the purpose of methods</p>
</blockquote>
<pre><code class="language-typescript">import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams, HttpErrorResponse } from '@angular/common/http';
import { Observable, BehaviorSubject } from 'rxjs';
import { from, of, forkJoin } from 'rxjs';
import { catchError, retry, map, tap } from 'rxjs/operators';
import { AlertController, LoadingController } from '@ionic/angular';
import { Platform } from '@ionic/angular';
import { Storage } from '@capacitor/storage';
import { Network } from '@capacitor/network';

export const domainConfig = {
  client: 'chattuto',
  virtual_host: 'http://127.0.0.1:8000/',
  domainApp: 'http://127.0.0.1:8000/',
  staticStorage: "static/storage/"
}

export enum ConnectionStatus {
  Online,
  Offline
}

@Injectable({
  providedIn: 'root'
})
export class ApiserviceService {

  public status: BehaviorSubject&lt;ConnectionStatus&gt; = new BehaviorSubject(ConnectionStatus.Offline);
  public tokenSet: BehaviorSubject&lt;Boolean&gt; = new BehaviorSubject&lt;Boolean&gt;(false);

  public tokenSSO: String = "";
  networkConnected: boolean = true;
  virtualHostName: string = ''
  appName: string = '';
  apiPrefix = "/api"
  loader: any;
  isShowingLoader = false;

  // ================ AUTHENTIFICATION METHODS ====================
  getLoginUrl: string = ""

  getResetPwdUrl: string = "";
  getRefreshTokenUrl: string = ""
  getMeUrl: string = ""

  getUserUrl: string = '';
  getCreateUserUrl: string = '';

  initProvider(url, app_name, apiPrefix) {
    this.virtualHostName = url;
    this.appName = app_name;
    this.apiPrefix = apiPrefix;
    console.log("init provider appName " + this.appName);
    this.initUrls()
  }

  private initUrls() {
    // ================ AUTHENTIFICATION METHODS ====================
    this.getLoginUrl = this.virtualHostName + "auth/jwt/create/"
    this.getCreateUserUrl = this.virtualHostName + "auth/users/"
    this.getResetPwdUrl = this.virtualHostName + "auth/users/reset_password/"
    this.getRefreshTokenUrl = this.virtualHostName + "auth/jwt/refresh/"
    this.getMeUrl = this.virtualHostName + "auth/users/me/"
    // =================================================================

    this.getUserUrl = this.virtualHostName + this.apiPrefix + "/user/"
  }

  constructor(public http: HttpClient,
    public loadingController: LoadingController,
    public alertCtrl: AlertController,
    public platform: Platform) {

    this.initializeNetworkEvents();
    this.initProvider(domainConfig.virtual_host, domainConfig.client, "api")
    this.http = http
  }

  /**
   * This method will check network events and if a change occurs will store the current network status
   */
  public async initializeNetworkEvents() {
    console.log("======== Initialise Network Events ======")
    if (this.platform.is("capacitor")) {
      let status = await Network.getStatus();
      if (status["connected"] == false) {
        this.networkConnected = false
        this.updateNetworkStatus(ConnectionStatus.Offline);
      }
      else {
        this.networkConnected = true;
        this.updateNetworkStatus(ConnectionStatus.Online);
      }
      let handler = Network.addListener('networkStatusChange', (status) =&gt; {
        console.log("Network status changed", status);
        if (status["connected"] == false) {
          this.networkConnected = false
          this.updateNetworkStatus(ConnectionStatus.Offline);
        }
        else {
          this.networkConnected = true;
          this.updateNetworkStatus(ConnectionStatus.Online);
        }
      });
    }
    else {
      if (navigator.onLine) {
        this.updateNetworkStatus(ConnectionStatus.Online);
      }
      else {
        this.updateNetworkStatus(ConnectionStatus.Offline);
      }
    }
  }

  private async updateNetworkStatus(status: ConnectionStatus) {
    this.status.next(status);
    this.networkConnected = status == ConnectionStatus.Offline ? false : true;
    console.log("networkConnected " + this.networkConnected)
  }

  public onNetworkChange(): Observable&lt;ConnectionStatus&gt; {
    return this.status.asObservable();
  }

  public getCurrentNetworkStatus(): ConnectionStatus {
    return this.status.getValue();
  }

  // Store data (as json) with provided key name 
  public setLocalData(key, jsonData) {
    return new Promise(async resolve =&gt; {
      await Storage.set({ key: &lt;code&gt;${domainConfig.client}-${key}&lt;/code&gt;, value: JSON.stringify(jsonData) })
      resolve(true)

    });
  }

  // Remove local data for a specific key
  public removeLocalData(key) {
    return new Promise(async resolve =&gt; {
      let ret = await Storage.remove({ key: &lt;code&gt;${domainConfig.client}-${key}&lt;/code&gt; })

    });
  }

  // Get local data for a specific key
  public getLocalData(key) {
    return new Promise(async resolve =&gt; {
      let ret = await Storage.get({ key: &lt;code&gt;${domainConfig.client}-${key}&lt;/code&gt; })

      if (ret.value) {
        resolve(JSON.parse(ret.value))
      }
      else {
        resolve(null)
      }
    });
  }

  //========= Useful methods =========
  // show a No network alert

  async showNoNetwork() {
    let alert = await this.alertCtrl.create({
      header: 'Sorry',
      message: 'No network detected, please check your connexion',
      buttons: ['OK']
    });
    return await alert.present();

  }

  // Show a loader
  async showLoading() {
    if (!this.isShowingLoader) {
      this.isShowingLoader = true
      this.loader = await this.loadingController.create({
        message: 'Merci de patienter',
        duration: 4000
      });
      return await this.loader.present();

    }

  }

  //Dismiss loader 
  async stopLoading() {
   if (this.loader) {
      this.loader.dismiss()
      this.loader = null
      this.isShowingLoader = false
    }

  }

  // Show a loader with a specific message
  public async showLoadingMessage(message) {
    this.loader = await this.loadingController.create({
      message: message,
    });
    this.loader.present();
  }

  // Show an error message
  async showError(text) {
    let alert = await this.alertCtrl.create({
      header: 'Error',
      message: text,
      buttons: ['OK']
    });
    return await alert.present();
  }

  /**
  * Show a message  
  *
  * @param title - The title of the message to show
  * @param message - The text of the message to show
  * 
  */
  async showMessage(title, message) {
    let alert = await this.alertCtrl.create({
      header: title,
      message: message,
      buttons: ['OK']
    });
    return await alert.present();
  }

  // ================ JWT AUTHENTIFICATION METHODS ====================

  /**
   * This method is used to obtain a new access token using the refresh token value
   * @param refreshToken 
   * @returns 
   */
  refreshToken(refreshToken) {
    const options = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json'
      })
    };

    let params = {
      "refresh": refreshToken
    }
    console.log("=== ask new access token with refresh token ", params)
    return this.http.post(this.getRefreshTokenUrl, params, options).pipe(
      tap(response =&gt; {
        console.log("=== REFRESH response", response)
        this.setLocalData("access", { "access": response["access"] })
        this.setLocalData("refresh", { "refresh": response["refresh"] })

      })
    );
  }

}</code></pre>
<blockquote>
<p>We will use these methods later while implementing our application pages. </p>
</blockquote>
<div class="page-break"></div>
<h2>Creating a Register page</h2>
<p>The first step for our application, will be to create a page on which our users will be able to register. Let&#8217;s do it and create that page. In a terminal, launch the command:</p>
<pre><code class="language-shell">ionic g page RegisterPage</code></pre>
<p>Then move generated code to our <strong>pages</strong> directory</p>
<pre><code class="language-shell"> mv src/app/register-page src/app/pages </code></pre>
<p>Then we need to edit the <strong>app-routing.module.ts</strong> to reflect the new path of our page:</p>
<pre><code class="language-typescript">  {
    path: 'register-page',
    loadChildren: () 
        =&gt; import('./pages/register-page/register-page.module').then( m =&gt; m.RegisterPagePageModule)
  }</code></pre>
<p>Because we will not use the <strong>default ionic generated pages</strong> we will erase the folder named <strong>folder</strong> and will remove reference of it from the  <strong>app-routing.module.ts</strong> file which should look like this:</p>
<pre><code class="language-typescript">  import { NgModule } from '@angular/core';
import { PreloadAllModules, RouterModule, Routes } from '@angular/router';

const routes: Routes = [
  {
    path: 'register-page',
    loadChildren: () =&gt; import('./pages/register-page/register-page.module').then( m =&gt; m.RegisterPagePageModule)
  }
];

@NgModule({
  imports: [
    RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })
  ],
  exports: [RouterModule]
})
export class AppRoutingModule {}</code></pre>
<h3>Adding a form to register</h3>
<p>First we will focus on the design of the page. Basically it will be a form asking our user his last name, first name, email and password. </p>
<p>Let&#8217;s edit the <strong>src/pages/register-page/register-page.page.html</strong> and add the following code:</p>
<pre><code class="language-html">&lt;ion-content color="medium" padding padding-24&gt;
  &lt;div class="auth-container"&gt;

    &lt;div class="form"&gt;

      &lt;div class="form-title"&gt;
        Register
      &lt;/div&gt;
        &lt;p *ngIf="submitAttempt" style="color: #ea6153;"&gt;Please filled all fields correctly&lt;/p&gt;

      &lt;form [formGroup]="userForm"&gt;

      &lt;ion-list lines="none" mode="ios" login-form&gt;
         &lt;ion-item&gt;
             &lt;ion-icon class="sw-user-solid" slot="start"&gt;&lt;/ion-icon&gt;
            &lt;ion-input formControlName="firstName" placeholder="LastName" type="text" [class.invalid]="!userForm.controls.firstName.valid &amp;&amp; (userForm.controls.firstName.dirty || submitAttempt)"&gt;&lt;/ion-input&gt;
          &lt;/ion-item&gt;
           &lt;ion-item *ngIf="!userForm.controls.firstName.valid  &amp;&amp; (userForm.controls.firstName.dirty || submitAttempt)"&gt;
              &lt;p&gt;3 characters min.&lt;/p&gt;
          &lt;/ion-item&gt;
        &lt;ion-item mode="ios"&gt;
          &lt;ion-icon class="sw-user-solid" slot="start"&gt;&lt;/ion-icon&gt;
           &lt;ion-input formControlName="lastName" type="text" placeholder="FirstName" [class.invalid]="!userForm.controls.lastName.valid &amp;&amp; (userForm.controls.lastName.dirty || submitAttempt)"&gt;&lt;/ion-input&gt;
        &lt;/ion-item&gt;
          &lt;ion-item *ngIf="!userForm.controls.lastName.valid  &amp;&amp; (userForm.controls.lastName.dirty || submitAttempt)"&gt;
              &lt;p&gt;3 characters min.&lt;/p&gt;
          &lt;/ion-item&gt;
        &lt;ion-item mode="ios"&gt;
          &lt;ion-icon class="sw-email-solid" slot="start"&gt;&lt;/ion-icon&gt;
          &lt;ion-input mode="ios" type="email" placeholder="Email"  name="email" formControlName="email" [class.invalid]="!userForm.controls.email.valid &amp;&amp; (userForm.controls.email.dirty || submitAttempt)"&gt;&lt;/ion-input&gt;
        &lt;/ion-item&gt;
         &lt;ion-item *ngIf="((!userForm.controls.email.valid  &amp;&amp; (userForm.controls.email.dirty || submitAttempt))"&gt;
              &lt;p&gt;An email is required
              &lt;/p&gt;
          &lt;/ion-item&gt;

        &lt;ion-item mode="ios"&gt;
          &lt;ion-icon class="sw-lock-sold" slot="start"&gt;&lt;/ion-icon&gt;
          &lt;ion-input mode="ios" type="password"  placeholder="Password" formControlName="password" [class.invalid]="!userForm.controls.password.valid &amp;&amp; (userForm.controls.password.dirty || submitAttempt)"&gt;&lt;/ion-input&gt;
        &lt;/ion-item&gt;
         &lt;ion-item *ngIf="!userForm.controls.password.valid  &amp;&amp; (userForm.controls.password.dirty || submitAttempt)"&gt;
              &lt;p&gt;min. 8 characters required with lowercase, uppercase, number and special character&lt;/p&gt;
          &lt;/ion-item&gt;
        &lt;ion-item mode="ios"&gt;
          &lt;ion-icon class="sw-lock-sold" slot="start"&gt;&lt;/ion-icon&gt;
          &lt;ion-input mode="ios" type="password"   placeholder="Confirm password"  formControlName="confirmpassword" [class.invalid]="!userForm.controls.confirmpassword.valid &amp;&amp; (userForm.controls.confirmpassword.dirty || submitAttempt)"&gt;&lt;/ion-input&gt;
        &lt;/ion-item&gt;
           &lt;ion-item *ngIf="!userForm.controls.confirmpassword.valid  &amp;&amp; (userForm.controls.confirmpassword.dirty || submitAttempt)"&gt;
              &lt;p&gt;Please confirm password&lt;/p&gt;
          &lt;/ion-item&gt;
      &lt;/ion-list&gt;
      &lt;ion-button mode="ios" expand="block" color="danger" [disabled]="!userForm.valid" (click)="register()"&gt;
        Create account
      &lt;/ion-button&gt;

      &lt;/form&gt;
      &lt;p class="instruction" (click)="cgu()"&gt;By registering you are accepting our terms and conditions&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;

&lt;/ion-content&gt;

&lt;ion-footer no-border mode="ios" auth&gt;
  &lt;ion-toolbar color="medium" mode="ios" text-center&gt;
    &lt;div class="option-auth"&gt;
      &lt;p&gt;Already a member ?&lt;/p&gt;
      &lt;ion-button fill="clear" expand="block" routerLink="/login" mode="ios" color="danger"&gt;Sign in&lt;/ion-button&gt;
    &lt;/div&gt;
  &lt;/ion-toolbar&gt;
&lt;/ion-footer&gt;</code></pre>
<p>Our form will contain some validation methods to check if values entered are in correct format. We will see in a moment how to specify this. But before that let&#8217;s edit the <strong>src/pages/register-page/register-page.page.scss</strong> file to focus on CSS design and add the following code:</p>
<pre><code class="language-css">p {
    font-size: 0.8em;
    color: red;
}

.option-auth{
    display: flex;
    flex-direction: column;
    align-items: center;
}</code></pre>
<p>We use <strong>Flex</strong> to center the items in our view, and we set the color of <strong>p</strong> html tag to be red, because it will be used to display validation error messages.</p>
<div class="page-break"></div>
<p>Now we will define some global CSS style for our <strong>Ionic</strong> project. </p>
<p>First we can edit the autogenerated file <strong>src/app/theme/variables.scss</strong> and type:</p>
<pre><code class="language-css">// Ionic Variables and Theming. For more info, please see:
// http://ionicframework.com/docs/theming/

/** Ionic CSS Variables **/
:root {
  /** primary **/
  --ion-color-primary: #3880ff;
  --ion-color-primary-rgb: 56, 128, 255;
  --ion-color-primary-contrast: #ffffff;
  --ion-color-primary-contrast-rgb: 255, 255, 255;
  --ion-color-primary-shade: #3171e0;
  --ion-color-primary-tint: #4c8dff;

  /** secondary **/
  --ion-color-secondary: #0cd1e8;
  --ion-color-secondary-rgb: 12, 209, 232;
  --ion-color-secondary-contrast: #ffffff;
  --ion-color-secondary-contrast-rgb: 255, 255, 255;
  --ion-color-secondary-shade: #0bb8cc;
  --ion-color-secondary-tint: #24d6ea;

  /** tertiary **/
  --ion-color-tertiary: #005B82;
  --ion-color-tertiary-rgb: 0, 91, 130;
  --ion-color-tertiary-contrast: #ffffff;
  --ion-color-tertiary-contrast-rgb: 255, 255, 255;
  --ion-color-tertiary-shade: #004968;
  --ion-color-tertiary-tint: #337c9b;

  /** success **/
  --ion-color-success: #10dc60;
  --ion-color-success-rgb: 16, 220, 96;
  --ion-color-success-contrast: #ffffff;
  --ion-color-success-contrast-rgb: 255, 255, 255;
  --ion-color-success-shade: #0ec254;
  --ion-color-success-tint: #28e070;

  /** warning **/
  --ion-color-warning: #FFE482;
  --ion-color-warning-rgb: 255, 228, 130;
  --ion-color-warning-contrast: #D93025;
  --ion-color-warning-contrast-rgb: 217, 48, 37;
  --ion-color-warning-shade: #e6cd75;
  --ion-color-warning-tint: #ffe99b;

  /** danger **/
  --ion-color-danger: #ff5b61;
  --ion-color-danger-rgb: 255, 91, 97;
  --ion-color-danger-contrast: #ffffff;
  --ion-color-danger-contrast-rgb: 255, 255, 255;
  --ion-color-danger-shade: #cc484c;
  --ion-color-danger-tint: #ff7b7f;

  /** dark **/
  --ion-color-dark: #222428;
  --ion-color-dark-rgb: 34, 34, 34;
  --ion-color-dark-contrast: #ffffff;
  --ion-color-dark-contrast-rgb: 255, 255, 255;
  --ion-color-dark-shade: #1e2023;
  --ion-color-dark-tint: #383a3e;

  /** medium **/
  --ion-color-medium: #f0f0f0;
  --ion-color-medium-rgb: 152, 154, 162;
  --ion-color-medium-contrast: #f0f0f0;
  --ion-color-medium-contrast-rgb: 255, 255, 255;
  --ion-color-medium-shade: #f0f0f0;
  --ion-color-medium-tint: #f0f0f0;

  /** light **/
  --ion-color-light: #f4f5f8;
  --ion-color-light-rgb: 244, 244, 244;
  --ion-color-light-contrast: #AEAEAE;
  --ion-color-light-contrast-rgb: 174, 174, 174;
  --ion-color-light-shade: #d7d8da;
  --ion-color-light-tint: #f5f6f9;

    /** facebook **/
    --ion-color-facebook: #3578E5;
    --ion-color-facebook-rgb: 53, 120, 229;
    --ion-color-facebook-contrast: #ffffff;
    --ion-color-facebook-contrast-rgb: 255, 255, 255;
    --ion-color-facebook-shade: #2a60b7;
    --ion-color-facebook-tint: #5d93ea;

    /** google **/
    --ion-color-google: #D93025;
    --ion-color-google-rgb: 217, 48, 17;
    --ion-color-google-contrast: #ffffff;
    --ion-color-google-contrast-rgb:  255, 255, 255;
    --ion-color-google-shade: #ae261e;
    --ion-color-google-tint: #e15951;

    /** apple **/
    --ion-color-apple: #222428;
    --ion-color-apple-rgb: 34, 34, 34;
    --ion-color-apple-contrast: #ffffff;
    --ion-color-apple-contrast-rgb:  255, 255, 255;
    --ion-color-apple-shade: #1e2023;
    --ion-color-apple-tint: #383a3e;

}

.ion-color-facebook {
  --ion-color-base:  #3578E5;
  --ion-color-base-rgb: 53, 120, 229;
  --ion-color-contrast:  #ffffff;
  --ion-color-contrast-rgb:  255, 255, 255;
  --ion-color-shade: #2a60b7;
  --ion-color-tint: #5d93ea;
}

.ion-color-google {
  --ion-color-base:  #D93025;
  --ion-color-base-rgb: 217, 48, 17;
  --ion-color-contrast:  #ffffff;
  --ion-color-contrast-rgb:  255, 255, 255;
  --ion-color-shade: #ae261e;
  --ion-color-tint: #e15951;
}

.ion-color-apple {
  --ion-color-dark: #222428;
  --ion-color-dark-rgb: 34, 34, 34;
  --ion-color-dark-contrast: #ffffff;
  --ion-color-dark-contrast-rgb: 255, 255, 255;
  --ion-color-dark-shade: #1e2023;
  --ion-color-dark-tint: #383a3e;
}

.ion-color-custombackground {
  --ion-color-dark: #f0f0f0;
  --ion-color-dark-rgb: 240, 240, 240;
  --ion-color-dark-contrast: #f0f0f0;
  --ion-color-dark-contrast-rgb: 240, 240, 240;

}</code></pre>
<div class="page-break"></div>
<p>and global scss style with modifying the  <strong>src/app/global.scss</strong> file:</p>
<pre><code class="language-css">/*
 * App Global CSS
 * ----------------------------------------------------------------------------
 * Put style rules here that you want to apply globally. These styles are for
 * the entire app and not just one component. Additionally, this file can be
 * used as an entry point to import other CSS/Sass files to be included in the
 * output CSS.
 * For more information on global stylesheets, visit the documentation:
 * https://ionicframework.com/docs/layout/global-stylesheets
 */

/* Core CSS required for Ionic components to work properly */
@import "~@ionic/angular/css/core.css";

/* Basic CSS for apps built with Ionic */
@import "~@ionic/angular/css/normalize.css";
@import "~@ionic/angular/css/structure.css";
@import "~@ionic/angular/css/typography.css";
@import '~@ionic/angular/css/display.css';

/* Optional CSS utils that can be commented out */
@import "~@ionic/angular/css/padding.css";
@import "~@ionic/angular/css/float-elements.css";
@import "~@ionic/angular/css/text-alignment.css";
@import "~@ionic/angular/css/text-transformation.css";
@import "~@ionic/angular/css/flex-utils.css";

// Font
@import url('https://fonts.googleapis.com/css?family=Lato:400,400i,700,900&amp;display=swap');

:root,
.md,
.ios {
    --ion-font-family: 'Lato', sans-serif;
    --ion-danger-gradient-start: #FF5A5F;
    --ion-danger-gradient-end: #FE4D88;
}

ion-title {
    font-size: 26px;
    font-weight: 700;
} 
[padding-24] {
    --padding-start: 24px;
    --padding-end: 24px;
    --padding-top: 24px;
    --padding-bottom: 24px;
}

[padding-lr-24] {
    --padding-start: 24px;
    --padding-end: 24px;
}

[padding-lr-16] {
    --padding-start: 16px;
    --padding-end: 16px;
}

ion-button {

    &amp;:not([shape="round"]) {
        --border-radius: 10px;
    }

    font-weight: 700;
    letter-spacing: normal;

    &amp;:not(.button-large) {
        &amp;:not(.button-small) {
            min-height: 44px;
            font-size: 12px;
        }

    }

    &amp;:not(.button-small) {
        &amp;:not(.button-large) {
            min-height: 44px;
            font-size: 12px;
        }

    }

    &amp;.button-large {
        font-size: 16px;
        min-height: 54px;

        ion-icon {
            font-size: 18px;

        }
    }

    &amp;.danger-gradiant {

         --ion-color-base: linear-gradient(135deg, var(--ion-danger-gradient-start) 0%, var(--ion-danger-gradient-end) 100%) !important;
    }
}

.auth-container {
    background-color: #f0f0f0;
    .form {
        background-color: #f0f0f0;
        .logo {
            margin: 8vh 0;
        }
    }

    .form-title {
        font-size: 40px;
        color: var(--ion-color-dark);
        margin-bottom: 24px;
        margin-top: 43px;
    }
}

.instruction {
    text-align: center;
    margin-top: 5vh;
    font-size: 15px;
    color: var(--ion-color-dark)
}

.buttons {
    ion-button {
        margin-top: 24px;
        margin-bottom: 24px;

        ion-icon {
            font-size: 14px;
            margin: 10px;
        }
    }
}

ion-footer {
    &amp;[auth] {
        .option-auth {
            p {
                margin: 0;
                font-size: 15px;
                color: var(--ion-color-dark)
            }

            ion-button {
                font-size: 15px;
                text-transform: none;
                height: 20px;
                margin: 0;
                margin-bottom: 8px;
                min-height: unset;
            }
        }

    }
}

ion-list {
    &amp;[login-form] {
        margin-bottom: 0;
        background-color: #f0f0f0;
        ion-item {
            border-radius: 5px;
            border: 1px solid #E7E9EB;
            margin: 16px 0;

            ion-icon {
                &amp;[slot="start"] {
                    font-size: 20px;
                    color: #707070;
                }
            }
        }
    }
}

.or {
    display: block;
    position: relative;
    text-align: center;
    margin-top: 24px;
    margin-bottom: 24px;

    &amp;:before {
        $lineWidth: 120px;
        width: $lineWidth;
        height: 1px;
        content: "";
        background: #E4E4E4;
        position: absolute;
        left: 50%;
        top: 50%;
        margin-left: -$lineWidth/2;
    }

    span {
        background: #f0f0f0;
        font-size: 14px;
        color: #707070;
        font-weight: 700;
        padding: 0 16px;
        position: relative;
        z-index: 1;
    }
}

.toolbar-logo-back {
    margin-left: -27px;
    text-align: center;

    img {
        max-height: 90px;
        object-fit: contain;
        margin: 0 auto;
    }
}

.toolbar-logo {
    text-align: center;
    color:#f0f0f0;
    img {
        width: 150px;
        object-fit: contain;
        margin: 0 auto;
        margin-top: 20px;
    }
}

.footer-partner {
    padding: 16px 24px;

    ion-button {
        margin-bottom: 16px;
    }

    .partners {
        margin-left: -8px;
        margin-right: -8px;

        .row-partners {
            padding: 8px;
            text-align: center;
            display: flex;
            align-items: center;
            justify-content: space-between;

            img {
                max-width: 58px;
                margin: 0 auto;
            }
        }
    }
}

ion-footer {
    &amp;[onrow] {
        ion-button {
            margin-bottom: 4px;
        }

        .footer-partner {
            padding-top: 0;

            .partners {
                .row-partners {
                    margin-left: -1px;
                    margin-right: -1px;
                    display: flex;
                    align-items: center;

                    .img {
                        padding-left: 1px;
                        padding-right: 1px;

                        img {
                            width: auto;
                            height: auto;
                            max-width: 100%;
                        }
                    }
                }
            }
        }
    }
}
ion-segment{
    padding: 3px;
    background: rgba(118,118,128,0.24);
    width: 100%;
    &amp;[shorting]{
        ion-segment-button{
            --background-checked:var(--ion-color-danger) !important;
            --color-checked: var(--ion-color-danger-contrast) !important;
            --indicator-height:0px;
            min-height: 42px;
            border-radius: 8px;
            border: none;
            position: relative;
            max-width: unset !important;
            &amp;+ion-segment-button{
                &amp;:before{
                    position: absolute;
                    left: 0;
                    height: 70%;
                    top: 15%;
                    width: 1px;
                    content: "";
                    background: rgba(142,142,147,0.30);
                }
            }
        }
    }
}
.setting-modal .modal-wrapper {
    height: 35%;
    top: 65%;
    background-color: white;
    position: absolute; 
    display: block;  
}

.popover-content.sc-ion-popover-ios {
    border-radius: 10px;
    top: 189px;
    left: calc(50px - var(--ion-safe-area-right, 0px))!important;
    transform-origin: right top;
    width: auto;
}</code></pre>
<blockquote>
<p>Please note you don&#8217;t need to understand everything in this code. Usually i used a purchased template or ask a designer to create the <strong>Ionic</strong> design template files for me. </p>
</blockquote>
<div class="page-break"></div>
<p>Now we will modify the <strong>src/pages/register-page/register-page.module.ts</strong> file to load <strong>ReactiveFormsModule</strong>  library which is mandatory to use the form we defined previously:</p>
<pre><code class="language-typescript">import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { ReactiveFormsModule } from '@angular/forms';

import { IonicModule } from '@ionic/angular';

import { RegisterPagePageRoutingModule } from './register-page-routing.module';

import { RegisterPage } from './register-page.page';

@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    ReactiveFormsModule,
    IonicModule,
    RegisterPagePageRoutingModule
  ],
  declarations: [RegisterPage]
})
export class RegisterPagePageModule {}
</code></pre>
<p>Last step is to edit our <strong>src/pages/register-page/register-page.page.ts</strong> file to implement the code of our <strong>RegisterPage</strong>. </p>
<p>If you pay attention to the <strong>HTML</strong> code that we wrote, the user can read the terms and conditions of our application. This is a mandatory step if you want your app to be validated by Apple. To do so we will use a <strong>Ionic</strong> library called <strong>IAppBrowser</strong> to open an external page in a browser pointing to an external terms &amp; conditions page.</p>
<p>Let&#8217;s install this library. Open a terminal, and write:  </p>
<pre><code class="language-shell">npm install cordova-plugin-inappbrowser
npm install @ionic-native/in-app-browser</code></pre>
<p>Now we can write the code of our view:</p>
<pre><code class="language-typescript">import { AfterViewInit, Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { LoadingController, Platform, AlertController } from '@ionic/angular';
import { InAppBrowser } from '@ionic-native/in-app-browser/ngx';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ApiserviceService } from 'src/app/services/api-service.service';

@Component({
  selector: 'app-register',
  templateUrl: './register-page.page.html',
  styleUrls: ['./register-page.page.scss'],
})
export class RegisterPage implements OnInit, AfterViewInit {
  public submitAttempt: boolean = false;
  public userForm: FormGroup;

  showWrongPattern = false;
  constructor(
    public loadingController: LoadingController,
    public router: Router,
    public platform: Platform,
    public alertController: AlertController,
    public apiService: ApiserviceService,
    public formBuilder: FormBuilder,
    public inAppBrowser: InAppBrowser) {

    this.userForm = formBuilder.group({
      firstName: ['', Validators.compose([Validators.minLength(3), Validators.required])],
      lastName: ['', Validators.compose([Validators.minLength(3), Validators.required])],
      email: ['', Validators.compose([Validators.required, Validators.pattern('^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$')])],
      password: ['', Validators.compose([
        Validators.minLength(8),
        Validators.required,
        Validators.pattern('(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[€$@$!%*?&amp;])[A-Za-z\d$@€$!%*?&amp;].{8,}')
      ])],
      confirmpassword: ['', Validators.compose([
        Validators.minLength(8),
        Validators.required,
        Validators.pattern('(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[€$@$!%*?&amp;])[A-Za-z\d$@€$!%*?&amp;].{8,}')
      ])],
    });

  }

  ngOnInit() {
  }

  ngAfterViewInit() {
  }

  cgu() {
    // Your CGU url
    let url = "https://policies.google.com/terms"
    let target = "_blank"
    this.inAppBrowser.create(url, target, "location=no,zoom=no")
  }
}</code></pre>
<p>The most important part in this code is the:</p>
<pre><code class="language-typescript">this.userForm = formBuilder.group({
      firstName: ['', Validators.compose([Validators.minLength(3), Validators.required])],
      lastName: ['', Validators.compose([Validators.minLength(3), Validators.required])],
      email: ['', Validators.compose([Validators.required, Validators.pattern('^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$')])],
      password: ['', Validators.compose([
        Validators.minLength(8),
        Validators.required,
        Validators.pattern('(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[€$@$!%*?&amp;])[A-Za-z\d$@€$!%*?&amp;].{8,}')
      ])],
      confirmpassword: ['', Validators.compose([
        Validators.minLength(8),
        Validators.required,
        Validators.pattern('(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[€$@$!%*?&amp;])[A-Za-z\d$@€$!%*?&amp;].{8,}')
      ])],
    });</code></pre>
<p>We create an <strong>Angular form group</strong> which indicates which fields are required and which are the validation rules.<br />
It&#8217;s quite easy to read and understand the rules by reading this code. </p>
<blockquote>
<p>If the form is invalid (rules are not valid), then the <strong>Create account</strong> button will be disable)</p>
</blockquote>
<p>Ok let&#8217;s try to run our <strong>Ionic</strong> application to see the results in a browser:</p>
<pre><code class="language-shell">ionic serve</code></pre>
<p>Once compiled and ready, <strong>Ionic</strong> will automatically opens your browser at the following url : <a href="http://localhost:8100/">http://localhost:8100/</a> which is the default one. You should see a blank page on the right side and a menu on the left side:</p>
<p><img src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/07/ionic_blank_page.png" alt="FirstRun" /></p>
<p>The menu is a <strong>Ionic default standard one</strong> and is configured in the <strong>app.component.html</strong> file. We will focus on it later. </p>
<p>To get our <strong>RegisterPage</strong> we need to go to the specific url we define in our <strong>app-routing.module.ts</strong> file which is : <a href="http://localhost:8100/register-page">http://localhost:8100/register-page</a></p>
<p><img src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/07/Capture-décran-2021-07-19-à-13.55.19.png" alt="RegisterPage" /></p>
<p>If you want to check the result on a mobile screen, you can switch your browser to <a href="https://developer.chrome.com/docs/devtools/">development mode</a> and choose an iPhone to render the view: </p>
<p><img src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/07/Capture-décran-2021-07-19-à-13.57.36.png" alt="Devmode" /></p>
<p>Now if you fill the form with required information, the <strong>Create account</strong> button should activate, but with no effect since we didn&#8217;t implement the <strong>register()</strong> method.</p>
<p>Let&#8217;s focus on this. Edit our <strong>src/pages/register-page/register-page.page.ts</strong> file to implement the code:</p>
<pre><code class="language-typescript">register() {
    this.submitAttempt = true;
    let firstName = this.userForm.value["firstName"]
    let lastName = this.userForm.value["lastName"]
    let email = this.userForm.value["email"]
    let password = this.userForm.value["password"]
    let confirmpassword = this.userForm.value["confirmpassword"]

    if (password != confirmpassword) {
      this.apiService.showError("Passwords don't match");
    }
    else if (password.length&lt;6){

    }
    else {
      //chek si le pseudo est disponible

      if (this.apiService.networkConnected) {
        this.apiService.showLoading().then(() =&gt; {
          this.showWrongPattern=false;
           let params = {
                  "email": email,
                  "password": password,
                  "firstName":firstName,
                  "lastName":lastName
          }
          this.createAccount(params)
        })
      }
      else {
        this.apiService.showNoNetwork();
      }
    }
  }</code></pre>
<p>This method will get the values submitted with the form, then check if passwords are matching, then checks if network is connected (we will use our standard methods defined earlier to do this) or display a no network message, and if everything is ok will call a <strong>createAccount</strong> method that we will implement now:</p>
<pre><code class="language-typescript"> createAccount(params) {
    if (this.apiService.networkConnected) {
      this.apiService.registerUser(params).subscribe((resultat) =&gt; {
        let status = resultat["status"];
        console.log(status)
        if (status == "OK") {
          //User created 
          this.apiService.stopLoading();
          let data = resultat["data"]
          console.log(data)
        }
        else {
          this.apiService.stopLoading();
          let error = resultat["error"]
          console.log(error)
          if (error.status == 400) {
            this.apiService.showError('An account already exists for this email. Please login');
          }
          else {
            this.apiService.showError("An error occured")
          }
      }
      })
    }
    else {
      this.apiService.stopLoading();
      this.apiService.showNoNetwork()
    }
  }</code></pre>
<p>This method is quite simple too. It checks if network is still connected (or display an error message), then  call a method <strong>registerUser</strong> of our <strong>apiService</strong> file and check the result status of this call. If the status is <strong>OK</strong> the account has been created otherwise an error message is showned.</p>
<h3>Adding a registerUser method in our ApiService file</h3>
<p>The <strong>registerUser</strong> method will call our backend to create a user as we have seen earlier on previous tutorial.</p>
<p>If we refer to the documentation of our <strong>JWT API</strong>, we can create a user with an HTTP post call and passing paramaters <strong>email</strong>,<strong>password</strong>: </p>
<pre><code class="language-shell">curl -X POST \
  http://127.0.0.1:8000/auth/users/ \
  -H 'Content-Type: application/json' \
  -d '{"email": "csurbier@idevotion.fr", "password": "YOURPASSWORD_75$"}'</code></pre>
<p>So let&#8217;s implement this method in our <strong>api-service.service.ts</strong> file by adding the following code:</p>
<pre><code class="language-typescript">registerUser(params) {
    const options = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json'
      })
    };
    return new Observable(observer =&gt; {
      this.http.post(this.getCreateUserUrl, params, options).subscribe(
        (val) =&gt; {
          observer.next({ "status": "OK", "data": val })
          observer.complete()
        },
        response =&gt; {
          console.log("POST call in error", response);
          observer.next({ "status": "KO", "error": response })
          observer.complete()
        },

        () =&gt; {

        });
    })
  }</code></pre>
<blockquote>
<p>the <strong>getCreateUserUrl</strong> has already been defined in our <strong>api-service.service.ts</strong> file and it&#8217;s value is</p>
<pre><code class="language-typescript">this.getCreateUserUrl = this.virtualHostName + "auth/users/"</code></pre>
</blockquote>
<div class="page-break"></div>
<blockquote>
<p>The <strong>virtualHostName</strong> comes from the definition:</p>
</blockquote>
<pre><code class="language-typescript">export const domainConfig = {
  client: 'chattuto',
  virtual_host: 'http://127.0.0.1:8000/',
  domainApp: 'http://127.0.0.1:8000/',
  staticStorage: "static/storage/"
}</code></pre>
<blockquote>
<p>We will see later how to deploy our backend code on a real server but for now we run it on our local machine. </p>
</blockquote>
<p>With that in place, we can test our <strong>Create account</strong> button, but of course we need to have our backend running by launching the command:</p>
<pre><code class="language-shell">python manage.py runserver</code></pre>
<p>Now if you fill the form and click the <strong>Create account</strong> button, you should see in your javascript console log, the following values: </p>
<pre><code class="language-shell">OK
register-page.page.ts:88 {email: "testcsurbier@idevotion.fr", id: "42266767-119e-486f-890d-db67c85cf5ec"}</code></pre>
<p>Our backend processes the request correctly and send us back the identifier (<strong>id</strong> parameter in the json) of our user. </p>
<p>We can also use the <a href="http://127.0.0.1:8000/admin/chat/user/"><strong>Django admin</strong></a> to check that the user has been created.</p>
<p>If we check the user values with the <strong>Django admin</strong> we can see that first_name and last_name values are empty. It&#8217;s because the <strong>JWT API</strong> only takes an email and a password as arguments. </p>
<p>You can find code of this tutorial as usual on my <a href="https://github.com/csurbier/chattuto/tree/main/day-4">Github</a></p>
<p>In next tutorial, we will see how to complete the <strong>User</strong> fields and how to get the <strong>JWT access token</strong> to automatically authenticate the user when doing <strong>API</strong> calls.</p>
<div class="page-break"></div>
<h3>Questions/Answers</h3>
<ol>
<li>Modify this code snippet to have first name and last name not mandatory</li>
</ol>
<pre><code class="language-typescript">this.userForm = formBuilder.group({
      firstName: ['', Validators.compose([Validators.minLength(3), Validators.required])],
      lastName: ['', Validators.compose([Validators.minLength(3), Validators.required])],
      email: ['', Validators.compose([Validators.required, Validators.pattern('^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$')])],
      password: ['', Validators.compose([
        Validators.minLength(8),
        Validators.required,
        Validators.pattern('(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[€$@$!%*?&amp;])[A-Za-z\d$@€$!%*?&amp;].{8,}')
      ])],
      confirmpassword: ['', Validators.compose([
        Validators.minLength(8),
        Validators.required,
        Validators.pattern('(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[€$@$!%*?&amp;])[A-Za-z\d$@€$!%*?&amp;].{8,}')
      ])],
    });

  }</code></pre>
<blockquote>
<p>Answer</p>
</blockquote>
<pre><code class="language-typescript">this.userForm = formBuilder.group({
      firstName: [''],
      lastName: [''],
      email: ['', Validators.compose([Validators.required, Validators.pattern('^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$')])],
      password: ['', Validators.compose([
        Validators.minLength(8),
        Validators.required,
        Validators.pattern('(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[€$@$!%*?&amp;])[A-Za-z\d$@€$!%*?&amp;].{8,}')
      ])],
      confirmpassword: ['', Validators.compose([
        Validators.minLength(8),
        Validators.required,
        Validators.pattern('(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[€$@$!%*?&amp;])[A-Za-z\d$@€$!%*?&amp;].{8,}')
      ])],
    });

  }</code></pre>
<ol>
<li>How to run a <strong>ionic project</strong> and see results into a browser ?</li>
</ol>
<blockquote>
<p>Answer</p>
<pre><code class="language-shell">ionic serve</code></pre>
</blockquote>
<ol>
<li>If we want to add a method to dialog with our backend API (such a login for instance), in which file should we do this ? </li>
</ol>
<blockquote>
<p>Answer<br />
<strong>api-service.service.ts</strong> file</p>
</blockquote>
<ol>
<li>In next tutorial, we will add code to update a <strong>User</strong> and set it&#8217;s first name and last name values. Refering to our previous tutorial and the <strong>API</strong> documentation (<a href="http://127.0.0.1:8000/documentation/">http://127.0.0.1:8000/documentation/</a>) which endpoint should we use ?  </li>
</ol>
<blockquote>
<p>Answer</p>
</blockquote>
<p>The <strong>/api/user/{id}/</strong> endpoint with a Patch Http request</p>
<div class="page-break"></div>
<p>The post <a rel="nofollow" href="https://www.ionicanddjangotutorial.com/day-4-ionic-application/">Day 4 : Ionic application</a> appeared first on <a rel="nofollow" href="https://www.ionicanddjangotutorial.com">Ionic and Django Tutorial</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Day 3 : Django Rest Framework API</title>
		<link>https://www.ionicanddjangotutorial.com/day-3-django-rest-framework-api/</link>
		
		<dc:creator><![CDATA[Christophe Surbier]]></dc:creator>
		<pubDate>Fri, 13 Aug 2021 15:32:06 +0000</pubDate>
				<category><![CDATA[Create a real world mobile chat application with Ionic and Django]]></category>
		<guid isPermaLink="false">https://www.ionicanddjangotutorial.com/?p=1320</guid>

					<description><![CDATA[<p>Create a chat application with Ionic and Django &#8211; Series &#8211; Part three Day 3 : Django Rest Framework API&#8230; <a href="https://www.ionicanddjangotutorial.com/day-3-django-rest-framework-api/" class="more-link">Continue Reading <span class="meta-nav">&#8594;</span></a></p>
<p>The post <a rel="nofollow" href="https://www.ionicanddjangotutorial.com/day-3-django-rest-framework-api/">Day 3 : Django Rest Framework API</a> appeared first on <a rel="nofollow" href="https://www.ionicanddjangotutorial.com">Ionic and Django Tutorial</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h1>Create a chat application with Ionic and Django &#8211; Series &#8211; Part three</h1>
<h1>Day 3 : Django Rest Framework API</h1>
<p>In the previous tutorial, we saw how to secure our Chat with <strong>JWT</strong> Token</p>
<p>In this tutorial, we will implement our API to manage our <strong>Chat</strong> and <strong>Message</strong> models.</p>
<p>First we will create a python directory in the root folder of our <strong>chattuto</strong> project:</p>
<pre><code class="language-shell">mkdir api</code></pre>
<p>Then we will add a new router <strong>api</strong> route in the <strong>chattuto/urls.py</strong> file:</p>
<pre><code class="language-python">from django.conf.urls import include
from django.urls import path
from django.contrib import admin

urlpatterns = [
    path('chat/', include('chat.urls')),
    path('admin/', admin.site.urls),
    path('auth/', include('djoser.urls')),
    path('auth/', include('djoser.urls.jwt')),
    path('api/', include('api.urls')),
]</code></pre>
<p>and add some configuration about <strong>Django Rest Framework</strong> in our <strong>chattuto/settings.py</strong> file:</p>
<pre><code class="language-python">REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
   'rest_framework_simplejwt.authentication.JWTAuthentication',
),
'DEFAULT_FILTER_BACKENDS': (
'django_filters.rest_framework.DjangoFilterBackend',
    'rest_framework.filters.OrderingFilter',
),
'DEFAULT_RENDERER_CLASSES': (
    'rest_framework.renderers.JSONRenderer',
    'rest_framework.renderers.BrowsableAPIRenderer',
),
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 20,
}</code></pre>
<blockquote>
<p>To secure our API we ask the framework to use the <strong>JWTAuthentication</strong> class.</p>
</blockquote>
<p>We will also add new libraries to our <strong>requirements.txt</strong>, one to extend filter possibilities with <strong>Rest framework</strong> and the other one to avoid <strong>CORS</strong> issues when we will call our <strong>API</strong> from <strong>Ionic application</strong>.<br />
The last one <strong>drf-yasg</strong> will generate the documentation for our <strong>API</strong></p>
<pre><code class="language-shell">Django==3.1.7
pillow==8.0.1
psycopg2==2.8.5 --no-binary psycopg2
channels==3.0.3
channels-redis==3.2.0
django-channels-jwt-auth-middleware==1.0.0
djoser==2.1.0
djangorestframework_simplejwt==4.6.0
djangorestframework==3.12.2
django-filter==2.4.0
django-cors-headers==3.7.0
drf-yasg==1.20.0</code></pre>
<p>We modify once again our <strong>chattuto/settings.py</strong> file to fullfill requirements of the <a href="https://github.com/adamchainz/django-cors-headers">Corsheaders library</a></p>
<pre><code class="language-python">INSTALLED_APPS = [
    'drf_yasg',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'channels',
    'chat',
    'rest_framework',
    'djoser',
    'corsheaders',
]</code></pre>
<div class="page-break"></div>
<pre><code class="language-python">MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

CORS_ALLOW_ALL_ORIGINS=True</code></pre>
<p>Let&#8217;s modify our <strong>chattuto/urls.py</strong> file to include a self generated documentation for our <strong>API</strong></p>
<pre><code class="language-python">from django.conf.urls import include
from django.urls import path
from django.contrib import admin
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
from rest_framework import permissions

schema_view = get_schema_view(
    openapi.Info(
        title="API",
        default_version='v1',
        description="API description",
        terms_of_service="https://www.google.com/policies/terms/",
        contact=openapi.Contact(email="contact@snippets.local"),
        license=openapi.License(name="BSD License"),
    ),
    public=True,
    permission_classes=(permissions.AllowAny,),
)
urlpatterns = [
    path('chat/', include('chat.urls')),
    path('admin/', admin.site.urls),
    path('auth/', include('djoser.urls')),
    path('auth/', include('djoser.urls.jwt')),
    path('api/', include('api.urls')),
    path('documentation/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
]</code></pre>
<p>Ok now let&#8217;s implement our <strong>API</strong>. Create an <strong>api/urls.py</strong> file into our <strong>api</strong> folder:</p>
<pre><code class="language-python">from django.conf.urls import include, url
from .views import *
from rest_framework.decorators import api_view
from rest_framework.routers import DefaultRouter
router = DefaultRouter()

urlpatterns = [
  url(r'^user/(?P&lt;pk&gt;[0-9A-Fa-f-]+)/$', UserDetailView.as_view()),
  url(r'^user/$', UserListView.as_view()),

  url(r'^chat/(?P&lt;pk&gt;[0-9A-Fa-f-]+)/$', ChatDetailView.as_view()),
  url(r'^chat/$', ChatListView.as_view()),

  url(r'^message/(?P&lt;pk&gt;[0-9A-Fa-f-]+)/$', MessageDetailView.as_view()),
  url(r'^message/$', MessageListView.as_view()),

]</code></pre>
<p>We added some endpoints on <strong>User</strong>, <strong>Chat</strong> and <strong>Message</strong>. now let&#8217;s write the <strong>api/serializers.py</strong> file :</p>
<pre><code class="language-python">from rest_framework.serializers import ModelSerializer
from chat.models import *

class UserSerializer(ModelSerializer):
    class Meta:
        ref_name="MyCustomUser"
        model = User
        fields = '__all__'</code></pre>
<div class="page-break"></div>
<pre><code class="language-python">class ChatSerializer(ModelSerializer):
    class Meta:
        model = Chat
        fields = '__all__'

class MessageSerializer(ModelSerializer):
    class Meta:
        model = Message
        fields = '__all__'</code></pre>
<blockquote>
<p>The <strong>ref_name</strong> field on <strong>User</strong> Meta class is to avoid conflict with the DRF YASG library (which implement <strong>user</strong> endpoint too).</p>
</blockquote>
<p>and finally an <strong>api/views.py</strong> file:</p>
<pre><code class="language-python">from django.contrib.auth.models import User
from rest_framework import generics, permissions
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters
from api.serializers import *
from chat.models import *
import logging

# Get an instance of a logger
logger = logging.getLogger('django')

class UserListView(generics.ListCreateAPIView):
    permission_classes = [permissions.IsAuthenticated]
    queryset = User.objects.all()
    serializer_class = UserSerializer
    filterset_fields = ['password','id','email','first_name','last_name','valid']
    filter_backends = [DjangoFilterBackend,filters.SearchFilter,filters.OrderingFilter]
    # Filter for connected user
    def get_queryset(self):
        user = self.request.user
        queryset = User.objects.filter(pk=user.id)
        return queryset

class UserDetailView(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = [permissions.IsAuthenticated]
    queryset = User.objects.all()
    serializer_class = UserSerializer

class ChatListView(generics.ListCreateAPIView):
    permission_classes = [permissions.IsAuthenticated]
    queryset = Chat.objects.all()
    serializer_class = ChatSerializer
    filterset_fields = ['id']
    filter_backends = [DjangoFilterBackend,filters.SearchFilter,filters.OrderingFilter]

class ChatDetailView(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = [permissions.IsAuthenticated]
    queryset = Chat.objects.all()
    serializer_class = ChatSerializer

class MessageListView(generics.ListCreateAPIView):
    permission_classes = [permissions.IsAuthenticated]
    queryset = Message.objects.all()
    serializer_class = MessageSerializer
    filterset_fields = ['id','type','isRead']
    filter_backends = [DjangoFilterBackend,filters.SearchFilter,filters.OrderingFilter]

class MessageDetailView(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = [permissions.IsAuthenticated]
    queryset = Message.objects.all()
    serializer_class = MessageSerializer
</code></pre>
<p>And voila if you launch the server and go to url <strong><a href="http://127.0.0.1:8000/documentation/">http://127.0.0.1:8000/documentation/</a></strong></p>
<div class="page-break"></div>
<p>You should see the documentation: </p>
<p><img src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/03/Capture-décran-2021-03-18-à-10.21.39.png" alt="API documentation" /> </p>
<p>Now you can refer to this documentation to know how to do CRUDS operations for our models <strong>User</strong>, <strong>Chat</strong>,<strong>Message</strong>.</p>
<p>And because we secured the <strong>API</strong> with <strong>JWT</strong>, you should not be able to try the endpoints without being authenticated </p>
<div class="page-break"></div>
<p><img src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/03/Capture-décran-2021-03-18-à-10.25.41.png" alt="Unauthenticated" /></p>
<p>which is why you can see a <strong>Django login</strong> button on the interface.</p>
<p>We can also identified ourselves as we learn in previous tutorial:</p>
<pre><code class="language-shell">curl -X POST \
  http://127.0.0.1:8000/auth/jwt/create/ \
  -H 'Content-Type: application/json' \
  -d '{"email": "csurbier@idevotion.fr", "password": "YOURPASSWORD"}'</code></pre>
<blockquote>
<p>Don&#8217;t forger to replace with your user and password values</p>
</blockquote>
<p>And once you have your access token, try to access the <strong>Chat</strong> api by specifying the <strong>JWT token</strong> in the <strong>Authorization</strong> header of your request:</p>
<pre><code class="language-shell">  curl --location --request GET 'http://127.0.0.1:8000/api/chat/' \
--header 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjE2MDYxNTU0LCJqdGkiOiJiZGUwYjZkMTg1MGI0ZDlmYjJmNmViNzgwYzRlMWRjNyIsInVzZXJfaWQiOiI5MDUyYTA4MC0yNGQ4LTRhMGUtOGQ5YS03NDIyMTNjMGJmOTEifQ.H0rU-trZdO_rPSD9HAqU-mlKb8JIY52STo68iDiL8wc'</code></pre>
<div class="page-break"></div>
<p>You should receive a JSON with the list of existing <strong>Chat</strong> (if you created one before with the admin):</p>
<pre><code class="language-json">{
    "count": 1,
    "next": null,
    "previous": null,
    "results": [
        {
            "id": "53713a91-e936-4169-9fef-4bc870a0babc",
            "createdAt": "2021-03-08T08:11:30.677821Z",
            "updatedAt": "2021-03-08T08:11:30.677921Z",
            "fromUser": "9052a080-24d8-4a0e-8d9a-742213c0bf91",
            "toUser": "b4db6ca7-21e0-4c83-b56b-f5c86c4cbc43"
        }
    ]
}</code></pre>
<p>Ok now that we have our <strong>API</strong> ready to use, it&#8217;s time to dive into our <strong>Ionic application</strong> in our next tutorial.</p>
<div class="page-break"></div>
<h2>Questions / Answers</h2>
<ol>
<li>What is the name of the library used to create an API ?<br />
<blockquote>
<p>Django Rest Framework</p>
</blockquote>
</li>
<li>Is it possible to use the library without beeing authenticated ?<br />
<blockquote>
<p>Yes. </p>
</blockquote>
</li>
<li>What should we add to our settings to require a JWT authentication ?<br />
<blockquote>
<p>Specify the authentication class in the Django Rest Framework configuration dictionnary:</p>
<pre><code class="language-python">REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
),</code></pre>
</blockquote>
</li>
<li>What should we add to each http query header to be authenticated ?</li>
</ol>
<blockquote>
<p>Add an &#8216;Authorization: Bearer <JWT_Access_token>&#8216; parameter.</p>
</blockquote>
<div class="page-break"></div>
<p>The post <a rel="nofollow" href="https://www.ionicanddjangotutorial.com/day-3-django-rest-framework-api/">Day 3 : Django Rest Framework API</a> appeared first on <a rel="nofollow" href="https://www.ionicanddjangotutorial.com">Ionic and Django Tutorial</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Day 2 : Save history of chat messages with Django and Django channel</title>
		<link>https://www.ionicanddjangotutorial.com/day-2-save-history-of-chat-messages-with-django-and-django-channel/</link>
		
		<dc:creator><![CDATA[Christophe Surbier]]></dc:creator>
		<pubDate>Fri, 13 Aug 2021 15:29:51 +0000</pubDate>
				<category><![CDATA[Create a real world mobile chat application with Ionic and Django]]></category>
		<guid isPermaLink="false">https://www.ionicanddjangotutorial.com/?p=1316</guid>

					<description><![CDATA[<p>Create a chat application with Ionic and Django &#8211; Series &#8211; Part two Day 2 : Save history of chat&#8230; <a href="https://www.ionicanddjangotutorial.com/day-2-save-history-of-chat-messages-with-django-and-django-channel/" class="more-link">Continue Reading <span class="meta-nav">&#8594;</span></a></p>
<p>The post <a rel="nofollow" href="https://www.ionicanddjangotutorial.com/day-2-save-history-of-chat-messages-with-django-and-django-channel/">Day 2 : Save history of chat messages with Django and Django channel</a> appeared first on <a rel="nofollow" href="https://www.ionicanddjangotutorial.com">Ionic and Django Tutorial</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h1>Create a chat application with Ionic and Django &#8211; Series &#8211; Part two</h1>
<h1>Day 2 : Save history of chat messages with Django and Django channel</h1>
<p>In the previous tutorial, we saw how to setup a Django project, and create models to manage a <strong>Chat</strong> application. Then we saw how to implement Django channel to have the foundation of our <strong>Chat</strong> backend server with Django. </p>
<p>In this tutorial, we will use our <strong>Chat</strong> and <strong>Message</strong> models to save our chats history, then we will add  authentication to secure our application and we will also add an API with Django Rest Framework to allow our Ionic mobile application interacts with our backend. </p>
<h2>Using our Django models with  Django channel to implement a chat</h2>
<p>In previous journey, we saw that we can create and access a chat using any string for our chat name, just by accessing an url like these :</p>
<pre><code class="language-shell">http://127.0.0.1:8000/chat/lobby/
http://127.0.0.1:8000/chat/toto/
http://127.0.0.1:8000/chat/titi/
...</code></pre>
<p>Because in the <strong>routing.py</strong> file we just define a path like this:</p>
<pre><code class="language-python">websocket_urlpatterns = [
    re_path(r'ws/chat/(?P&lt;room_name&gt;\w+)/$', consumers.ChatConsumer.as_asgi()),
]</code></pre>
<p>Now we will secure our channel and allow access only to existing <strong>Chat</strong>. To do that while etablishing the chat connexion, we will check that the chat identifier exists otherwise the connexion will be rejected. </p>
<p>To do so, we will modify our <strong>consumers.py</strong> file like this:</p>
<pre><code class="language-python">class ChatConsumer(WebsocketConsumer):
    def connect(self):
        try:
            self.room_name = self.scope['url_route']['kwargs']['room_name']
            chat = Chat.objects.get(id=self.room_name)
            self.room_group_name = 'chat_%s' % str(chat.id)
            # Join room group
            async_to_sync(self.channel_layer.group_add)(
                self.room_group_name,
                self.channel_name
            )

            self.accept()
        except Chat.DoesNotExist as e:
            print(e)
            return</code></pre>
<p>Nothing really difficult. We get the <strong>Chat</strong> id from the url path and we check in our <strong>Chat</strong> model if we have one with such id. If so we can etablish a <strong>Chat</strong> connexion otherwise an exception is thrown and we return without etablishing connexion.</p>
<p>The <strong>Chat id</strong> is an <strong>UDID</strong> field (like any ID in our models)</p>
<pre><code class="language-python">  id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)</code></pre>
<p>So security is even stronger because it will not be a &quot;guessable&quot; id such as usual integers (1,2&#8230;)</p>
<p>Finally our last step is to modify the <strong>routing.py</strong> url pattern  regex to match <strong>UDID</strong> format:</p>
<pre><code class="language-python">websocket_urlpatterns = [
    re_path('ws/chat/(?P&lt;room_name&gt;[-a-zA-Z0-9_]+)/', consumers.ChatConsumer.as_asgi()),
]</code></pre>
<p>And voilà, now if you try to access any url like previous one, it will fails. </p>
<p>To test our modifications, we can use the <strong>Django Admin</strong> to create a chat, grab the ID and then access our url with this id.</p>
<blockquote>
<p>To create a <strong>Chat</strong>, we need two users. We already created a superuser so we can create a new one, or use the admin to create <strong>Users</strong>. To do so, just register the <strong>User</strong> model in the <strong>admin.py</strong> file :</p>
<pre><code class="language-python">admin.site.register(User)</code></pre>
<div class="page-break"></div>
</blockquote>
<h2>Saving messages to have chat history</h2>
<p>Now we will modify our <strong>consumers.py</strong> file to save  history of sent messages. To do so we will send a json message containing information required by our <strong>models.py</strong> such as the <strong>refChat</strong>, <strong>refUser</strong>, <strong>message</strong>, <strong>type</strong> and <strong>extraData</strong></p>
<pre><code class="language-json">{
    refChat : refChat
    author : refUser
    message : message
    type : 0
    extraData : ""
}</code></pre>
<p>So we first need to modify our <strong>room.html</strong> file to hardcode values (for now):</p>
<pre><code class="language-html">document.querySelector('#chat-message-submit').onclick = function(e) {
            const messageInputDom = document.querySelector('#chat-message-input');
            const message = messageInputDom.value;
            chatSocket.send(JSON.stringify({
                "message"': message,
                "refChat" : "53713a91-e936-4169-9fef-4bc870a0babc",
                "author" : "9052a080-24d8-4a0e-8d9a-742213c0bf91",
                "type" : 0,
                "extraData" : ""
            }));
            messageInputDom.value = '';
        };</code></pre>
<blockquote>
<p>I have hardcoded the <strong>refChat</strong> and <strong>author</strong> with values that i got from the admin/databases. You need to adapt with your own values. </p>
</blockquote>
<p>Now we can modify the <strong>receive</strong> method of our <strong>consumers.py</strong>:</p>
<pre><code class="language-python">def receive(self, text_data):
    data_json = json.loads(text_data)
    print("===Received")
    print(data_json)

    message = data_json['message']
    # save message to database
    self.new_message(data_json)
    # Send message to room group
    async_to_sync(self.channel_layer.group_send)(
        self.room_group_name,
        {
            'type': 'chat_message',
            'message': message
        }
    )</code></pre>
<p>The method expects a <strong>JSON</strong>, grabs the message from the JSON and transmit to the room as usual, but before that it call a <strong>new_message</strong> function to save values to database. Here is the code of the <strong>new_message</strong> function:</p>
<pre><code class="language-python">def new_message(self,data):
    message = Message()
    message.refChat_id = data["refChat"]
    message.message = data["message"]
    message.author_id = data["author"]
    message.isRead = False
    message.type = data["type"]
    message.extraData = data["extraData"]
    message.save()</code></pre>
<p>Now you can try as usual the chat room to send a message : <a href="http://127.0.0.1:8000/chat/53713a91-e936-4169-9fef-4bc870a0babc/">http://127.0.0.1:8000/chat/53713a91-e936-4169-9fef-4bc870a0babc/</a></p>
<blockquote>
<p>Don&#8217;t forget to replace the <strong>chatId</strong> in the url with your own value</p>
</blockquote>
<p>After that go to the <strong>Django Admin</strong> and check that a value exists in your <strong>Message</strong> model</p>
<p><img src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/03/Capture-décran-2021-03-16-à-15.44.12.png" alt="Admin" /></p>
<p>Super easy. We will learn later how to use the <strong>type</strong> and <strong>extraData</strong> parameters and how to not hardcoded <strong>refChat</strong> and <strong>author</strong> while developping the real <strong>Ionic application</strong>.</p>
<p>Next step, we will secure our <strong>Chat</strong> to avoid anonymous user connexion.</p>
<h2>Secure our chat</h2>
<p>To secure our chat, <strong>users</strong> will need to be authenticated. To manage this easily, we will install the <a href="https://pypi.org/project/django-channels-jwt-auth-middleware/">Django Channels Jwt Auth Middleware library</a>. I choose this library because in our <strong>Ionic</strong> application, users will have to authenticate and we will use <strong>JWT token</strong> authentication.<br />
First we need to install the library by adding it in our <strong>requirements.txt</strong> file:</p>
<pre><code class="language-shell">Django==3.1.7
pillow==8.0.1
psycopg2==2.8.5 --no-binary psycopg2
channels==3.0.3
channels-redis==3.2.0
django-channels-jwt-auth-middleware==1.0.0</code></pre>
<p>or install it manually :</p>
<pre><code class="language-shell">pip install django-channels-jwt-auth-middleware==1.0.0</code></pre>
<p>And then we need to modify our <strong>asgi.py</strong> file to add a <strong>JWTAuthMiddlewareStack</strong></p>
<pre><code class="language-python"># chattuto/asgi.py
import os

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
import chat.routing
from django_channels_jwt_auth_middleware.auth import JWTAuthMiddlewareStack

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "chattuto.settings")

application = ProtocolTypeRouter({
  "http": get_asgi_application(),
  "websocket": JWTAuthMiddlewareStack(
        URLRouter(
            chat.routing.websocket_urlpatterns
        )
    ),
})</code></pre>
<div class="page-break"></div>
<p>Now we will modify our <strong>consumers.py</strong> file to reject each  unauthenticated connexion:</p>
<pre><code class="language-python">def connect(self):
    try:
        user = self.scope['user']
        print("=== user %s" % user)
        if str(user)=="AnonymousUser":
            print("==Not authorized")
            return
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        chat = Chat.objects.get(id=self.room_name)
        self.room_group_name = 'chat_%s' % str(chat.id)
        # Join room group
        async_to_sync(self.channel_layer.group_add)(
            self.room_group_name,
            self.channel_name
        )

        self.accept()
    except Chat.DoesNotExist as e:
        print(e)
        return</code></pre>
<p>If a user is unauthenticated the library provides an <strong>AnonymousUser</strong> value so we just need to check the value of our <strong>user</strong> and reject the connexion if it equals.</p>
<p>Now if you try to access the chat room, you will see in the console logs, that an error occurs and that the connexion is closed. </p>
<h2>Implementing an API with Dango Rest Framework</h2>
<p>At this step of the tutorial, we need to implement the <strong>JWT token</strong> authentication to be able to access and use our chat room, but we also want to manipulate the <strong>Chat</strong> and <strong>Message</strong> models to do C.R.U.D operations (Create, Read, Update, Delete). It&#8217;s time to implement an API for that. </p>
<blockquote>
<p>This API will be the link between our <strong>Django backend</strong> and our <strong>Ionic application</strong> to communicate.</p>
<p>As a reminder <strong>JWT (Json Web Token)</strong> is an authentication system based on secured token. To obtain a token, the user needs to provide his credentials. Once the user is logged in, each subsequent request will include the JWT, allowing the user to access routes, services, and resources that are permitted with that token.</p>
</blockquote>
<p><a href="https://django-rest-framework-simplejwt.readthedocs.io/en/latest/getting_started.html">Django Simple JWT</a> is a simple package to manage JWT authentication with Django.</p>
<p><a href="https://djoser.readthedocs.io/en/latest/">Django DJOSER</a> is another package with more features and which include Django Simple JWT. So we will use this package.</p>
<p><a href="https://www.django-rest-framework.org/">Django Rest Framework</a>  is a powerful and flexible toolkit for building Web APIs.</p>
<p>Let&#8217;s add them to our <strong>requirements.txt</strong></p>
<pre><code class="language-shell">djoser==2.1.0
djangorestframework_simplejwt==4.6.0
djangorestframework==3.12.2</code></pre>
<p>Then we need to modify our <strong>settings.py</strong> and <strong>INSTALLED_APPS</strong> dictionnary to list them:</p>
<pre><code class="language-python">INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'channels',
    'chat',
    'rest_framework',
    'djoser',
]</code></pre>
<p>And add new routes to our <strong>urls.py</strong> file :</p>
<pre><code class="language-python">from django.conf.urls import include
from django.urls import path
from django.contrib import admin

urlpatterns = [
    path('chat/', include('chat.urls')),
    path('admin/', admin.site.urls),
    path('auth/', include('djoser.urls')),
    path('auth/', include('djoser.urls.jwt')),
]</code></pre>
<div class="page-break"></div>
<p>Ok now with all that in place, we can use <strong>Django DJOSER</strong> to get our JWT token. Available endpoints can be found in the <a href="https://djoser.readthedocs.io/en/latest/getting_started.html#available-endpoints">documentation</a> but we will focus on the:</p>
<ul>
<li><strong>/jwt/create/</strong> (JSON Web Token Authentication)
<ul>
<li>Use to authenticate and obtain a JWT TOKEN</li>
</ul>
</li>
<li><strong>/jwt/refresh/</strong> (JSON Web Token Authentication)
<ul>
<li>Use to refresh an expired JWT Token</li>
</ul>
</li>
<li><strong>/jwt/verify/</strong> (JSON Web Token Authentication)
<ul>
<li>Use to verify if a JWT Token is still valid</li>
</ul>
</li>
</ul>
<p>JWT Tokens expires very quickly (5 minutes). It&#8217;s possible to modify some configuration values in the <strong>settings.py</strong></p>
<pre><code class="language-python">from datetime import timedelta

SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=180),
    'ROTATE_REFRESH_TOKENS': True,
    'BLACKLIST_AFTER_ROTATION': True,

    'ALGORITHM': 'HS256',
    'SIGNING_KEY': SECRET_KEY,
    'VERIFYING_KEY': None,
    'AUDIENCE': None,
    'ISSUER': None,

    'AUTH_HEADER_TYPES': ('Bearer',),
    'USER_ID_FIELD': 'id',
    'USER_ID_CLAIM': 'user_id',

    'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
    'TOKEN_TYPE_CLAIM': 'token_type',

    'JTI_CLAIM': 'jti',

    'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
    'SLIDING_TOKEN_LIFETIME': timedelta(minutes=30),
    'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=180),
}</code></pre>
<p>Ok now let&#8217;s try to authenticate a User. We can try with one of the <strong>superuser</strong> we created earlier. </p>
<pre><code class="language-shell">curl -X POST \
  http://127.0.0.1:8000/auth/jwt/create/ \
  -H 'Content-Type: application/json' \
  -d '{"email": "csurbier@idevotion.fr", "password": "YOURPASSWORD"}'</code></pre>
<blockquote>
<p>Don&#8217;t forger to replace with your user and password values</p>
</blockquote>
<p>And you should get a JSON response:</p>
<pre><code class="language-json">{
"refresh":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTYzMTUyMDg2MSwianRpIjoiNzc0Mjk1Y2I4OTA1NDZlMzkyM2Y0OWI1ZGVhZTNmM2EiLCJ1c2VyX2lkIjoiOTA1MmEwODAtMjRkOC00YTBlLThkOWEtNzQyMjEzYzBiZjkxIn0.lwvDFS-2CR_xKHBPe4rvPnT_IKVac1oVtTaM-Abuo2I",
"access":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjE1OTcwNjYxLCJqdGkiOiJjNGQ4ZTkzZjhhZjM0MzcxODMxYWM1MDNkZGJjMjk5NiIsInVzZXJfaWQiOiI5MDUyYTA4MC0yNGQ4LTRhMGUtOGQ5YS03NDIyMTNjMGJmOTEifQ.__IVxioV8RWahcdpC6GLeEUuk52r-UZ4zFoNgDoCzPM"
}</code></pre>
<p>The response contains the access token (our JWT token) and the refresh token to use when the access token expires.</p>
<blockquote>
<p>If credentials are not correct or if the TOKEN has expired, the endpoint will return an HTTP 401 response (unauthorised).</p>
</blockquote>
<p>To get a new token just use the <strong>refresh</strong> token </p>
<pre><code class="language-shell">curl -X POST \
  http://127.0.0.1:8000/auth/jwt/refresh/ \
  -H 'Content-Type: application/json' \
  -d '{"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTYzMTUyMDg2MSwianRpIjoiNzc0Mjk1Y2I4OTA1NDZlMzkyM2Y0OWI1ZGVhZTNmM2EiLCJ1c2VyX2lkIjoiOTA1MmEwODAtMjRkOC00YTBlLThkOWEtNzQyMjEzYzBiZjkxIn0.lwvDFS-2CR_xKHBPe4rvPnT_IKVac1oVtTaM-Abuo2I"}'</code></pre>
<p>In next tutorial, we will continue to implement our <strong>Django Rest Framework</strong> API to have all required stuff to be able to start our <strong>Ionic application</strong>. </p>
<div class="page-break"></div>
<h2>Questions / Answers</h2>
<ol>
<li>How to avoid having integers as primary keys in a model ?<br />
<blockquote>
<p>Use an UDID by implement this line of code in the model</p>
<pre><code class="language-python">id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)</code></pre>
</blockquote>
</li>
<li>What is the use of JWT for ?<br />
<blockquote>
<p>JSON WEB TOKEN is used to secure API</p>
</blockquote>
</li>
<li>What is an access token ?<br />
<blockquote>
<p>An access token is obtained with user credentials and is used on each request to access resources. </p>
</blockquote>
</li>
<li>What is the use of a refresh token ?<br />
<blockquote>
<p>An access token is expiring after some delay. A Refresh token is used to get a new fresh access token</p>
</blockquote>
</li>
</ol>
<div class="page-break"></div>
<p>The post <a rel="nofollow" href="https://www.ionicanddjangotutorial.com/day-2-save-history-of-chat-messages-with-django-and-django-channel/">Day 2 : Save history of chat messages with Django and Django channel</a> appeared first on <a rel="nofollow" href="https://www.ionicanddjangotutorial.com">Ionic and Django Tutorial</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Day 1: Setup and design Backend</title>
		<link>https://www.ionicanddjangotutorial.com/day-1-setup-and-design-backend/</link>
		
		<dc:creator><![CDATA[Christophe Surbier]]></dc:creator>
		<pubDate>Fri, 13 Aug 2021 14:40:57 +0000</pubDate>
				<category><![CDATA[Create a real world mobile chat application with Ionic and Django]]></category>
		<guid isPermaLink="false">https://www.ionicanddjangotutorial.com/?p=1296</guid>

					<description><![CDATA[<p>What will we build ? This tutorials will focus on how to create a real world mobile chat application with&#8230; <a href="https://www.ionicanddjangotutorial.com/day-1-setup-and-design-backend/" class="more-link">Continue Reading <span class="meta-nav">&#8594;</span></a></p>
<p>The post <a rel="nofollow" href="https://www.ionicanddjangotutorial.com/day-1-setup-and-design-backend/">Day 1: Setup and design Backend</a> appeared first on <a rel="nofollow" href="https://www.ionicanddjangotutorial.com">Ionic and Django Tutorial</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h2>What will we build ?</h2>
<p>This tutorials will focus on how to create a real world mobile chat application with <strong>Ionic 5 / Angular 12, Capacitor 3 and Django 3</strong>. The <strong>Chat</strong> application will manage private chat between two persons and is not a group chat application. </p>
<p>For this application, we can list these main functionalities :</p>
<ul>
<li>Login / Register / Forget password screens</li>
<li>List of chats screen with search user features</li>
<li>Chat screen : real time message with WebSocket, loading previous messages, sending attachments</li>
<li>Receiving push notifications</li>
</ul>
<p>So this is what this book is about: We will learn each steps required to create a real world chat mobile application with it&#8217;s associated backend in a day to day process.</p>
<p>On backend, we will use Django and Django channels to have a socket based chat and we will also learn how to save and retrieve chat history. </p>
<p>On frontend, we will develop a Ionic application with angular framework, which will communicate with our backend to manage the chats. we will deploy the application on mobile devices using Capacitor. </p>
<p>Ready to start ? </p>
<h1>Day 1 : setup and design Backend</h1>
<p>First we will focus on the backend development before diving into the Ionic part.</p>
<p>You can find the source code for this day one journey in <a href="https://github.com/csurbier/chattuto.git">GitHub</a></p>
<h2>Setup : Software required</h2>
<p>Before diving into the subject of this book, we need to install software and set up our environment for the backend part. If you are familiar with Django development you can skip this section.</p>
<h3>Python 3</h3>
<p>All the development will be done with <strong>python 3</strong> so i will assume that python 3 is already install on your computer. If not, you will find tutorials on the internet on how to install it.</p>
<h3>Pip</h3>
<p>PIP is the Python Package Installer and is really helpful to install Django and all the libraries required to develop any <strong>Django</strong> project.</p>
<p>If you are on a Mac just open a terminal and enter</p>
<pre><code class="language-bash">sudo easy_install pip</code></pre>
<h3>Virtualenv</h3>
<p>Virtualenv is a tool to create isolated python environment. It is a recommended and standard way to develop Django projetcs. So let&#8217;s begin.<br />
First go in a directory where you would like to create the backend.</p>
<pre><code class="language-bash">cd Programmation
mkdir ChatTuto
mkdir ChatTuto/Backend
mkdir ChatTuto/Frontend</code></pre>
<p>I create a <strong>ChatTuto</strong> directory and inside this directory, i create the <strong>Backend</strong> in which we will develop our backend and a <strong>Frontend</strong> directory in which we will develop the Ionic application.</p>
<p>To install virtualenv</p>
<pre><code class="language-bash">sudo pip install virtualenv</code></pre>
<p>And now we create the virtual environment into a directory named <strong>venv</strong></p>
<pre><code class="language-bash">virtualenv -p python3 venv</code></pre>
<p>Once installed, we need to activate the environment using the command:</p>
<pre><code class="language-bash">source venv/bin/activate</code></pre>
<h3>Django</h3>
<p>We can install Django 3</p>
<pre><code class="language-bash">pip install Django==3</code></pre>
<p>And now we can create our django backend which will be called <strong>chattuto</strong> </p>
<pre><code class="language-bash">django-admin startproject chattuto
cd chattuto</code></pre>
<p>Ok now we can begin to implement our models that will be used to deal with our <strong>Chat application</strong>.</p>
<h2>Conception</h2>
<p>For each mobile app that you need to develop, the process is always the same. Starting from the storyboard of the application, we can deduce which entities will be needed. From these entities, we can conceptualize the models, the back-office (web administration) required to manage data and finally as last step, design the API which will be consumed by the frontend application.</p>
<blockquote>
<p>Usually, i don&#8217;t recommend to implement the API without having seen application storyboards. Because it is better to implement API endpoints that will fulfill each screen requirements and then improve drastically the performance.<br />
If you don&#8217;t proceed that way, to provide data to a screen, then may be you will need to make multiple http requests whereas only one request could have done the job. </p>
</blockquote>
<p>For our <strong>Chat</strong> application, we need to display a list of chats and messages inside a choosen chat. And of course a chat is between two users, so we can easily deduce the following models:</p>
<ul>
<li>User</li>
<li>Chat</li>
<li>Message</li>
</ul>
<p>This is a good start to design our Django models.</p>
<h2>Designing models</h2>
<p>We need to create our first app in Django to declare our models and then we will add a backoffice which will be used by the administrator of the mobile application. To create this <strong>chat</strong> application in Django:</p>
<pre><code class="language-bash">python manage.py startapp chat</code></pre>
<p>Inside the newly created <strong>chat</strong> directory, Django will initialize some standard files for us. At this stage, we are interested with the <strong>models.py</strong> file.</p>
<blockquote>
<p>I didn&#8217;t mentioned earlier but i use <a href="https://www.jetbrains.com/fr-fr/pycharm/">PyCharm</a> to develop all my Django projects. You will find a community edition (FREE to use) or a PRO edition. But you can also use Eclipse with PyDev plugin, or VSCode. It&#8217;s really up to you.</p>
</blockquote>
<h3>User model</h3>
<p>Most of mobile application have users who can subscribe or login, so this will be the first entity we need. By default, Django includes a <strong>User</strong> model entity and all required methods to deals with authentification. </p>
<p>But by default in a Django project, the field used to manage authentication is a <strong>username</strong> whereas for a mobile application it makes more sense to use an <strong>email</strong> for authentication process.</p>
<p>However it is possible to easily modify the Django default mechanism to use an email. To do so we have to extend the default <strong>User</strong> entity.</p>
<p>So let&#8217;s do it:</p>
<pre><code class="language-python">class UserManager(BaseUserManager):
    use_in_migrations = True

    def _create_user(self, email, password, **extra_fields):
        """
        Creates and saves a User with the given email and password.
        """
        if not email:
            raise ValueError('The given email must be set')
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, email, password=None, **extra_fields):
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(email, password, **extra_fields)

    def create_superuser(self, email, password, **extra_fields):
        extra_fields.setdefault('is_superuser', True)

        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')

        return self._create_user(email, password, **extra_fields)

class User(AbstractBaseUser, PermissionsMixin):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    email = models.EmailField(_('email address'), unique=True)
    first_name = models.CharField(_('first name'), max_length=30, blank=True)
    last_name = models.CharField(_('last name'), max_length=30, blank=True)
    date_joined = models.DateTimeField(_('date joined'), auto_now_add=True)
    is_active = models.BooleanField(_('active'), default=True)
    is_staff = models.BooleanField(_('active'), default=True)
    avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
    lastConnexionDate = models.DateTimeField(null=True, blank=True)
    valid = models.BooleanField(default=True)

    objects = UserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')
    createdAt = models.DateTimeField(auto_now_add=True)
    updatedAt = models.DateTimeField(auto_now=True)

    @property
    def last_login(self):
        return self.lastConnexionDate

    def __str__(self):
        return u'%s' % (self.email)</code></pre>
<p>Now <strong>User</strong> can be authenticated with email because we set </p>
<pre><code class="language-bash">USERNAME_FIELD = 'email'</code></pre>
<blockquote>
<p>The <strong>lastConnexionDate</strong> can be used to track last connexion of the user into the mobile application. </p>
</blockquote>
<h3>Chat models</h3>
<p>A chat is a dialog conversation between two users. The dialog is composed of a list of text messages but it also can be something else than text such as photos, videos, &#8230;<br />
We can deduce that we will need two more models to store Chats and Messages.</p>
<pre><code class="language-python">class Chat(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    fromUser = models.ForeignKey(User, db_index=True,on_delete=models.SET_NULL, null=True,related_name="fromuser")
    toUser = models.ForeignKey(User, db_index=True,on_delete=models.SET_NULL, null=True,related_name="toUser")
    createdAt = models.DateTimeField(auto_now_add=True)
    updatedAt = models.DateTimeField(auto_now=True)
    unique_together = (("fromUser", "toUser"),)

    def __str__(self):
        return u'%s - %s' % (self.fromUser,self.toUser)

class Message(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    refChat = models.ForeignKey(Chat, db_index=True,on_delete=models.CASCADE)
    message = models.TextField()
    msg_type = (
        (0, "TEXT"),
        (1, "GEOLOC"),
        (2, "PHOTO"),
    )
    type = models.IntegerField(choices=msg_type, default=0)
    extraData = models.CharField(default='', null=True, blank=True, max_length=255)
    author = models.ForeignKey(User, db_index=True,related_name='author',on_delete=models.SET_NULL,null=True)
    isRead = models.BooleanField(default=False)
    createdAt = models.DateTimeField(auto_now_add=True)
    updatedAt = models.DateTimeField(auto_now=True)

    def __str__(self):
        return u'%s - %d' % (self.refChat,self.type)</code></pre>
<blockquote>
<p>A chat must be unique between two users so we set a <strong>unique_together</strong> key based on users primary keys.</p>
<p>A message refers to a chat, can be a text, a geolocation position (using latitude/longitude) or a photo. We could extend this message type based on the application requirements like videos, sound&#8230; And of course a message has an author (the <strong>User</strong> who wrotes the message)</p>
</blockquote>
<h2>Setup database</h2>
<p>To create our models, we need to install a database. I will use <strong>PostgreSQL</strong> with <strong>postgis</strong> extension. </p>
<p>On Mac, you will need <a href="https://postgresapp.com/"><strong>Postres</strong></a> software and you can use <a href="https://eggerapps.at/postico/"><strong>Postico</strong></a> on your local machine while developping. It&#8217;s quite easy to use and install.</p>
<p>On Windows or Linux, you will have to setup your database which can be quite painful. To connect to your database, you can use the free <a href="https://dbeaver.io/" title="**DBeaver**"><strong>DBeaver</strong></a> software.</p>
<p>I will assume that this step is done and will move forward on to next section.</p>
<blockquote>
<p>I strongly recommend to use <a href="https://www.clever-cloud.com/en/" title="**Clever cloud**"><strong>Clever cloud</strong></a> services, a french provider that i will use for these tutorials. With <strong>Clever cloud</strong> services, you can focus on your development features and not architecture. They offer a FREE database hosting for development. It&#8217;s like Heroku but cheaper and more performant.</p>
</blockquote>
<div class="page-break"></div>
<h2>Configure Django project</h2>
<p>We can configure our Django project to use our database. First we will need the <strong>psycopg2</strong> package which is the PostgreSQL python driver. Let&#8217;s add it to our <strong>requirements.txt</strong> file</p>
<pre><code class="language-bash">Django==3.1.7
pillow==8.0.1
psycopg2==2.8.5 --no-binary psycopg2</code></pre>
<p>I&#8217;m using the non binary version to be sure that the driver will be accurate with the server system libraries (it was a recommendation from the Clever cloud team).</p>
<blockquote>
<p>As you could have notice, i&#8217;m always specifiying the version of the library that i want to use. If you don&#8217;t do that while deploying your project on a server, the latest version of libraries will be installed and could lead to a bug (if something changed in a library). Specifying the version will avoid that.</p>
</blockquote>
<p>Don&#8217;t forget to install the libraries</p>
<pre><code class="language-bash">pip install -r requirements.txt</code></pre>
<p>Now, we can configure <strong>Django</strong> to use our database. We edit the <strong>settings.py</strong> file inside the <strong>chattuto</strong> directory and will replace the <strong>DATABASES</strong> section with:</p>
<pre><code class="language-bash">
AUTH_USER_MODEL = "chat.User"

# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
POSTGRESQL_ADDON_URI = os.getenv("POSTGRESQL_ADDON_URI")
POSTGRESQL_ADDON_PORT = os.getenv("POSTGRESQL_ADDON_PORT")
POSTGRESQL_ADDON_HOST = os.getenv("POSTGRESQL_ADDON_HOST")
POSTGRESQL_ADDON_DB = os.getenv("POSTGRESQL_ADDON_DB")
POSTGRESQL_ADDON_PASSWORD = os.getenv("POSTGRESQL_ADDON_PASSWORD")
POSTGRESQL_ADDON_USER = os.getenv("POSTGRESQL_ADDON_USER")
REDIS_URL= os.getenv("REDIS_URL")
DATABASES = {
    'default': {
        'ENGINE': 'django.contrib.gis.db.backends.postgis',
        'NAME': POSTGRESQL_ADDON_DB,
        'USER': POSTGRESQL_ADDON_USER,
        'PASSWORD': POSTGRESQL_ADDON_PASSWORD,
        'HOST': POSTGRESQL_ADDON_HOST,
        'PORT': POSTGRESQL_ADDON_PORT,
        'CONN_MAX_AGE': 1200,
    }
}</code></pre>
<p>I&#8217;m getting my database connexion parameters from environment variables and then set the databases dictionary keys with these values.</p>
<blockquote>
<p>Using environment variables it&#8217;s easy to switch from a development database to a production database. Values are not hardcoded.</p>
<p>Please notice the line <strong>AUTH_USER_MODEL = &quot;chat.User&quot;</strong>. Since we override the default Django User entity, this line is very important and tells Django that it needs to use our custom model.</p>
</blockquote>
<p>With <strong>PyCharm</strong> you can declare your environment variables in the settings of your project, or you can use a <strong>environment.env</strong> file and use this <a href="https://plugins.jetbrains.com/plugin/7861-envfile" title="plugin">plugin</a> to declare them, which is more easy.</p>
<p>Within my <strong>environment.env</strong> file, i declare my database connection parameters like this:</p>
<pre><code class="language-bash">POSTGRESQL_ADDON_URI=postgresql://u6rogoiqgpupp3mvhqi9:F1Kc6J1pKEt5X28iX4Fn@bx8gb6agpf58jh3rtupa-postgresql.services.clever-cloud.com:5432/bx8gb6agpf58jh3rtupa
POSTGRESQL_ADDON_PORT=5432
POSTGRESQL_ADDON_HOST=bx8gb6agpf58jh3rtupa-postgresql.services.clever-cloud.com
POSTGRESQL_ADDON_DB=b9pgqzgccizfpklha4gv
POSTGRESQL_ADDON_USER=u6rogoiqgpupp3mvhqi9
POSTGRESQL_ADDON_PASSWORD=&lt;YOURPASSWORD&gt;
DEBUG=True
MEDIA_URL_PREFIX=/media
STATIC_URL_PREFIX=/static</code></pre>
<blockquote>
<p>Don&#8217;t forget to replace these values with your own database connexion values</p>
</blockquote>
<p>We could also do the same for the <strong>DEBUG</strong> variable</p>
<pre><code class="language-bash">DEBUG = os.getenv("DEBUG")</code></pre>
<p>and add the DEBUG value in our <strong>environment.env</strong> file:</p>
<pre><code class="language-bash">DEBUG=True</code></pre>
<div class="page-break"></div>
<p>We can try to run our <strong>Django</strong> project with PyCharm and if database connexion is OK, we should see the following:</p>
<pre><code class="language-bash">Performing system checks...

System check identified no issues (0 silenced).

You have 17 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
August 07, 2020 - 11:24:32
Django version 3.0, using settings 'chattuto.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.</code></pre>
<p>To launch <strong>Django</strong> with command line, you will need to export your environment variables and then run in a terminal:</p>
<pre><code class="language-bash">python manage.py runserver</code></pre>
<blockquote>
<p>To export my environment variables, i usually use a text file and then source it (on Mac or Linux).</p>
</blockquote>
<h3>Create database tables</h3>
<p>At this point, <strong>Django</strong> tells us that we need to run migrations. You can learn more about migrations <a href="https://docs.djangoproject.com/en/3.0/topics/migrations/" title="here">here</a> but basically it means that we have modifications in our <strong>models.py</strong> file that are not reflected on the database which is true since we haven&#8217;t created it yet.</p>
<p>First we need to edit again the <strong>settings.py</strong> file to add our application <strong>chat</strong> to the <strong>INSTALLED_APPS</strong> dictionnary:</p>
<pre><code class="language-bash"># Application definition
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'chat'
]</code></pre>
<p>Now because we have overrided the default <strong>Django User</strong>, we first need to make a migrations with our <strong>chat</strong> application (which will tell Django where to find the <strong>User</strong> model) and then we can migrate to create all other tables required by Django.<br />
So let&#8217;s do it by typing in a terminal: </p>
<pre><code class="language-bash">python manage.py makemigrations chat
python manage.py migrate</code></pre>
<div class="page-break"></div>
<h2>Implement Chat server with Websockets and django channels</h2>
<p>[swpm_protected  format_protected_msg=1 custom_msg=&#8217;This content is for members only ! You can learn more here  <a href="https://www.ionicanddjangotutorial.com/learn-how-to-create-a-real-world-chat-application-with-ionic-and-django/">here</a> to access the content&#8217;]<br />
To implement our Chat backend we will use websockets which are more performant than HTTP requests and are asynchronous.<br />
To implement websockets within Django, [**Channels**](https://channels.readthedocs.io/en/stable/) has been introduce recently. You can read more about the definition on the channel website documentation.</p>
<p>Let&#8217;s add channel and daphne to our **requirements.txt** file</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
Django==3.1.7
pillow==8.0.1
psycopg2==2.8.5 --no-binary psycopg2
daphne==3.0.1
channels
channels_redis
</pre>
<p>and install it  </p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
pip install -r requirements.txt
</pre>
<p>From now and to finish this day one tutorial, we will just follow the [Channel Tutorials](https://channels.readthedocs.io/en/stable/tutorial/index.html) part 1 to 2. We will stop at tutorial two, because we don&#8217;t want our chat server to be asynchronous. </p>
<p>Before diving into that, we will create a superuser for our Django backend:</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
python manage.py createsuperuser
</pre>
<p>and we will implement our **admin.py** file to be able to manage Chat or Message models thru the admin:</p>
<pre data-enlighter-language="python" class="EnlighterJSRAW">
from django.contrib import admin

from chat.models import *


class ChatAdmin(admin.ModelAdmin):
    fieldsets = [
        (None, {&#039;fields&#039;: [&#039;fromUser&#039;,&#039;toUser&#039;]}),
    ]
    list_display = (&#039;fromUser&#039;,&#039;toUser&#039;,&#039;createdAt&#039;,&#039;updatedAt&#039;,)
    search_fields = (&#039;fromUser__email&#039;,&#039;toUser__email&#039;,&#039;fromUser__last_name&#039;,&#039;toUser__last_name&#039;,)
    raw_id_fields = (&#039;fromUser&#039;,&#039;toUser&#039;,)
    list_select_related = (&#039;fromUser&#039;,&#039;toUser&#039;,)


class MessageAdmin(admin.ModelAdmin):
    fieldsets = [
        (None, {&#039;fields&#039;: [&#039;refChat&#039;,&#039;message&#039;,&#039;author&#039;,&#039;isRead&#039;,&#039;type&#039;,&#039;extraData&#039;]}),
    ]
    list_display = (&#039;refChat&#039;,&#039;message&#039;,&#039;author&#039;,&#039;isRead&#039;,&#039;createdAt&#039;,)
    ordering = (&#039;-createdAt&#039;,)
    search_fields = (&#039;message&#039;,&#039;author__email&#039;,)
    raw_id_fields = (&#039;author&#039;,)
    list_select_related = (&#039;refChat&#039;,&#039;author&#039;,)

# Register your models here.
admin.site.register(Chat,ChatAdmin)
admin.site.register(Message,MessageAdmin)
</pre>
<p>Ok so now as i said we will just follow the [**Channel tutorials**](https://channels.readthedocs.io/en/stable/tutorial/index.html) to learn basics on Django channels.</p>
<div class="page-break"></div>
<p>### Adding an index view</p>
<p>Inside our **chat** directory, let&#8217;s create a template directory containing a **chat** directory</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
mkdir templates
mkdir templates/chat
</pre>
<p>Then we create an **index.html** file with the code</p>
<pre data-enlighter-language="html" class="EnlighterJSRAW">
&lt;!-- chat/templates/chat/index.html --&gt;
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;meta charset=&quot;utf-8&quot;/&gt;
    &lt;title&gt;Chat Rooms&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    What chat room would you like to enter?&lt;br&gt;
    &lt;input id=&quot;room-name-input&quot; type=&quot;text&quot; size=&quot;100&quot;&gt;&lt;br&gt;
    &lt;input id=&quot;room-name-submit&quot; type=&quot;button&quot; value=&quot;Enter&quot;&gt;

    &lt;script&gt;
        document.querySelector(&#039;#room-name-input&#039;).focus();
        document.querySelector(&#039;#room-name-input&#039;).onkeyup = function(e) {
            if (e.keyCode === 13) {  // enter, return
                document.querySelector(&#039;#room-name-submit&#039;).click();
            }
        };

        document.querySelector(&#039;#room-name-submit&#039;).onclick = function(e) {
            var roomName = document.querySelector(&#039;#room-name-input&#039;).value;
            window.location.pathname = &#039;/chat/&#039; + roomName + &#039;/&#039;;
        };
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
<p>This page will let us specify wich **chat** room we would like to join.<br />
Let&#8217;s write the Django view which will render this template. Edit the **views.py** file inside the **chat** directory (not the one from the template directory but from the Django chat application)</p>
<pre data-enlighter-language="python" class="EnlighterJSRAW">
# chat/views.py
from django.shortcuts import render

def index(request):
    return render(request, &#039;chat/index.html&#039;)
</pre>
<p>Then we need to create an url for this view. Edit or create an **urls.py** file if it doesn&#8217;t exists, and write </p>
<pre data-enlighter-language="python" class="EnlighterJSRAW">
# chat/urls.py
from django.urls import path

from . import views

urlpatterns = [
    path(&#039;&#039;, views.index, name=&#039;index&#039;),
]
</pre>
<p>The next step is to point the root URLconf at the chat.urls module. In **chattuto/urls.py**, add an import for django.conf.urls.include and insert an include() in the urlpatterns list, so you have:</p>
<pre data-enlighter-language="python" class="EnlighterJSRAW">
from django.conf.urls import include
from django.urls import path
from django.contrib import admin

urlpatterns = [
    path(&#039;chat/&#039;, include(&#039;chat.urls&#039;)),
    path(&#039;admin/&#039;, admin.site.urls),
]
</pre>
<p>Now we can launch the server</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
python manage runserver
</pre>
<p>And go to the following url : http://127.0.0.1:8000/chat/</p>
<p>You should see the text “What chat room would you like to enter?” along with a text input to provide a room name.</p>
<div class="page-break"></div>
<p>### Adding the channel library</p>
<p>Now it’s time to integrate Channels.</p>
<p>Let’s start by creating a root routing configuration for Channels. A Channels routing configuration is an ASGI application that is similar to a Django URLconf, in that it tells Channels what code to run when an HTTP request is received by the Channels server.</p>
<p>Start by adjusting the **chattuto/asgi.py** file to include the following code:</p>
<pre data-enlighter-language="python" class="EnlighterJSRAW">
# chattuto/asgi.py
import os

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
import chat.routing

os.environ.setdefault(&quot;DJANGO_SETTINGS_MODULE&quot;, &quot;chattuto.settings&quot;)

application = ProtocolTypeRouter({
  &quot;http&quot;: get_asgi_application(),
  &quot;websocket&quot;: AuthMiddlewareStack(
        URLRouter(
            chat.routing.websocket_urlpatterns
        )
    ),
})
</pre>
<p>Now add the Channels library to the list of installed apps. Edit the **chattuto/settings.py** file and add &#8216;channels&#8217; to the INSTALLED_APPS **settings.py** file</p>
<pre data-enlighter-language="python" class="EnlighterJSRAW">
INSTALLED_APPS = [
    &#039;django.contrib.admin&#039;,
    &#039;django.contrib.auth&#039;,
    &#039;django.contrib.contenttypes&#039;,
    &#039;django.contrib.sessions&#039;,
    &#039;django.contrib.messages&#039;,
    &#039;django.contrib.staticfiles&#039;,
    &#039;channels&#039;,
    &#039;chat&#039;
]
</pre>
<p>We also need to point Channels at the root routing configuration. Edit the **chattuto/settings.py** file again and add the following line:</p>
<pre data-enlighter-language="python" class="EnlighterJSRAW">
# Channels
ASGI_APPLICATION = &#039;chattuto.asgi.application&#039;
</pre>
<p>With Channels now in the installed apps, it will take control of the runserver command, replacing the standard Django development server with the Channels development server.</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
python manage runserver
</pre>
<p>And we will see in the console logs :</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
Django version 3.1.7, using settings &#039;chattuto.settings&#039;
Starting ASGI/Channels version 3.0.4 development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
</pre>
<p>The important line is **Starting ASGI/Channels**</p>
<div class="page-break"></div>
<p>### Adding the room view</p>
<p>Create a new file chat/templates/chat/room.html</p>
<pre data-enlighter-language="html" class="EnlighterJSRAW">
&lt;!-- chat/templates/chat/room.html --&gt;
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;meta charset=&quot;utf-8&quot;/&gt;
    &lt;title&gt;Chat Room&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;textarea id=&quot;chat-log&quot; cols=&quot;100&quot; rows=&quot;20&quot;&gt;&lt;/textarea&gt;&lt;br&gt;
    &lt;input id=&quot;chat-message-input&quot; type=&quot;text&quot; size=&quot;100&quot;&gt;&lt;br&gt;
    &lt;input id=&quot;chat-message-submit&quot; type=&quot;button&quot; value=&quot;Send&quot;&gt;
    {{ room_name|json_script:&quot;room-name&quot; }}
    &lt;script&gt;
        const roomName = JSON.parse(document.getElementById(&#039;room-name&#039;).textContent);

        const chatSocket = new WebSocket(
            &#039;ws://&#039;
            + window.location.host
            + &#039;/ws/chat/&#039;
            + roomName
            + &#039;/&#039;
        );

        chatSocket.onmessage = function(e) {
            const data = JSON.parse(e.data);
            document.querySelector(&#039;#chat-log&#039;).value += (data.message + &#039;\n&#039;);
        };

        chatSocket.onclose = function(e) {
            console.error(&#039;Chat socket closed unexpectedly&#039;);
        };

        document.querySelector(&#039;#chat-message-input&#039;).focus();
        document.querySelector(&#039;#chat-message-input&#039;).onkeyup = function(e) {
            if (e.keyCode === 13) {  // enter, return
                document.querySelector(&#039;#chat-message-submit&#039;).click();
            }
        };

        document.querySelector(&#039;#chat-message-submit&#039;).onclick = function(e) {
            const messageInputDom = document.querySelector(&#039;#chat-message-input&#039;);
            const message = messageInputDom.value;
            chatSocket.send(JSON.stringify({
                &#039;message&#039;: message
            }));
            messageInputDom.value = &#039;&#039;;
        };
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
<p>Then we can add the view function for the room view in **chat/views.py**</p>
<pre data-enlighter-language="python" class="EnlighterJSRAW">
# chat/views.py
from django.shortcuts import render

def index(request):
    return render(request, &#039;chat/index.html&#039;)

def room(request, room_name):
    return render(request, &#039;chat/room.html&#039;, {
        &#039;room_name&#039;: room_name
    })
</pre>
<p>And create the route for the room view in **chat/urls.py**:</p>
<pre data-enlighter-language="python" class="EnlighterJSRAW">
# chat/urls.py
from django.urls import path

from . import views

urlpatterns = [
    path(&#039;&#039;, views.index, name=&#039;index&#039;),
    path(&#039;&lt;str:room_name&gt;/&#039;, views.room, name=&#039;room&#039;),
]
</pre>
<p>We can start the server again :</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
python manage runserver
</pre>
<p>Go to http://127.0.0.1:8000/chat/ in your browser and to see the index page.</p>
<p>Type in “lobby” as the room name and press enter. You should be redirected to the room page at http://127.0.0.1:8000/chat/lobby/ which now displays an empty chat log.</p>
<p>Type the message “hello” and press enter. Nothing happens. In particular the message does not appear in the chat log. Why?</p>
<p>The room view is trying to open a WebSocket to the URL ws://127.0.0.1:8000/ws/chat/lobby/ but we haven’t created a consumer that accepts WebSocket connections yet. If you open your browser’s JavaScript console, you should see an error that looks like:</p>
<pre data-enlighter-language="javascript" class="EnlighterJSRAW">
WebSocket connection to &#039;ws://127.0.0.1:8000/ws/chat/lobby/&#039; failed: Unexpected response code: 500
</pre>
<p>### Creating a consumer</p>
<p>When Django accepts an HTTP request, it consults the root URLconf to lookup a view function, and then calls the view function to handle the request. Similarly, when Channels accepts a WebSocket connection, it consults the root routing configuration to lookup a consumer, and then calls various functions on the consumer to handle events from the connection.</p>
<p>We will write a basic consumer that accepts WebSocket connections on the path /ws/chat/ROOM_NAME/ that takes any message it receives on the WebSocket and echos it back to the same WebSocket.</p>
<p>Create a new file **chat/consumers.py**</p>
<pre data-enlighter-language="python" class="EnlighterJSRAW">
# chat/consumers.py
import json
from channels.generic.websocket import WebsocketConsumer

class ChatConsumer(WebsocketConsumer):
    def connect(self):
        self.accept()

    def disconnect(self, close_code):
        pass

    def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json[&#039;message&#039;]

        self.send(text_data=json.dumps({
            &#039;message&#039;: message
        }))
</pre>
<p>We need to create a routing configuration for the chat app that has a route to the consumer. Create a new file **chat/routing.py**:</p>
<pre data-enlighter-language="python" class="EnlighterJSRAW">
from django.urls import re_path

from . import consumers

websocket_urlpatterns = [
    re_path(r&#039;ws/chat/(?P&lt;room_name&gt;\w+)/$&#039;, consumers.ChatConsumer.as_asgi()),
]
</pre>
<p>Let’s verify that the consumer for the **/ws/chat/ROOM_NAME/** path works. Run migrations to apply database changes (Django’s session framework needs the database) and then start the Channels development server:</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
python manage.py migrate
python manage.py runserver
</pre>
<p>Go to the room page at http://127.0.0.1:8000/chat/lobby/ which now displays an empty chat log.</p>
<p>Type the message “hello” and press enter. You should now see “hello” echoed in the chat log.</p>
<p>However if you open a second browser tab to the same room page at http://127.0.0.1:8000/chat/lobby/ and type in a message, the message will not appear in the first tab. For that to work, we need to have multiple instances of the same ChatConsumer be able to talk to each other. Channels provides a channel layer abstraction that enables this kind of communication between consumers.</p>
<p>A channel layer is a kind of communication system. It allows multiple consumer instances to talk with each other, and with other parts of Django.</p>
<p>We will use **REDIS** has a channel layer. Once again i will use [**Clever cloud**](https://www.clever-cloud.com/en/) to setup a Redis server really easily. </p>
<div class="page-break"></div>
<p>Then i just need to update the **channel layer** config in my **chattutu/settings.py** file:</p>
<pre data-enlighter-language="python" class="EnlighterJSRAW">
REDIS_URL= os.getenv(&quot;REDIS_URL&quot;)
# Channels
ASGI_APPLICATION = &#039;chattuto.asgi.application&#039;
CHANNEL_LAYERS = {
    &#039;default&#039;: {
        &#039;BACKEND&#039;: &#039;channels_redis.core.RedisChannelLayer&#039;,
        &#039;CONFIG&#039;: {
            &quot;hosts&quot;: [REDIS_URL],
        },
    },
}
</pre>
<p>and declare a new environment variable based on the value provided by the **Clever cloud** add-on:</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
export REDIS_URL=&#039;redis://:&lt;YourPASSWORD&gt;@btka0rdhzdplteztka9i-redis.services.clever-cloud.com:3067&#039;
</pre>
<p>Now we can edit the **consumers.py* file again and replace the existing code with:</p>
<pre data-enlighter-language="python" class="EnlighterJSRAW">
# chat/consumers.py
import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer

class ChatConsumer(WebsocketConsumer):
    def connect(self):
        self.room_name = self.scope[&#039;url_route&#039;][&#039;kwargs&#039;][&#039;room_name&#039;]
        self.room_group_name = &#039;chat_%s&#039; % self.room_name

        # Join room group
        async_to_sync(self.channel_layer.group_add)(
            self.room_group_name,
            self.channel_name
        )

        self.accept()

    def disconnect(self, close_code):
        # Leave room group
        async_to_sync(self.channel_layer.group_discard)(
            self.room_group_name,
            self.channel_name
        )

    # Receive message from WebSocket
    def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json[&#039;message&#039;]

        # Send message to room group
        async_to_sync(self.channel_layer.group_send)(
            self.room_group_name,
            {
                &#039;type&#039;: &#039;chat_message&#039;,
                &#039;message&#039;: message
            }
        )

    # Receive message from room group
    def chat_message(self, event):
        message = event[&#039;message&#039;]

        # Send message to WebSocket
        self.send(text_data=json.dumps({
            &#039;message&#039;: message
        }))
</pre>
<p>When a user posts a message, a JavaScript function will transmit the message over WebSocket to a ChatConsumer. The ChatConsumer will receive that message and forward it to the group corresponding to the room name. Every ChatConsumer in the same group (and thus in the same room) will then receive the message from the group and forward it over WebSocket back to JavaScript, where it will be appended to the chat log.</p>
<p>And voila.</p>
<p>>Remember we use the the [**Channel tutorials**](https://channels.readthedocs.io/en/stable/tutorial/index.html) to learn basics on Django channels so please refer to it for more information or clarifications.</p>
<p> You can find the source code for this day one journey in [GitHub](https://github.com/csurbier/chattuto.git)</p>
<p>In next tutorial **Day two** we will learn how to modify our **Channel** to use our models **Chat** and **Message** and to implement persistent messages. We will also learn how to secure our **Chat server** with authentication and we will setup an API with **Django Rest Framework**.</p>
<p>## Questions / Answers </p>
<p>1. What are Django models ?  </p>
<p>>Models are classed which will represent your entities and will be used to create the database tables.<br />
2. By default, which field is using Django for authenticating users : Username or email ?<br />
>Username<br />
3. What is the library name to create web sockets with Django ?<br />
>Django Channels<br />
4. What do we have to setup to enable multiple asynchronous connection with Django Channels ?<br />
>A channel layer </p>
<div class="page-break"></div>
<p>[/swpm_protected]</p>
<p>The post <a rel="nofollow" href="https://www.ionicanddjangotutorial.com/day-1-setup-and-design-backend/">Day 1: Setup and design Backend</a> appeared first on <a rel="nofollow" href="https://www.ionicanddjangotutorial.com">Ionic and Django Tutorial</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>How to validate a phone number by sending an SMS code using Ionic,Django and SmsApi ?</title>
		<link>https://www.ionicanddjangotutorial.com/how-to-validate-a-phone-number-by-sending-an-sms-code-using-ionicdjango-and-smsapi/</link>
		
		<dc:creator><![CDATA[Christophe Surbier]]></dc:creator>
		<pubDate>Mon, 22 Feb 2021 13:25:17 +0000</pubDate>
				<category><![CDATA[Tutorials]]></category>
		<guid isPermaLink="false">https://www.ionicanddjangotutorial.com/?p=907</guid>

					<description><![CDATA[<p>In this tutorial, we will learn how to enter an international phone number within a Ionic application, and how to&#8230; <a href="https://www.ionicanddjangotutorial.com/how-to-validate-a-phone-number-by-sending-an-sms-code-using-ionicdjango-and-smsapi/" class="more-link">Continue Reading <span class="meta-nav">&#8594;</span></a></p>
<p>The post <a rel="nofollow" href="https://www.ionicanddjangotutorial.com/how-to-validate-a-phone-number-by-sending-an-sms-code-using-ionicdjango-and-smsapi/">How to validate a phone number by sending an SMS code using Ionic,Django and SmsApi ?</a> appeared first on <a rel="nofollow" href="https://www.ionicanddjangotutorial.com">Ionic and Django Tutorial</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In this tutorial, we will learn how to enter an international phone number within a Ionic application, and how to validate this phone number by sending an SMS code using a Django backend and SMSAPI services.</p>


<p><strong><em>Please notice</em></strong>, you should have read previous tutorials to know how to create and manage API services with Ionic (like checking the network, displaying loading alert, passing data to pages) or how to manage API with Django and Rest framework.</p>

<p>
You can find source code on my <a href="https://github.com/csurbier/ionicanddjangotutorial/tree/master/PhoneValidation">Github</a></p>
<p></p></p>

<h2>How to enter an international phone number using ionic ?</h2>


<figure class="wp-block-image size-large is-resized"><img loading="lazy" src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/02/phone-1-473x1024.png" alt="" class="wp-image-1004" width="237" height="512" srcset="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/02/phone-1-473x1024.png 473w, https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/02/phone-1-600x1299.png 600w, https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/02/phone-1-139x300.png 139w, https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/02/phone-1-709x1536.png 709w, https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/02/phone-1-400x866.png 400w, https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/02/phone-1.png 750w" sizes="(max-width: 237px) 100vw, 237px" /></figure>


<figure class="wp-block-image size-large is-resized"><img loading="lazy" src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/02/phone-473x1024.png" alt="" class="wp-image-998" width="237" height="512" srcset="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/02/phone-473x1024.png 473w, https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/02/phone-600x1299.png 600w, https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/02/phone-139x300.png 139w, https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/02/phone-709x1536.png 709w, https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/02/phone-400x866.png 400w, https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/02/phone.png 750w" sizes="(max-width: 237px) 100vw, 237px" /></figure>


<p>First we will create a new Ionic/Capacitor project called <strong>PhoneValidation</strong> </p>


<pre class="EnlighterJSRAW" data-enlighter-language="typescript" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">ionic start PhoneValidation blank --capacitor --project-id=phonevalidation --package-id=com.idevotion.phonevalidation</pre>


<p>Next we will install and use an <a href="https://github.com/azzamasghar1/ion-intl-tel-input" target="_blank" rel="noreferrer noopener">external library </a>to deal with international phone number (no need to re-invent the wheel).</p>


<pre class="EnlighterJSRAW" data-enlighter-language="typescript" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">npm install --force ion-intl-tel-input ionic-selectable flag-icon-css google-libphonenumber --save</pre>


<p>To use this library we need to import it. So we will modify the <strong>home.module.ts </strong>file to import the library and also to use <strong>Reactive Forms</strong> with Angular.</p>


<pre class="EnlighterJSRAW" data-enlighter-language="typescript" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';
import { FormsModule,ReactiveFormsModule } from '@angular/forms';
import { HomePage } from './home.page';
import { HomePageRoutingModule } from './home-routing.module';
import { IonIntlTelInputModule } from 'ion-intl-tel-input';

 @NgModule({
  imports: [
    CommonModule,
    FormsModule,
    ReactiveFormsModule,
    IonicModule,
    IonIntlTelInputModule,
    HomePageRoutingModule
  ],
  declarations: [HomePage]
})
export class HomePageModule {}
</pre>


<p>Then we will modify the <strong>home.page.html</strong> to use the library within a reactive form</p>


<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;ion-header [translucent]="true">
  &lt;ion-toolbar>
    &lt;ion-title>
      Phone Validation
    &lt;/ion-title>
  &lt;/ion-toolbar>
&lt;/ion-header>

&lt;ion-content [fullscreen]="true">
  &lt;form [formGroup]="form" (ngSubmit)="onSubmit()">
    &lt;ion-item>
      &lt;ion-label position="floating">Tel Input&lt;/ion-label>
      &lt;ion-intl-tel-input inputPlaceholder="Your phone number"  
      [defaultCountryiso]="FR"
      [preferredCountries]="preferredCountries"
        formControlName="phoneNumber" >
      &lt;/ion-intl-tel-input>
    &lt;/ion-item>
    &lt;ion-button mode="ios" expand="block" color="primary" type="submit">
      Validate
    &lt;/ion-button>
  &lt;/form>
&lt;/ion-content></pre>


<p>and the <strong>home.page.ts</strong> file to declare our form and select some preferred countries.</p>


<pre class="EnlighterJSRAW" data-enlighter-language="typescript" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage {
  formValue = {phoneNumber: '', test: ''};
  form: FormGroup;
  preferredCountries = ["fr","gb","us"]
  constructor() { }

  ngOnInit() {
    this.form = new FormGroup({
      phoneNumber: new FormControl({
        value: this.formValue.phoneNumber
      })
    });
  }

  get phoneNumber() { return this.form.get('phoneNumber'); }

  onSubmit() {
    console.log(this.phoneNumber.value);
  }

}
</pre>


<p>To display country flag into the modal list of countries, we need to import a specific css file. To do so, we need to edit our <strong>global.scss</strong> file and add the line</p>


<pre class="EnlighterJSRAW" data-enlighter-language="typescript" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@import "node_modules/flag-icon-css/sass/flag-icon.scss";</pre>


<p>Ok so now we can enter a phone number and when clicking on the validate button, the <strong>onSubmit</strong> function will be called  and will display the phone number information</p>


<pre class="EnlighterJSRAW" data-enlighter-language="json" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">{internationalNumber: "+34 695 96 XX XX", nationalNumber: "695 96 XX XX", isoCode: "es", dialCode: "+34"}</pre>


<p>At this point we need to send the phone number information to our Django backend, which will send an SMS with a code that the user will need to enter on a new page to validate his phone number. </p>


<p>Let&#8217;s see first how we can send an sms using Django and a sms provider like <a href="https://www.smsapi.com/en" target="_blank" rel="noreferrer noopener">www.smsapi.com</a> or whatever sms provider you will choose.</p>




<h2>How to send an sms code using Django and SMSAPI ?</h2>


<p>As we learned in previous tutorials how to create and manage a backend using Django (and deploying it using Clever cloud), we will just focus on how to send the Sms code.</p>


<p>To use SMSAPI with Django, we need to edit our requirements.txt file and add the library</p>


<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">smsapi-client==2.4.2</pre>


<p>Then we will create a new endpoint in our API by adding in our urls.py file</p>


<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> url(r'^sendSmsCode/$',
        sendSmsCode,
        name='sendSmsCode'),</pre>


<p>And then we will write the view <strong>sendSmsCode</strong> which will generate a code, send it by sms and return a JSON response to our Ionic application with the code generated:</p>


<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">def generateSmsCode():
    # select a random sample without replacement
    codeSms = ''.join(random.choice(string.digits) for _ in range(4))
    return codeSms

@api_view(['GET'])
def sendSmsCode(request):
    from phonevalidation.settings import SMSAPI_API_KEY
    client = SmsApiComClient(access_token=SMSAPI_API_KEY)
    phone = request.GET.get('phone', None)
    code = generateSmsCode()
    message="SMS code:"+code
    try:
      client.sms.send(to=phone, message=message)
      smsSent = SMSSent()
      smsSent.phoneNumber = phone
      smsSent.codeEnvoye = code
      smsSent.save()
      json = {"status":"OK","code": code}
    except Exception as e:
        print(e)
        json = {"status": "KO"}

    return JsonResponse(json)</pre>


<p><em>Of course you need to create and set a valid SMSAPI_API_KEY</em> to use <a href="https://www.smsapi.com/en" target="_blank" rel="noreferrer noopener">smsapi.com</a> service.</p>




<h2>How to check and validate an SMS code using Ionic ?</h2>


<p>Now on our submit method, we can:</p>


<p>. check if  network is available </p>


<p>. call our new API and passed the phone number that the user entered. </p>


<p>. check our JSON response status. If it&#8217;s ok we can go to a new page/url : sms/code otherwise we should display an error to the end user.</p>


<pre class="EnlighterJSRAW" data-enlighter-language="typescript" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> onSubmit() {
    console.log(this.phoneNumber.value);
    if (this.apiService.networkConnected){
      let formatedNumber = this.phoneNumber.value.internationalNumber
      this.apiService.sendSmsCode(formatedNumber).subscribe((results)=>{
        if (results){
          let statuts = results["status"]
          if (statuts=="OK"){
            let code = results["code"]
            this.navData.setDataWithoutId({
                  "phoneNumber": formatedNumber,
                  "smscode": code
                })
                this.router.navigate(['/sms-code']).catch(err => console.error(err));
          }
          else{
            //Display error
          }

        }
        else{
          //Display error
        }
      })
    }    
  }</pre>


<p>Now we need to create the page that will ask the sms code received and will verify it with the code sent by our backend.</p>


<figure class="wp-block-image size-large is-resized"><img loading="lazy" src="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/02/validateSms-473x1024.png" alt="" class="wp-image-1008" width="237" height="512" srcset="https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/02/validateSms-473x1024.png 473w, https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/02/validateSms-600x1299.png 600w, https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/02/validateSms-139x300.png 139w, https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/02/validateSms-709x1536.png 709w, https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/02/validateSms-400x866.png 400w, https://www.ionicanddjangotutorial.com/wp-content/uploads/2021/02/validateSms.png 750w" sizes="(max-width: 237px) 100vw, 237px" /></figure>


<p>First let&#8217;s add our new page</p>


<pre class="EnlighterJSRAW" data-enlighter-language="typescript" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">ionic g page SmsCode  </pre>


<p>Now let&#8217;s edit our sms-code.page.html </p>


<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;ion-header class="ion-no-border" mode="ios">
  &lt;ion-toolbar mode="ios">
 &lt;ion-buttons slot="start">
   &lt;ion-back-button defaultHref="home">&lt;/ion-back-button>
 &lt;/ion-buttons>
 &lt;ion-title>Code&lt;/ion-title>
  &lt;/ion-toolbar>
&lt;/ion-header>

&lt;ion-content>
  &lt;div class="page-flex-align code">
    &lt;div class="top-content ion-text-center">
      &lt;h1>Validation code&lt;/h1>
      &lt;p>Please enter the code send to 
        &lt;br>Phone: {{phoneNumber}}&lt;/p>
      &lt;ion-list mode="ios" formCodeValidate lines="none">
        &lt;ion-item >
          &lt;ion-input #codesInpunt0 (keyup)="changeFocus(1)"  [(ngModel)]="code[0]" type="tel" maxlength="1"  >&lt;/ion-input>
        &lt;/ion-item>
        &lt;ion-item >
          &lt;ion-input #codesInpunt1 (keyup)="changeFocus(2)" [(ngModel)]="code[1]" type="tel" maxlength="1"  >&lt;/ion-input>
        &lt;/ion-item>
        &lt;ion-item >
          &lt;ion-input #codesInpunt2 (keyup)="changeFocus(3)" [(ngModel)]="code[2]" type="tel" maxlength="1"  >&lt;/ion-input>
        &lt;/ion-item>
        &lt;ion-item >
          &lt;ion-input #codesInpunt3  (keyup)="changeFocus(4)"   [(ngModel)]="code[3]" type="tel" maxlength="1"  >&lt;/ion-input>
        &lt;/ion-item>
      &lt;/ion-list>

    &lt;/div>
  &lt;/div>
&lt;/ion-content>

</pre>


<p>and the sms.code.page.scss tohave our digit input design</p>


<pre class="EnlighterJSRAW" data-enlighter-language="scss" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">
ion-list {
    &amp;[form] {
        ion-item {
            &amp;.item {
                --border-width: 1px;
                --border-radius: 5px;
                --min-height: 60px;
                --ion-border-color: rgba(0, 0, 0, 0.10);

                ion-select {
                    --padding-start: 0;
                    width: 100%;
                    //font-size: 1.8rem;
                    max-width: unset;
                }

                &amp;+.item {
                    margin-top: 16px;
                }
            }

        }

        ion-row {
            &amp;[lrm] {
                margin-left: -8px;
                margin-right: -8px;
            }

            ion-col {
                padding: 8px;
                padding-top: 16px;
            }
        }
    }

 &amp;[formCodeValidate] {
    display: -webkit-box;
    display: -ms-flexbox;
    display: flex;
    -webkit-box-align: center;
    -ms-flex-align: center;
    align-items: center;
    -webkit-box-pack: justify;
    -ms-flex-pack: justify;
    justify-content: space-between;
    max-width: 310px;
    margin-left: auto;
    margin-right: auto;

    ion-item {
        &amp;.item {
            --border-width: 1px;
            --border-radius: 5px;
            --min-height: 60px;
            max-width: 55px;
            text-align: center;
            font-size: 18px;
        }

    }
    }
}</pre>


<p>and the sms-code.page.ts to verify if the code entered by the user is matching the code sent by our backend</p>


<pre class="EnlighterJSRAW" data-enlighter-language="typescript" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import { ChangeDetectorRef, Component, NgZone, OnInit, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { Platform, ToastController } from '@ionic/angular';
import { NavDataServiceService } from '../services/nav-data-service.service';

@Component({
  selector: 'app-sms-code',
  templateUrl: './sms-code.page.html',
  styleUrls: ['./sms-code.page.scss'],
})
export class SmsCodePage implements OnInit {

  phoneNumber= ''
  verificationId="1245";
  code = Array();
  started=false;
  verificationInProgress=false;
  @ViewChild('codesInpunt0') codesInpunt0;
  @ViewChild('codesInpunt1') codesInpunt1;
  @ViewChild('codesInpunt2') codesInpunt2;
  @ViewChild('codesInpunt3') codesInpunt3;

  constructor(
    public router: Router,
    public navData:NavDataServiceService,
    public platform : Platform,
    public toastCtrl: ToastController) {
      this.started=false;
  }

  ngOnInit() {

      let data = this.navData.getDataWithoutId()
      this.phoneNumber = data['phoneNumber'];
      this.verificationId = data['smscode'];
      console.log("route special "+this.phoneNumber+" verif code "+this.verificationId)

  }

  ionViewDidEnter() {
    this.codesInpunt0.setFocus();
  } 

  changeFocus(inputToFocus) {
    switch (inputToFocus) {
      case 1:
        this.codesInpunt1.setFocus();
        break;

      case 2:
        this.codesInpunt2.setFocus();
        break;

      case 3:
        this.codesInpunt3.setFocus();
        break;
       case 4:
        let enteredCode = this.code[0]+this.code[1] + this.code[2] + this.code[3]   ;
        this.resetCode()
        if (this.verificationInProgress==false){
          this.verificationInProgress=true;
          this.activate(enteredCode)
        }
       break;

    }

  }

  activate(enteredCode) {
    if (enteredCode){
      console.log("Compare code sms "+enteredCode+" avec "+this.verificationId)
      if (enteredCode.length == 4) {
        console.log(enteredCode);
        console.log("veificationCode is" + this.verificationId);
        if (enteredCode==this.verificationId){
          //Good job
        }
        else{

          this.presentToastError()
        }
      }
      else{
        this.presentToastError()
      }
    }
    else{
      this.presentToastAgain()
    }
  }

  resetCode(){
      this.code[0]="";
      this.code[1]="";
      this.code[2]="";
      this.code[3]="";

  }

  async presentToastAgain(){
    let toast = await this.toastCtrl.create({
      message: "Please enter code again",
      duration: 2000,
      position: 'bottom'
    });

    toast.present();
    this.verificationInProgress=false;
    this.resetCode()
    this.codesInpunt0.setFocus();
  }

  async presentToastError() {
    this.verificationInProgress=false;
    this.codesInpunt0.setFocus();
    let toast = await this.toastCtrl.create({
      message: "Code invalid",
      duration: 2000,
      position: 'bottom'
    });
   toast.present();
  }

}
</pre>


<p>The post <a rel="nofollow" href="https://www.ionicanddjangotutorial.com/how-to-validate-a-phone-number-by-sending-an-sms-code-using-ionicdjango-and-smsapi/">How to validate a phone number by sending an SMS code using Ionic,Django and SmsApi ?</a> appeared first on <a rel="nofollow" href="https://www.ionicanddjangotutorial.com">Ionic and Django Tutorial</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
