IT consultant @
Software Engineer | Trainer
Node.js Foundation Member
Co-Founder @
Rabat.js
👨🎨 Front-End developers ?
👨💻 Back-End developers ?
🦸♂️ Something else ? something in between ?
☁️ Cloud developers ?
Text
Text
USER EXPERIENCE | DEVELOPER EXPERIENCE |
---|---|
Installable | Hard to multiplatform |
Available Offline | Ineffective workstyle |
Engaging | Store approval |
Download required | Expensive to develop |
Updates required | |
No Google | |
No Multiplatform |
USER EXPERIENCE | DEVELOPER EXPERIENCE |
---|---|
No install / updates | Web technologies |
Works on all devices | Code Once Run Everywhere |
Known by search engines | Huge ecosystem |
No offline Support | 'Cheap' to develop |
Not installable | Secured over TLS/SSL layer |
Not engaging | |
USER EXPERIENCE | DEVELOPER EXPERIENCE |
---|---|
Offline Support | Web technologies |
Engaging | Code Once Run Everywhere |
Installable | Huge ecosystem |
No install / updates | 'Cheap' to develop |
Works on all devices | Secured over TLS/SSL layer |
Known by search engines | |
With a home screen icon
With Offline Support
Receives notifications
Secured over HTTPS
{
"short_name": "Twitter",
"name": "Twitter App",
"icons": [
{
"src": "/images/icons-192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "/images/icons-512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": "/?source=pwa",
"background_color": "#3367D6",
"display": "standalone",
"scope": "/",
"theme_color": "#3367D6"
}
<link rel="manifest" href="/manifest.json">
manifest.json
index.html
Install/update application
Launch application while offline
No access to sync storage
Promise-based
Requires HTTPS
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw-test/sw.js')
.then((reg) => {
// registration worked
console.log('Registration succeeded. Scope is ' + reg.scope);
}).catch((error) => {
// registration failed
console.log('Registration failed with ' + error);
});
}
main.js
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('v1').then((cache) => {
return cache.addAll([
'/sw-test/index.html',
'/sw-test/style.css',
'/sw-test/app.js',
'/sw-test/logo.jpg',
]);
})
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});
self.addEventListener('push', function(event) {
const title = 'PWA man!';
const options = {
body: 'Hey Devoxxians!',
icon: 'images/devoxx.png'
};
event.waitUntil(self.registration.showNotification(title, options));
});
ng add @angular/pwa
Installed packages for tooling via yarn.
CREATE ngsw-config.json (585 bytes)
CREATE src/manifest.webmanifest (1063 bytes)
CREATE src/assets/icons/icon-128x128.png (1253 bytes)
CREATE src/assets/icons/icon-144x144.png (1394 bytes)
CREATE src/assets/icons/icon-152x152.png (1427 bytes)
CREATE src/assets/icons/icon-192x192.png (1790 bytes)
CREATE src/assets/icons/icon-384x384.png (3557 bytes)
CREATE src/assets/icons/icon-512x512.png (5008 bytes)
CREATE src/assets/icons/icon-72x72.png (792 bytes)
CREATE src/assets/icons/icon-96x96.png (958 bytes)
UPDATE angular.json (3574 bytes)
UPDATE package.json (1350 bytes)
UPDATE src/app/app.module.ts (604 bytes)
SPA + ng add @angular/pwa = PWA
{
"name": "my-pwa",
"short_name": "pwa",
"theme_color": "#1976d2",
"background_color": "#fafafa",
"display": "standalone",
"scope": "/",
"start_url": "/",
"icons": [
{
"src": "assets/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "assets/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png"
}
...
]
}
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('v1').then((cache) => {
return cache.addAll([
'/sw-test/index.html',
'/sw-test/style.css',
'/sw-test/app.js',
'/sw-test/logo.jpg',
]);
})
);
});
ng add @angular/pwa
Installed packages for tooling via yarn.
CREATE ngsw-config.json (585 bytes)
CREATE src/manifest.webmanifest (1063 bytes)
CREATE src/assets/icons/icon-128x128.png (1253 bytes)
CREATE src/assets/icons/icon-144x144.png (1394 bytes)
CREATE src/assets/icons/icon-152x152.png (1427 bytes)
CREATE src/assets/icons/icon-192x192.png (1790 bytes)
CREATE src/assets/icons/icon-384x384.png (3557 bytes)
CREATE src/assets/icons/icon-512x512.png (5008 bytes)
CREATE src/assets/icons/icon-72x72.png (792 bytes)
CREATE src/assets/icons/icon-96x96.png (958 bytes)
UPDATE angular.json (3574 bytes)
UPDATE package.json (1350 bytes)
UPDATE src/app/app.module.ts (604 bytes)
{
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html",
"/*.css",
"/*.js"
]
}
}, {
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**",
"/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
]
}
}
]
}
ngsw-config.json
"installMode": "prefetch",
"files": [
"/favicon.ico",
"/index.html",
"/*.css",
"/*.js"
]
ng build --prod
{
"configVersion": 1,
"timestamp": 1572297590111,
"index": "/index.html",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"updateMode": "prefetch",
"urls": [
"/index.html",
"/main-es5.55c7be03cd19bb680a35.js",
"/polyfills-es5.98f7268495936fc0a233.js",
"/runtime-es5.d3647fbfa3de00cd0bdf.js",
"/styles.3ff695c00d717f2d2a11.css"
...
]
}
]
...
}
ngsw-config.json
ngsw-worker.js + ngsw.json
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
ServiceWorkerModule.register(
'ngsw-worker.js',
{ enabled: environment.production }
)
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
app.module.ts
@Injectable()
export class updateService {
constructor(updates: SwUpdate, toastr: ToastrService) {
updates.available.subscribe(event => {
toastr.refresh('A new version is available');
});
}
}
constructor(private swPush: SwPush) {}
subscribeToNotification() {
this.swPush.requestSubscription({
serverPublicKey: this.VAPID_PUBLIC_KEY
})
.then(sub => this.sendSubcriptionToBackend(sub))
}
handleNotification() {
this.swPush.notificationClicks.subscribe( event => {
const dataFromBackend = event.notification.data;
doSemthing(dataFromBackend);
});
}
progressivewebapproom.com
{
pikatchu: pokemon(id: "1") {
name
type
}
}
{
"data": {
"pokemon": {
"name": "Pikachu",
"type": "Electric"
}
}
}
function pokemon_name(pokemon) {
return pokemon.getName();
}
function pokemon_type(pokemon) {
return pokemon.getType();
}
function pokemon_healthname(pokemon) {
return pokemon.getHealth();
}
{
pikatchu: pokemon(id: "1") {
name
type
}
}
{
"data": {
"pokemon": {
"name": "Pikachu",
"type": "Electric"
}
}
}
query BestPokemon {
pikatchu: pokemon(id: "1") {
name
type
}
}
mutation CreatePokemonMutation() {
createPokemon(name: "Pikachu") {
id
type
}
}
{
"data": {
"pokemon": {
"id" : 1
"name": "Pikachu",
"type": "Electric"
}
}
}
subscription onPokemonCreateSubscription(){
onPokemonCreate(){
id
content
}
}
subscription onPokemonCreateSubscription(){
onPokemonCreate(){
id
content
}
}
subscription onPokemonCreateSubscription(){
onPokemonCreate(){
id
content
}
}
type Query {
allPokemons(last: Int): [Pokemon!]!
}
type Query { ... }
type Mutation { ... }
type Subscription { ... }
type Mutation {
createPokemons(name: String!, type: String!): Pokemon!
}
type Subscription {
newPokemon: Pokemon!
}
Apollo Client is a Feature-rich GraphQL client that manages data and state in an application.
Apollo Cache is a Reactive store for your GraphQL requests
A sort of middleware for your GraphQL Requests
ng add apollo-graphql
CREATE src/app/graphql.module.ts (628 bytes)
UPDATE package.json (1728 bytes)
UPDATE tsconfig.json (572 bytes)
UPDATE src/app/app.module.ts (1702 bytes)
const uri = 'https://myapp.io/graphql';
export function createApollo(httpLink: HttpLink) {
return {
link: httpLink.create({uri}),
cache: new InMemoryCache(),
};
}
@NgModule({
exports: [ApolloModule, HttpLinkModule],
providers: [
{
provide: APOLLO_OPTIONS,
useFactory: createApollo,
deps: [HttpLink],
},
],
})
export class GraphQLModule {}
const GET_USERS = gql`users { email firstName lastName }`;
constructor(private apollo: Apollo) { }
getUsers() {
this.apollo
.watchQuery({
query: GET_USERS,
})
.valueChanges
.subscribe(result => {
this.data = result.data && result.data.users;
this.loading = result.loading;
this.error = result.error;
});
}
const CREATE_USER = gql`mutation createUser(user: CreateUserInput!) {
createUser(user: $user) {id email firstName lastName}
}`;
constructor(private apollo: Apollo) { }
createUser({ email, firstName, lastName }) {
this.apollo.mutate({
mutation: CREATE_USER,
variables: {
user: { email, firstName, lastName }
}
})
.subscribe(
user => this.user = user
);
}
const CREATE_USER = gql`subscription {onUpdateTopic { id name active }}`;
constructor(private apollo: Apollo) { }
subscribeToUpdate({ email, firstName, lastName }) {
return this.apollo
.subscribe({
query: gql`${subscription}`,
})
.subscribe(
topic => this.topic = topic
);
}
AppSync Client is an Apollo Client
export const environment = {
production: true,
aws: {
aws_project_region: 'eu-west-1',
aws_appsync_graphqlEndpoint: 'https://xxx.region.amazonaws.com/graphql',
aws_appsync_apiKey: 'da2-xxx',
}
};
npm install aws-appsync
import { AUTH_TYPE, AWSAppSyncClient } from 'aws-appsync';
import { environment } from 'src/environments/environment';
@NgModule({
exports: [ApolloModule, HttpLinkModule],
})
export class GraphQLModule {
constructor(apollo: Apollo) {
const client: any = new AWSAppSyncClient({
url: environment.aws.aws_appsync_graphqlEndpoint,
region: environment.aws.aws_project_region,
auth: {
type: AUTH_TYPE.API_KEY,
apiKey: environment.aws.aws_appsync_apiKey
}
});
apollo.setClient(client);
}
}
$ npm install -g @aws-amplify/cli
$ amplify init
$ amplify add api
? Please select from one of the below mentioned services: GraphQL
..
? Do you have an annotated GraphQL schema? Yes
? Provide your schema file path: src/schema.graphql
GraphQL schema compiled successfully.
$ amplify push
√ Downloaded the schema
√ Generated GraphQL operations successfully and saved at src\graphql
√ Code generated successfully and saved in file src\app\API.service.ts
GraphQL endpoint: https://xxxx.appsync-api.us-east-1.amazonaws.com/graphql
GraphQL API KEY: da2-xxxxx
const awsmobile = {
"aws_project_region": "us-east-1",
"aws_appsync_graphqlEndpoint": "https://xxxx.appsync-api.us-east-1.amazonaws.com/graphql",
"aws_appsync_region": "us-east-1",
"aws_appsync_authenticationType": "API_KEY",
"aws_appsync_apiKey": "da2-xxxxxx",
"aws_content_delivery_bucket": "ngx-pwa-201911081804-hostingbucket-dev",
"aws_content_delivery_bucket_region": "us-east-1",
"aws_content_delivery_url": "https://xxxxxx.cloudfront.net"
};
export default awsmobile;