Previous page: Display a list of messages with Angular
Angular Components with GraphQL Queries
Use a GraphQL query to populate a list of messages in an Angular component
We will set up Apollo Angular so that we can talk to the Apollo GraphQL API to retrieve a message list with a GraphQL query.
Create an Angular Service to retrieve messages
Open a command-line prompt or terminal if you haven't already. We will need to create a few files to hold our Angular service and GraphQL query. Run the following commands.
cd <project-folder>/web-app/src/app/messages/
mkdir -p ./shared
cd ./shared
touch ./conversation-message-fragment.gql.ts
touch ./conversation-user-fragment.gql.ts
ng generate service retrieve-messages
The following folder and files should be added:
|--app
| |--messages
| | |--shared
| | | |--conversation-message-fragment.gql.ts
| | | |--conversation-user-fragment.gql.ts
| | | |--retrieve-messages.service.ts
We will create a service to describe a query that we'll use to call our GraphQL endpoint, but first we will focus on creating query Fragments in the shape of the GraphQL data that we require in the response.
GraphQL Fragments
Fragments are an excellent way of grouping common properties used for different user interface views of the same data models.
We will create a fragment for viewing a message and also a user within our
view-conversation.component.ts
component. We will start with the user, open
the file conversation-user-fragment.gql.ts
and add the following code:

import gql from 'graphql-tag';
export default gql`fragment conversationUser on User {
id
name
colour
avatarUrl
}`;
You may have noticed that the properties resemble the User
object that we
created for our GraphQL API. The syntax fragment conversationUser on User
defines a fragment called conversationUser
that will define properties from
the User
model of our GraphQL API.
If you have a compatible IDE (such as JetBrains Webstorm), there may be a plugin available similar to JS GraphQL for Webstorm. The plugin gives you GraphQL Schema introspection, which allows external services to map the data models defined in a schema and can add an extra level of error-catching and debugging when describing properties and models used from the API.
Open the file conversation-message-fragment.gql.ts
and add the following code:

import gql from 'graphql-tag';
import conversationUserFragment from './conversation-user-fragment.gql';
export default gql`fragment conversationMessage on Message {
id
userId
text
fromYou
timestamp
user {
...conversationUser
}
}
${conversationUserFragment}
`;
Here we've created a fragment to represent the data we need on a Message
model for our view-conversation.component.ts
file. We've also mixed-in the
conversationUserFragment
fragment into the user
properties to add to our Message
fragment and maintain a clean level of separation. In order to use the
conversationUserFragment
, we have to defined it within our gql
template literal
using the template literal expression ${conversationUserFragment}
.
Now we have all the fragments of our GraphQL request, lets create a service and use them to build a query.
GraphQL Client Queries
Our new service will implement GraphQL's Query
interface, which will let us
focus on the format of the query and not worry too much about the
implementation. Open the file shared/retrieve-messages.service.ts
and
replace the contents with the following code:

import { Injectable } from '@angular/core';
import gql from 'graphql-tag';
import { Query } from 'apollo-angular';
import conversationMessageFragment from './conversation-message-fragment.gql';
@Injectable({
providedIn: 'root'
})
export class RetrieveMessagesService extends Query<Response> {
document = gql`
query Messages($max: Int) {
messages(max: $max) {
...conversationMessage
}
}
${conversationMessageFragment}
`;
}
In our RetrieveMessagesService
service all we need to create a query is
implement apollo-angular
's Query and simply define a document property to
hold our request string. Within the gql
template literal, we've written our
top-level query for retrieving a list of messages and again used fragments to
define the messages
shape. We can restrict the amount of messages returned
from this query by using the $max
parameter that will be later defined in our
component that uses this service.
The @Injectable
syntax lets Angular know that this is a service to be
injected into components and other services.
Injectors can be
defined as application-wide singleton instances by specifying the
providedIn: root
property and value.
Subscribe to the Angular Service to display messages from the GraphQL endpoint
Open the file messages/view-conversation/view-conversation.component.ts
. Lets remove the
hard-coded data that we left in this file. Change the ViewConversation
components messages
property from:

export class ViewConversationComponent implements OnInit {
messages: Array<MessageViewModel> = [
// Remove the code below
{
id: '1',
sent: new Date(),
text: 'This message is bound to component data',
user: {
name: 'Shane Edwards',
avatarUrl: 'https://lh3.googleusercontent.com/a-/AAuE7mAuiepkKGsj-1L9lhHSHtnQsXlVqHEQfJqzOoGz=s96-cc-rg',
colour: '0080a2'
},
fromYou: false
},
{
id: '2',
sent: new Date(),
text: 'Cool',
user: {
name: 'You',
avatarUrl: 'https://angular.io/assets/images/logos/angular/shield-large.svg',
colour: 'c16fe0'
},
fromYou: true
}
// Stop here
];
// Existing code...
}
To the empty array property:

export class ViewConversationComponent implements OnInit {
messages: Array<MessageViewModel> = []; // <-- Should be left with this
constructor() {}
ngOnInit() {}
}
This should clear all messages from our message user interface. We will need
to add two imports at the top of the file as well as a MessageModel
interface
that will tell Typescript what the shape of our returned data will look like,
which we will put before our MessageViewModel
interface.

import { Component, OnInit } from '@angular/core';
import { RetrieveMessagesService } from '../shared/retrieve-messages.service'; // <-- Add new import
import { ApolloQueryResult } from "apollo-client"; // <-- Add new import
// New MessageModel
interface MessageModel {
id: string;
userId: string;
text: string;
timestamp: string;
fromYou: boolean;
user: any;
}
interface MessageViewModel {
// Existing code...
}
// Existing code...
Next we need to add the RetrieveMessagesService
type to our component
constructor to get an instance of our new service that will retrieve new
messages:

// Existing code...
export class ViewConversationComponent implements OnInit {
messages: Array<MessageViewModel> = [];
constructor(
private retrieveMessagesService: RetrieveMessagesService,
) {}
ngOnInit() {}
}
Add a new static method that will be responsible for creating MessageViewModel
instances from MessageModel
data to create an object that our Angular
template can bind to. Define the new static method after the constructor
.

// Existing code...
export class ViewConversationComponent implements OnInit {
// Existing code...
// Static methods should be defined before instance methods
static createMessageInstance(messageData: MessageModel): MessageViewModel {
return <MessageViewModel>{
...messageData,
sent: new Date(parseInt(messageData.timestamp) * 1000),
user: {
...messageData.user,
avatarUrl: messageData.user.avatarUrl || 'assets/avatar.png'
}
};
}
ngOnInit() {}
}
This method essentially transforms an object from a MessageModel
type to a
MessageViewModel
type. The
spread operator
(...
) will mixin properties from the MessageModel
to add to the new one.
Message instances created by the method above use the same static image if
the user doesn't have an image associated with their account and also anonymous
users. Here, you can save and add the
image below to your
web-app/src/assets
folder or add your own if you wish.
We need another method for updating our messages after they've each been mapped
from a MessageModel
to a MessageViewModel
.

// Existing code...
export class ViewConversationComponent implements OnInit {
// Existing code...
ngOnInit() {}
updateMessages(result: ApolloQueryResult<{messages: Array<MessageModel>}>) {
if (!result || !result.data || !result.data.messages) {
return false;
}
this.messages = result.data.messages.map(ViewConversationComponent.createMessageInstance).reverse();
}
}
We've used a guard (an if
statement) to protect the function from
attempting to assign the messages property without the required data in the
result
.
If the message data is valid, the component property messages
is
assigned with a mapped version of the result.data.messages
array using our
new static method (ViewConversationComponent.createMessageInstance
).
Lastly we need to wire everything up in our
ngOnInit Angular lifecycle method and
subscribe to our RetrieveMessagesService
service.

// Existing code...
export class ViewConversationComponent implements OnInit {
// Existing code...
ngOnInit() {
this.retrieveMessagesService
.fetch({ max: 4 })
.subscribe((result: ApolloQueryResult<any>) => this.updateMessages(result));
}
updateMessages(result: ApolloQueryResult<{messages: Array<MessageModel>}>) {
// Existing code...
}
}
Here we call fetch
on our new service, with an object that represents the
variable of our query { max: 4 }
. this will instruct our API to limit the
amount of results returned to the latest 4 results.