Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Realtime Cross Platform Apps with Angular, ASP.NET Core and SignalR

Realtime Cross Platform Apps with Angular, ASP.NET Core and SignalR

Angular provides web developers a platform where applications can be written not only for the web. Mobile devices such as smartphones or tablets, as well as the desktop also consume line of business (LOB) apps and are indispensable. Once the angular code has been written, it doesn't take much to port the application to the smartphone or desktop. In this talk, Fabian Gosebrink shows how an existing angular application can be ported to a mobile device with a few steps using Capacitor and how native features can also be used. With Electron, the desktop is also used as a platform, so that at the end a code base can serve all platforms and the application can be used via any end device.

Fabian Gosebrink

February 22, 2023
Tweet

More Decks by Fabian Gosebrink

Other Decks in Technology

Transcript

  1. Realtim Cr
    Platfor App
    Angular, ASP.NET
    Cor & Signa R
    wit

    View Slide

  2. Fabian
    Gosebrink

    View Slide

  3. Realtim Cr
    Platfor App
    Angular, ASP.NET
    Cor & Signa R
    wit

    View Slide

  4. Server
    Clien

    View Slide

  5. Server
    Clien

    View Slide

  6. Server
    Clien
    HTTPS WS

    View Slide

  7. View Slide

  8. Realtim
    Signa R
    wit

    View Slide

  9. Pollin

    View Slide

  10. Pollin

    View Slide

  11. Signa R

    View Slide

  12. Signa R
    WebSocket

    View Slide

  13. Signa R
    Server Sen Event

    View Slide

  14. Signa R
    Lon Pollin

    View Slide

  15. Signa R
    WebSocket
    Server Sen Event
    Lon Pollin

    View Slide

  16. var builder = WebApplication.CreateBuilder(args);
    // ...
    builder.Services.AddSignalR();
    // ...
    var app = builder.Build();
    app.MapHub("/doggoHub");
    // ...
    app.Run();
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    View Slide

  17. Hub

    View Slide

  18. Hub
    using Microsoft.AspNetCore.SignalR;
    namespace DoggoApi.Hubs
    {
    public class DoggoHub : Hub
    {
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9

    View Slide

  19. Hub
    using Microsoft.AspNetCore.SignalR;
    namespace DoggoApi.Hubs
    {
    public class DoggoHub : Hub
    {
    public async Task MySuperDuperAction(object data)
    {
    await Clients.All.SendAsync("myclientmethod", data);
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    View Slide

  20. private readonly IHubContext _hubContext;
    IHubContext hubContext
    _hubContext = hubContext;
    public class DoggosController : ControllerBase
    1
    {
    2
    3
    4
    public DoggosController(
    5
    6
    )
    7
    {
    8
    9
    }
    10
    11
    [HttpPost(Name = nameof(AddDoggo))]
    12
    public async Task AddDoggo(
    13
    [FromBody] DoggoCreateDto createDto)
    14
    {
    15
    // ... do http things
    16
    17
    await _hubContext.Clients.All.SendAsync("DoggoAdded", dto);
    18
    19
    return CreatedAtRoute(/* ... */);
    20
    }
    21
    }
    22

    View Slide

  21. private readonly IHubContext _hubContext;
    IHubContext hubContext
    _hubContext = hubContext;
    public class DoggosController : ControllerBase
    1
    {
    2
    3
    4
    public DoggosController(
    5
    6
    )
    7
    {
    8
    9
    }
    10
    11
    [HttpPost(Name = nameof(AddDoggo))]
    12
    public async Task AddDoggo(
    13
    [FromBody] DoggoCreateDto createDto)
    14
    {
    15
    // ... do http things
    16
    17
    await _hubContext.Clients.All.SendAsync("DoggoAdded", dto);
    18
    19
    return CreatedAtRoute(/* ... */);
    20
    }
    21
    }
    22
    public async Task AddDoggo(
    [FromBody] DoggoCreateDto createDto)
    {
    // ... do http things
    await _hubContext.Clients.All.SendAsync("DoggoAdded", dto);
    return CreatedAtRoute(/* ... */);
    }
    public class DoggosController : ControllerBase
    1
    {
    2
    private readonly IHubContext _hubContext;
    3
    4
    public DoggosController(
    5
    IHubContext hubContext
    6
    )
    7
    {
    8
    _hubContext = hubContext;
    9
    }
    10
    11
    [HttpPost(Name = nameof(AddDoggo))]
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    }
    22

    View Slide

  22. private readonly IHubContext _hubContext;
    IHubContext hubContext
    _hubContext = hubContext;
    public class DoggosController : ControllerBase
    1
    {
    2
    3
    4
    public DoggosController(
    5
    6
    )
    7
    {
    8
    9
    }
    10
    11
    [HttpPost(Name = nameof(AddDoggo))]
    12
    public async Task AddDoggo(
    13
    [FromBody] DoggoCreateDto createDto)
    14
    {
    15
    // ... do http things
    16
    17
    await _hubContext.Clients.All.SendAsync("DoggoAdded", dto);
    18
    19
    return CreatedAtRoute(/* ... */);
    20
    }
    21
    }
    22
    public async Task AddDoggo(
    [FromBody] DoggoCreateDto createDto)
    {
    // ... do http things
    await _hubContext.Clients.All.SendAsync("DoggoAdded", dto);
    return CreatedAtRoute(/* ... */);
    }
    public class DoggosController : ControllerBase
    1
    {
    2
    private readonly IHubContext _hubContext;
    3
    4
    public DoggosController(
    5
    IHubContext hubContext
    6
    )
    7
    {
    8
    _hubContext = hubContext;
    9
    }
    10
    11
    [HttpPost(Name = nameof(AddDoggo))]
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    }
    22
    await _hubContext.Clients.All.SendAsync("DoggoAdded", dto);
    public class DoggosController : ControllerBase
    1
    {
    2
    private readonly IHubContext _hubContext;
    3
    4
    public DoggosController(
    5
    IHubContext hubContext
    6
    )
    7
    {
    8
    _hubContext = hubContext;
    9
    }
    10
    11
    [HttpPost(Name = nameof(AddDoggo))]
    12
    public async Task AddDoggo(
    13
    [FromBody] DoggoCreateDto createDto)
    14
    {
    15
    // ... do http things
    16
    17
    18
    19
    return CreatedAtRoute(/* ... */);
    20
    }
    21
    }
    22

    View Slide

  23. View Slide

  24. View Slide

  25. View Slide

  26. @microsoft/signalr

    View Slide

  27. let connection = new signalR.HubConnectionBuilder()
    .withUrl(".../doggoHub")
    .build();
    1
    2
    3
    4
    connection.on("doggoadded", data => {
    5
    console.log(data);
    6
    });
    7
    8
    connection.start()
    9
    .catch((err) => console.log(err.toString()));
    10

    View Slide

  28. let connection = new signalR.HubConnectionBuilder()
    .withUrl(".../doggoHub")
    .build();
    1
    2
    3
    4
    connection.on("doggoadded", data => {
    5
    console.log(data);
    6
    });
    7
    8
    connection.start()
    9
    .catch((err) => console.log(err.toString()));
    10
    connection.on("doggoadded", data => {
    console.log(data);
    });
    let connection = new signalR.HubConnectionBuilder()
    1
    .withUrl(".../doggoHub")
    2
    .build();
    3
    4
    5
    6
    7
    8
    connection.start()
    9
    .catch((err) => console.log(err.toString()));
    10

    View Slide

  29. let connection = new signalR.HubConnectionBuilder()
    .withUrl(".../doggoHub")
    .build();
    1
    2
    3
    4
    connection.on("doggoadded", data => {
    5
    console.log(data);
    6
    });
    7
    8
    connection.start()
    9
    .catch((err) => console.log(err.toString()));
    10
    connection.on("doggoadded", data => {
    console.log(data);
    });
    let connection = new signalR.HubConnectionBuilder()
    1
    .withUrl(".../doggoHub")
    2
    .build();
    3
    4
    5
    6
    7
    8
    connection.start()
    9
    .catch((err) => console.log(err.toString()));
    10
    connection.start()
    .catch((err) => console.log(err.toString()));
    let connection = new signalR.HubConnectionBuilder()
    1
    .withUrl(".../doggoHub")
    2
    .build();
    3
    4
    connection.on("doggoadded", data => {
    5
    console.log(data);
    6
    });
    7
    8
    9
    10

    View Slide

  30. Dem

    View Slide

  31. View Slide

  32. View Slide

  33. Cr Platfor

    View Slide

  34. Ma

    View Slide

  35. Ma
    Window

    View Slide

  36. Ma
    Window
    Lin

    View Slide

  37. Mobil

    View Slide

  38. Mobil
    Desktop

    View Slide

  39. Mobil
    Desktop
    Web

    View Slide

  40. s
    muc
    mor ...

    View Slide

  41. View Slide

  42. View Slide

  43. View Slide

  44. View Slide

  45. View Slide

  46. View Slide

  47. View Slide

  48. View Slide

  49. View Slide

  50. View Slide

  51. View Slide

  52. View Slide

  53. View Slide

  54. import { CapacitorConfig } from '@capacitor/cli';
    const config: CapacitorConfig = {
    appId: 'com.example.app',
    appName: 'ratemydoggo',
    webDir: 'dist/ratemydoggo',
    bundledWebRuntime: false,
    };
    export default config;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    View Slide

  55. Dem

    View Slide

  56. View Slide

  57. Electron is a tool for building Cross-
    Platform Desktop Apps with
    Javascript, Html and CSS
    "
    "

    View Slide

  58. View Slide

  59. Chromiu

    View Slide

  60. View Slide

  61. View Slide

  62. an man mor ...

    View Slide

  63. $ npm install electron -g

    View Slide

  64. ├── index.html
    ├── index.js
    └── package.json

    View Slide

  65. {
    "name": "your-app",
    "version": "0.1.0",
    "main": "index.js",
    "scripts": {
    "start": "electron ."
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8

    View Slide

  66. {
    "name": "your-app",
    "version": "0.1.0",
    "main": "index.js",
    "scripts": {
    "start": "electron ."
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    "main": "index.js",
    {
    1
    "name": "your-app",
    2
    "version": "0.1.0",
    3
    4
    "scripts": {
    5
    "start": "electron ."
    6
    }
    7
    }
    8

    View Slide

  67. const { app, BrowserWindow } = require('electron');
    createWindow = () => {
    // Create the browser window.
    const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
    nodeIntegration: true,
    },
    });
    // and load the index.html of the app.
    win.loadFile('index.html');
    };
    app.whenReady().then(createWindow);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    View Slide

  68. const { app, BrowserWindow } = require('electron');
    createWindow = () => {
    // Create the browser window.
    const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
    nodeIntegration: true,
    },
    });
    // and load the index.html of the app.
    win.loadFile('index.html');
    };
    app.whenReady().then(createWindow);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    const { app, BrowserWindow } = require('electron');
    1
    2
    createWindow = () => {
    3
    // Create the browser window.
    4
    const win = new BrowserWindow({
    5
    width: 800,
    6
    height: 600,
    7
    webPreferences: {
    8
    nodeIntegration: true,
    9
    },
    10
    });
    11
    12
    // and load the index.html of the app.
    13
    win.loadFile('index.html');
    14
    };
    15
    16
    app.whenReady().then(createWindow);
    17

    View Slide

  69. const { app, BrowserWindow } = require('electron');
    createWindow = () => {
    // Create the browser window.
    const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
    nodeIntegration: true,
    },
    });
    // and load the index.html of the app.
    win.loadFile('index.html');
    };
    app.whenReady().then(createWindow);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    const { app, BrowserWindow } = require('electron');
    1
    2
    createWindow = () => {
    3
    // Create the browser window.
    4
    const win = new BrowserWindow({
    5
    width: 800,
    6
    height: 600,
    7
    webPreferences: {
    8
    nodeIntegration: true,
    9
    },
    10
    });
    11
    12
    // and load the index.html of the app.
    13
    win.loadFile('index.html');
    14
    };
    15
    16
    app.whenReady().then(createWindow);
    17
    createWindow = () => {
    // Create the browser window.
    const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
    nodeIntegration: true,
    },
    });
    // and load the index.html of the app.
    win.loadFile('index.html');
    };
    const { app, BrowserWindow } = require('electron');
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    app.whenReady().then(createWindow);
    17

    View Slide

  70. const { app, BrowserWindow } = require('electron');
    createWindow = () => {
    // Create the browser window.
    const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
    nodeIntegration: true,
    },
    });
    // and load the index.html of the app.
    win.loadFile('index.html');
    };
    app.whenReady().then(createWindow);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    const { app, BrowserWindow } = require('electron');
    1
    2
    createWindow = () => {
    3
    // Create the browser window.
    4
    const win = new BrowserWindow({
    5
    width: 800,
    6
    height: 600,
    7
    webPreferences: {
    8
    nodeIntegration: true,
    9
    },
    10
    });
    11
    12
    // and load the index.html of the app.
    13
    win.loadFile('index.html');
    14
    };
    15
    16
    app.whenReady().then(createWindow);
    17
    createWindow = () => {
    // Create the browser window.
    const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
    nodeIntegration: true,
    },
    });
    // and load the index.html of the app.
    win.loadFile('index.html');
    };
    const { app, BrowserWindow } = require('electron');
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    app.whenReady().then(createWindow);
    17
    const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
    nodeIntegration: true,
    },
    });
    const { app, BrowserWindow } = require('electron');
    1
    2
    createWindow = () => {
    3
    // Create the browser window.
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // and load the index.html of the app.
    13
    win.loadFile('index.html');
    14
    };
    15
    16
    app.whenReady().then(createWindow);
    17

    View Slide

  71. const { app, BrowserWindow } = require('electron');
    createWindow = () => {
    // Create the browser window.
    const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
    nodeIntegration: true,
    },
    });
    // and load the index.html of the app.
    win.loadFile('index.html');
    };
    app.whenReady().then(createWindow);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    const { app, BrowserWindow } = require('electron');
    1
    2
    createWindow = () => {
    3
    // Create the browser window.
    4
    const win = new BrowserWindow({
    5
    width: 800,
    6
    height: 600,
    7
    webPreferences: {
    8
    nodeIntegration: true,
    9
    },
    10
    });
    11
    12
    // and load the index.html of the app.
    13
    win.loadFile('index.html');
    14
    };
    15
    16
    app.whenReady().then(createWindow);
    17
    createWindow = () => {
    // Create the browser window.
    const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
    nodeIntegration: true,
    },
    });
    // and load the index.html of the app.
    win.loadFile('index.html');
    };
    const { app, BrowserWindow } = require('electron');
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    app.whenReady().then(createWindow);
    17
    const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
    nodeIntegration: true,
    },
    });
    const { app, BrowserWindow } = require('electron');
    1
    2
    createWindow = () => {
    3
    // Create the browser window.
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // and load the index.html of the app.
    13
    win.loadFile('index.html');
    14
    };
    15
    16
    app.whenReady().then(createWindow);
    17
    win.loadFile('index.html');
    const { app, BrowserWindow } = require('electron');
    1
    2
    createWindow = () => {
    3
    // Create the browser window.
    4
    const win = new BrowserWindow({
    5
    width: 800,
    6
    height: 600,
    7
    webPreferences: {
    8
    nodeIntegration: true,
    9
    },
    10
    });
    11
    12
    // and load the index.html of the app.
    13
    14
    };
    15
    16
    app.whenReady().then(createWindow);
    17

    View Slide

  72. const { app, BrowserWindow } = require('electron');
    createWindow = () => {
    // Create the browser window.
    const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
    nodeIntegration: true,
    },
    });
    // and load the index.html of the app.
    win.loadFile('index.html');
    };
    app.whenReady().then(createWindow);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    const { app, BrowserWindow } = require('electron');
    1
    2
    createWindow = () => {
    3
    // Create the browser window.
    4
    const win = new BrowserWindow({
    5
    width: 800,
    6
    height: 600,
    7
    webPreferences: {
    8
    nodeIntegration: true,
    9
    },
    10
    });
    11
    12
    // and load the index.html of the app.
    13
    win.loadFile('index.html');
    14
    };
    15
    16
    app.whenReady().then(createWindow);
    17
    createWindow = () => {
    // Create the browser window.
    const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
    nodeIntegration: true,
    },
    });
    // and load the index.html of the app.
    win.loadFile('index.html');
    };
    const { app, BrowserWindow } = require('electron');
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    app.whenReady().then(createWindow);
    17
    const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
    nodeIntegration: true,
    },
    });
    const { app, BrowserWindow } = require('electron');
    1
    2
    createWindow = () => {
    3
    // Create the browser window.
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // and load the index.html of the app.
    13
    win.loadFile('index.html');
    14
    };
    15
    16
    app.whenReady().then(createWindow);
    17
    win.loadFile('index.html');
    const { app, BrowserWindow } = require('electron');
    1
    2
    createWindow = () => {
    3
    // Create the browser window.
    4
    const win = new BrowserWindow({
    5
    width: 800,
    6
    height: 600,
    7
    webPreferences: {
    8
    nodeIntegration: true,
    9
    },
    10
    });
    11
    12
    // and load the index.html of the app.
    13
    14
    };
    15
    16
    app.whenReady().then(createWindow);
    17 app.whenReady().then(createWindow);
    const { app, BrowserWindow } = require('electron');
    1
    2
    createWindow = () => {
    3
    // Create the browser window.
    4
    const win = new BrowserWindow({
    5
    width: 800,
    6
    height: 600,
    7
    webPreferences: {
    8
    nodeIntegration: true,
    9
    },
    10
    });
    11
    12
    // and load the index.html of the app.
    13
    win.loadFile('index.html');
    14
    };
    15
    16
    17

    View Slide





  73. Hello World!


    Hello World!


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    View Slide

  74. $ electron .

    View Slide

  75. View Slide

  76. View Slide

  77. Process
    Mai

    View Slide

  78. Process
    Renderer

    View Slide

  79. View Slide

  80. View Slide

  81. View Slide

  82. ├── assets
    │ ...
    ├── 6.2c6c97b8608a85a82adc.js
    ├── common.42c8477e0640407ae045.js
    ├── favicon.ico
    ├── icon.ico
    ├── index.html
    ├── index.js
    ├── main.8bb24261aa275d6a43db.js
    ├── package.json
    ├── polyfills-es5.739d51467cc1d8efbfae.js
    ├── polyfills.ef4ae634e106e395892c.js
    ├── runtime.061b6e0597d9fe17b937.js
    ├── scripts.82aad45f2fad02beee3c.js
    ├── styles.f503a9ab21b5d570c8af.css
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    View Slide

  83. ├── assets
    │ ...
    ├── 6.2c6c97b8608a85a82adc.js
    ├── common.42c8477e0640407ae045.js
    ├── favicon.ico
    ├── icon.ico
    ├── index.html
    ├── index.js
    ├── main.8bb24261aa275d6a43db.js
    ├── package.json
    ├── polyfills-es5.739d51467cc1d8efbfae.js
    ├── polyfills.ef4ae634e106e395892c.js
    ├── runtime.061b6e0597d9fe17b937.js
    ├── scripts.82aad45f2fad02beee3c.js
    ├── styles.f503a9ab21b5d570c8af.css
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    ├── assets
    │ ...
    ├── 6.2c6c97b8608a85a82adc.js
    ├── common.42c8477e0640407ae045.js
    ├── favicon.ico
    ├── index.html
    ├── main.8bb24261aa275d6a43db.js
    ├── polyfills-es5.739d51467cc1d8efbfae.js
    ├── polyfills.ef4ae634e106e395892c.js
    ├── runtime.061b6e0597d9fe17b937.js
    ├── scripts.82aad45f2fad02beee3c.js
    ├── styles.f503a9ab21b5d570c8af.css
    1
    2
    3
    4
    5
    ├── icon.ico
    6
    7
    ├── index.js
    8
    9
    ├── package.json
    10
    11
    12
    13
    14
    15

    View Slide

  84. ├── assets
    │ ...
    ├── 6.2c6c97b8608a85a82adc.js
    ├── common.42c8477e0640407ae045.js
    ├── favicon.ico
    ├── icon.ico
    ├── index.html
    ├── index.js
    ├── main.8bb24261aa275d6a43db.js
    ├── package.json
    ├── polyfills-es5.739d51467cc1d8efbfae.js
    ├── polyfills.ef4ae634e106e395892c.js
    ├── runtime.061b6e0597d9fe17b937.js
    ├── scripts.82aad45f2fad02beee3c.js
    ├── styles.f503a9ab21b5d570c8af.css
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    ├── assets
    │ ...
    ├── 6.2c6c97b8608a85a82adc.js
    ├── common.42c8477e0640407ae045.js
    ├── favicon.ico
    ├── index.html
    ├── main.8bb24261aa275d6a43db.js
    ├── polyfills-es5.739d51467cc1d8efbfae.js
    ├── polyfills.ef4ae634e106e395892c.js
    ├── runtime.061b6e0597d9fe17b937.js
    ├── scripts.82aad45f2fad02beee3c.js
    ├── styles.f503a9ab21b5d570c8af.css
    1
    2
    3
    4
    5
    ├── icon.ico
    6
    7
    ├── index.js
    8
    9
    ├── package.json
    10
    11
    12
    13
    14
    15
    ├── icon.ico
    ├── index.js
    ├── package.json
    ├── assets
    1
    │ ...
    2
    ├── 6.2c6c97b8608a85a82adc.js
    3
    ├── common.42c8477e0640407ae045.js
    4
    ├── favicon.ico
    5
    6
    ├── index.html
    7
    8
    ├── main.8bb24261aa275d6a43db.js
    9
    10
    ├── polyfills-es5.739d51467cc1d8efbfae.js
    11
    ├── polyfills.ef4ae634e106e395892c.js
    12
    ├── runtime.061b6e0597d9fe17b937.js
    13
    ├── scripts.82aad45f2fad02beee3c.js
    14
    ├── styles.f503a9ab21b5d570c8af.css
    15

    View Slide

  85. View Slide

  86. Communicatio

    View Slide

  87. Communicatio
    IPC

    View Slide

  88. mai proces
    const { ipcMain } = require('electron')
    ipcMain.on('asynchronous-message', (event, arg) => {
    console.log(arg) // prints "ping"
    event.reply('asynchronous-reply', 'pong')
    })
    ipcMain.on('synchronous-message', (event, arg) => {
    console.log(arg) // prints "ping"
    event.returnValue = 'pong'
    })
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    View Slide

  89. mai proces
    const { ipcMain } = require('electron')
    ipcMain.on('asynchronous-message', (event, arg) => {
    console.log(arg) // prints "ping"
    event.reply('asynchronous-reply', 'pong')
    })
    ipcMain.on('synchronous-message', (event, arg) => {
    console.log(arg) // prints "ping"
    event.returnValue = 'pong'
    })
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const { ipcMain } = require('electron')
    1
    2
    ipcMain.on('asynchronous-message', (event, arg) => {
    3
    console.log(arg) // prints "ping"
    4
    event.reply('asynchronous-reply', 'pong')
    5
    })
    6
    7
    ipcMain.on('synchronous-message', (event, arg) => {
    8
    console.log(arg) // prints "ping"
    9
    event.returnValue = 'pong'
    10
    })
    11

    View Slide

  90. mai proces
    const { ipcMain } = require('electron')
    ipcMain.on('asynchronous-message', (event, arg) => {
    console.log(arg) // prints "ping"
    event.reply('asynchronous-reply', 'pong')
    })
    ipcMain.on('synchronous-message', (event, arg) => {
    console.log(arg) // prints "ping"
    event.returnValue = 'pong'
    })
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const { ipcMain } = require('electron')
    1
    2
    ipcMain.on('asynchronous-message', (event, arg) => {
    3
    console.log(arg) // prints "ping"
    4
    event.reply('asynchronous-reply', 'pong')
    5
    })
    6
    7
    ipcMain.on('synchronous-message', (event, arg) => {
    8
    console.log(arg) // prints "ping"
    9
    event.returnValue = 'pong'
    10
    })
    11
    ipcMain.on('asynchronous-message', (event, arg) => {
    console.log(arg) // prints "ping"
    event.reply('asynchronous-reply', 'pong')
    })
    const { ipcMain } = require('electron')
    1
    2
    3
    4
    5
    6
    7
    ipcMain.on('synchronous-message', (event, arg) => {
    8
    console.log(arg) // prints "ping"
    9
    event.returnValue = 'pong'
    10
    })
    11

    View Slide

  91. mai proces
    const { ipcMain } = require('electron')
    ipcMain.on('asynchronous-message', (event, arg) => {
    console.log(arg) // prints "ping"
    event.reply('asynchronous-reply', 'pong')
    })
    ipcMain.on('synchronous-message', (event, arg) => {
    console.log(arg) // prints "ping"
    event.returnValue = 'pong'
    })
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const { ipcMain } = require('electron')
    1
    2
    ipcMain.on('asynchronous-message', (event, arg) => {
    3
    console.log(arg) // prints "ping"
    4
    event.reply('asynchronous-reply', 'pong')
    5
    })
    6
    7
    ipcMain.on('synchronous-message', (event, arg) => {
    8
    console.log(arg) // prints "ping"
    9
    event.returnValue = 'pong'
    10
    })
    11
    ipcMain.on('asynchronous-message', (event, arg) => {
    console.log(arg) // prints "ping"
    event.reply('asynchronous-reply', 'pong')
    })
    const { ipcMain } = require('electron')
    1
    2
    3
    4
    5
    6
    7
    ipcMain.on('synchronous-message', (event, arg) => {
    8
    console.log(arg) // prints "ping"
    9
    event.returnValue = 'pong'
    10
    })
    11
    ipcMain.on('synchronous-message', (event, arg) => {
    console.log(arg) // prints "ping"
    event.returnValue = 'pong'
    })
    const { ipcMain } = require('electron')
    1
    2
    ipcMain.on('asynchronous-message', (event, arg) => {
    3
    console.log(arg) // prints "ping"
    4
    event.reply('asynchronous-reply', 'pong')
    5
    })
    6
    7
    8
    9
    10
    11

    View Slide

  92. renderer proces
    const { ipcRenderer } = require('electron');
    // prints "pong"
    console.log(ipcRenderer.sendSync('synchronous-message', 'ping'))
    ipcRenderer.on('asynchronous-reply', (event, arg) => {
    console.log(arg) // prints "pong"
    })
    ipcRenderer.send('asynchronous-message', 'ping')
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    View Slide

  93. renderer proces
    const { ipcRenderer } = require('electron');
    // prints "pong"
    console.log(ipcRenderer.sendSync('synchronous-message', 'ping'))
    ipcRenderer.on('asynchronous-reply', (event, arg) => {
    console.log(arg) // prints "pong"
    })
    ipcRenderer.send('asynchronous-message', 'ping')
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const { ipcRenderer } = require('electron');
    1
    2
    // prints "pong"
    3
    console.log(ipcRenderer.sendSync('synchronous-message', 'ping'))
    4
    5
    ipcRenderer.on('asynchronous-reply', (event, arg) => {
    6
    console.log(arg) // prints "pong"
    7
    })
    8
    9
    ipcRenderer.send('asynchronous-message', 'ping')
    10

    View Slide

  94. renderer proces
    const { ipcRenderer } = require('electron');
    // prints "pong"
    console.log(ipcRenderer.sendSync('synchronous-message', 'ping'))
    ipcRenderer.on('asynchronous-reply', (event, arg) => {
    console.log(arg) // prints "pong"
    })
    ipcRenderer.send('asynchronous-message', 'ping')
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const { ipcRenderer } = require('electron');
    1
    2
    // prints "pong"
    3
    console.log(ipcRenderer.sendSync('synchronous-message', 'ping'))
    4
    5
    ipcRenderer.on('asynchronous-reply', (event, arg) => {
    6
    console.log(arg) // prints "pong"
    7
    })
    8
    9
    ipcRenderer.send('asynchronous-message', 'ping')
    10
    // prints "pong"
    console.log(ipcRenderer.sendSync('synchronous-message', 'ping'))
    const { ipcRenderer } = require('electron');
    1
    2
    3
    4
    5
    ipcRenderer.on('asynchronous-reply', (event, arg) => {
    6
    console.log(arg) // prints "pong"
    7
    })
    8
    9
    ipcRenderer.send('asynchronous-message', 'ping')
    10

    View Slide

  95. renderer proces
    const { ipcRenderer } = require('electron');
    // prints "pong"
    console.log(ipcRenderer.sendSync('synchronous-message', 'ping'))
    ipcRenderer.on('asynchronous-reply', (event, arg) => {
    console.log(arg) // prints "pong"
    })
    ipcRenderer.send('asynchronous-message', 'ping')
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const { ipcRenderer } = require('electron');
    1
    2
    // prints "pong"
    3
    console.log(ipcRenderer.sendSync('synchronous-message', 'ping'))
    4
    5
    ipcRenderer.on('asynchronous-reply', (event, arg) => {
    6
    console.log(arg) // prints "pong"
    7
    })
    8
    9
    ipcRenderer.send('asynchronous-message', 'ping')
    10
    // prints "pong"
    console.log(ipcRenderer.sendSync('synchronous-message', 'ping'))
    const { ipcRenderer } = require('electron');
    1
    2
    3
    4
    5
    ipcRenderer.on('asynchronous-reply', (event, arg) => {
    6
    console.log(arg) // prints "pong"
    7
    })
    8
    9
    ipcRenderer.send('asynchronous-message', 'ping')
    10
    ipcRenderer.on('asynchronous-reply', (event, arg) => {
    console.log(arg) // prints "pong"
    })
    ipcRenderer.send('asynchronous-message', 'ping')
    const { ipcRenderer } = require('electron');
    1
    2
    // prints "pong"
    3
    console.log(ipcRenderer.sendSync('synchronous-message', 'ping'))
    4
    5
    6
    7
    8
    9
    10

    View Slide

  96. mai proces
    var os = require('os');
    var cpus = os.cpus();
    1
    2
    3

    View Slide

  97. mai proces
    var os = require('os');
    var cpus = os.cpus();
    1
    2
    3
    var os = require('os');
    1
    2
    var cpus = os.cpus();
    3

    View Slide

  98. mai proces
    var os = require('os');
    var cpus = os.cpus();
    1
    2
    3
    var os = require('os');
    1
    2
    var cpus = os.cpus();
    3 var cpus = os.cpus();
    var os = require('os');
    1
    2
    3

    View Slide

  99. mai proces
    let startSendCpuValues = () => {
    setInterval(() => {
    cpuValues.getCPUUsage(percentage => {
    if (mainWindow) {
    mainWindow.webContents.send(
    'newCpuValue',
    (percentage * 100).toFixed(2)
    );
    }
    });
    }, 1000);
    };
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    View Slide

  100. mai proces
    let startSendCpuValues = () => {
    setInterval(() => {
    cpuValues.getCPUUsage(percentage => {
    if (mainWindow) {
    mainWindow.webContents.send(
    'newCpuValue',
    (percentage * 100).toFixed(2)
    );
    }
    });
    }, 1000);
    };
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    let startSendCpuValues = () => {
    setInterval(() => {
    cpuValues.getCPUUsage(percentage => {
    if (mainWindow) {
    mainWindow.webContents.send(
    'newCpuValue',
    (percentage * 100).toFixed(2)
    );
    }
    });
    }, 1000);
    };
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    View Slide

  101. mai proces
    let startSendCpuValues = () => {
    setInterval(() => {
    cpuValues.getCPUUsage(percentage => {
    if (mainWindow) {
    mainWindow.webContents.send(
    'newCpuValue',
    (percentage * 100).toFixed(2)
    );
    }
    });
    }, 1000);
    };
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    let startSendCpuValues = () => {
    setInterval(() => {
    cpuValues.getCPUUsage(percentage => {
    if (mainWindow) {
    mainWindow.webContents.send(
    'newCpuValue',
    (percentage * 100).toFixed(2)
    );
    }
    });
    }, 1000);
    };
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    cpuValues.getCPUUsage(percentage => {
    });
    let startSendCpuValues = () => {
    1
    setInterval(() => {
    2
    3
    if (mainWindow) {
    4
    mainWindow.webContents.send(
    5
    'newCpuValue',
    6
    (percentage * 100).toFixed(2)
    7
    );
    8
    }
    9
    10
    }, 1000);
    11
    };
    12

    View Slide

  102. mai proces
    let startSendCpuValues = () => {
    setInterval(() => {
    cpuValues.getCPUUsage(percentage => {
    if (mainWindow) {
    mainWindow.webContents.send(
    'newCpuValue',
    (percentage * 100).toFixed(2)
    );
    }
    });
    }, 1000);
    };
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    let startSendCpuValues = () => {
    setInterval(() => {
    cpuValues.getCPUUsage(percentage => {
    if (mainWindow) {
    mainWindow.webContents.send(
    'newCpuValue',
    (percentage * 100).toFixed(2)
    );
    }
    });
    }, 1000);
    };
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    cpuValues.getCPUUsage(percentage => {
    });
    let startSendCpuValues = () => {
    1
    setInterval(() => {
    2
    3
    if (mainWindow) {
    4
    mainWindow.webContents.send(
    5
    'newCpuValue',
    6
    (percentage * 100).toFixed(2)
    7
    );
    8
    }
    9
    10
    }, 1000);
    11
    };
    12
    mainWindow.webContents.send(
    'newCpuValue',
    (percentage * 100).toFixed(2)
    );
    let startSendCpuValues = () => {
    1
    setInterval(() => {
    2
    cpuValues.getCPUUsage(percentage => {
    3
    if (mainWindow) {
    4
    5
    6
    7
    8
    }
    9
    });
    10
    }, 1000);
    11
    };
    12

    View Slide

  103. renderer proces
    @Injectable({ providedIn: 'root' })
    export class CpuValueService {
    private onNewCpuValue = new Subject();
    get newCpuValue(){
    return this.onNewCpuValue.asObservable();
    }
    constructor(private electronService: ElectronService) {
    if (environment.desktop) {
    this.registerCpuEvent();
    }
    }
    private registerCpuEvent() {
    if (this.electronService.ipcRenderer) {
    this.electronService.ipcRenderer.on(
    'newCpuValue',
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

    View Slide

  104. renderer proces
    @Injectable({ providedIn: 'root' })
    export class CpuValueService {
    private onNewCpuValue = new Subject();
    get newCpuValue(){
    return this.onNewCpuValue.asObservable();
    }
    constructor(private electronService: ElectronService) {
    if (environment.desktop) {
    this.registerCpuEvent();
    }
    }
    private registerCpuEvent() {
    if (this.electronService.ipcRenderer) {
    this.electronService.ipcRenderer.on(
    'newCpuValue',
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    constructor(private electronService: ElectronService) {
    if (environment.desktop) {
    this.registerCpuEvent();
    }
    }
    @Injectable({ providedIn: root })
    1
    export class CpuValueService {
    2
    private onNewCpuValue = new Subject();
    3
    4
    get newCpuValue(){
    5
    return this.onNewCpuValue.asObservable();
    6
    }
    7
    8
    9
    10
    11
    12
    13
    14
    private registerCpuEvent() {
    15
    if (this.electronService.ipcRenderer) {
    16
    this.electronService.ipcRenderer.on(
    17
    'newCpuValue',
    18
    (event: any, data: any) => {
    19
    this.onNewCpuValue.next(data);
    20
    }
    21

    View Slide

  105. renderer proces
    @Injectable({ providedIn: 'root' })
    export class CpuValueService {
    private onNewCpuValue = new Subject();
    get newCpuValue(){
    return this.onNewCpuValue.asObservable();
    }
    constructor(private electronService: ElectronService) {
    if (environment.desktop) {
    this.registerCpuEvent();
    }
    }
    private registerCpuEvent() {
    if (this.electronService.ipcRenderer) {
    this.electronService.ipcRenderer.on(
    'newCpuValue',
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    constructor(private electronService: ElectronService) {
    if (environment.desktop) {
    this.registerCpuEvent();
    }
    }
    @Injectable({ providedIn: 'root' })
    1
    export class CpuValueService {
    2
    private onNewCpuValue = new Subject();
    3
    4
    get newCpuValue(){
    5
    return this.onNewCpuValue.asObservable();
    6
    }
    7
    8
    9
    10
    11
    12
    13
    14
    private registerCpuEvent() {
    15
    if (this.electronService.ipcRenderer) {
    16
    this.electronService.ipcRenderer.on(
    17
    'newCpuValue',
    18
    private registerCpuEvent() {
    if (this.electronService.ipcRenderer) {
    this.electronService.ipcRenderer.on(
    'newCpuValue',
    (event: any, data: any) => {
    this.onNewCpuValue.next(data);
    }
    );
    }
    }
    }
    7
    8
    constructor(private electronService: ElectronService) {
    9
    if (environment.desktop) {
    10
    this.registerCpuEvent();
    11
    }
    12
    }
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    }
    25

    View Slide

  106. renderer proces
    @Injectable({ providedIn: 'root' })
    export class CpuValueService {
    private onNewCpuValue = new Subject();
    get newCpuValue(){
    return this.onNewCpuValue.asObservable();
    }
    constructor(private electronService: ElectronService) {
    if (environment.desktop) {
    this.registerCpuEvent();
    }
    }
    private registerCpuEvent() {
    if (this.electronService.ipcRenderer) {
    this.electronService.ipcRenderer.on(
    'newCpuValue',
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    constructor(private electronService: ElectronService) {
    if (environment.desktop) {
    this.registerCpuEvent();
    }
    }
    @Injectable({ providedIn: 'root' })
    1
    export class CpuValueService {
    2
    private onNewCpuValue = new Subject();
    3
    4
    get newCpuValue(){
    5
    return this.onNewCpuValue.asObservable();
    6
    }
    7
    8
    9
    10
    11
    12
    13
    14
    private registerCpuEvent() {
    15
    if (this.electronService.ipcRenderer) {
    16
    this.electronService.ipcRenderer.on(
    17
    'newCpuValue',
    18
    private registerCpuEvent() {
    if (this.electronService.ipcRenderer) {
    this.electronService.ipcRenderer.on(
    'newCpuValue',
    @Injectable({ providedIn: 'root' })
    1
    export class CpuValueService {
    2
    private onNewCpuValue = new Subject();
    3
    4
    get newCpuValue(){
    5
    return this.onNewCpuValue.asObservable();
    6
    }
    7
    8
    constructor(private electronService: ElectronService) {
    9
    if (environment.desktop) {
    10
    this.registerCpuEvent();
    11
    }
    12
    }
    13
    14
    15
    16
    17
    18
    private onNewCpuValue = new Subject();
    get newCpuValue(){
    return this.onNewCpuValue.asObservable();
    }
    @Injectable({ providedIn: 'root' })
    1
    export class CpuValueService {
    2
    3
    4
    5
    6
    7
    8
    constructor(private electronService: ElectronService) {
    9
    if (environment.desktop) {
    10
    this.registerCpuEvent();
    11
    }
    12
    }
    13
    14
    private registerCpuEvent() {
    15
    if (this.electronService.ipcRenderer) {
    16
    this.electronService.ipcRenderer.on(
    17
    'newCpuValue',
    18

    View Slide

  107. $ npm install electron-packager -g

    View Slide

  108. $ electron-packager .temp/desktop

    View Slide

  109. $ electron-packager .temp/desktop --platform=linux, win32

    View Slide

  110. Automatic Updates
    Crash Reports
    Content Tracing
    Installation Packages

    View Slide

  111. View Slide

  112. Dem

    View Slide

  113. What about variation points?
    "
    "

    View Slide

  114. View Slide

  115. Dependenc Injectio

    View Slide

  116. @Injectable({
    providedIn: 'root',
    })
    export abstract class AbstractCameraService {
    abstract getPhoto(...): Observable;
    }
    1
    2
    3
    4
    5
    6

    View Slide

  117. @Injectable({
    providedIn: 'root',
    useFactory: cameraFactory,
    deps: [PlatformInformationService],
    })
    export abstract class AbstractCameraService {
    abstract getPhoto(...): Observable;
    }
    1
    2
    3
    4
    5
    6
    7
    8

    View Slide

  118. export function cameraFactory(
    platformInformationService: PlatformInformationService
    ): AbstractCameraService {
    return platformInformationService.isMobile
    ? new MobileCameraService()
    : new DesktopCameraService();
    }
    @Injectable({
    providedIn: 'root',
    useFactory: cameraFactory,
    deps: [PlatformInformationService],
    })
    export abstract class AbstractCameraService {
    abstract getPhoto(...): Observable;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    View Slide

  119. import {
    AbstractCameraService, Photo
    } from '@workspace/features/camera';
    @Component({ ... })
    export class ProfileDetailsComponent {
    constructor(
    private cameraService: AbstractCameraService
    ) {}
    pictureClicked() {
    this.cameraService.getPhoto()
    .subscribe((result: Photo) => {
    // ...
    });
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

    View Slide

  120. import {
    AbstractCameraService, Photo
    } from '@workspace/features/camera';
    @Component({ ... })
    export class ProfileDetailsComponent {
    constructor(
    private cameraService: AbstractCameraService
    ) {}
    pictureClicked() {
    this.cameraService.getPhoto()
    .subscribe((result: Photo) => {
    // ...
    });
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import {
    AbstractCameraService, Photo
    } from '@workspace/features/camera';
    1
    2
    3
    4
    @Component({ ... })
    5
    export class ProfileDetailsComponent {
    6
    7
    constructor(
    8
    private cameraService: AbstractCameraService
    9
    ) {}
    10
    11
    pictureClicked() {
    12
    this.cameraService.getPhoto()
    13
    .subscribe((result: Photo) => {
    14
    // ...
    15
    });
    16
    }
    17
    }
    18

    View Slide

  121. import {
    AbstractCameraService, Photo
    } from '@workspace/features/camera';
    @Component({ ... })
    export class ProfileDetailsComponent {
    constructor(
    private cameraService: AbstractCameraService
    ) {}
    pictureClicked() {
    this.cameraService.getPhoto()
    .subscribe((result: Photo) => {
    // ...
    });
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import {
    AbstractCameraService, Photo
    } from '@workspace/features/camera';
    1
    2
    3
    4
    @Component({ ... })
    5
    export class ProfileDetailsComponent {
    6
    7
    constructor(
    8
    private cameraService: AbstractCameraService
    9
    ) {}
    10
    11
    pictureClicked() {
    12
    this.cameraService.getPhoto()
    13
    .subscribe((result: Photo) => {
    14
    // ...
    15
    });
    16
    }
    17
    }
    18
    constructor(
    private cameraService: AbstractCameraService
    ) {}
    import {
    1
    AbstractCameraService, Photo
    2
    } from '@workspace/features/camera';
    3
    4
    @Component({ ... })
    5
    export class ProfileDetailsComponent {
    6
    7
    8
    9
    10
    11
    pictureClicked() {
    12
    this.cameraService.getPhoto()
    13
    .subscribe((result: Photo) => {
    14
    // ...
    15
    });
    16
    }
    17
    }
    18

    View Slide

  122. import {
    AbstractCameraService, Photo
    } from '@workspace/features/camera';
    @Component({ ... })
    export class ProfileDetailsComponent {
    constructor(
    private cameraService: AbstractCameraService
    ) {}
    pictureClicked() {
    this.cameraService.getPhoto()
    .subscribe((result: Photo) => {
    // ...
    });
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import {
    AbstractCameraService, Photo
    } from '@workspace/features/camera';
    1
    2
    3
    4
    @Component({ ... })
    5
    export class ProfileDetailsComponent {
    6
    7
    constructor(
    8
    private cameraService: AbstractCameraService
    9
    ) {}
    10
    11
    pictureClicked() {
    12
    this.cameraService.getPhoto()
    13
    .subscribe((result: Photo) => {
    14
    // ...
    15
    });
    16
    }
    17
    }
    18
    constructor(
    private cameraService: AbstractCameraService
    ) {}
    import {
    1
    AbstractCameraService, Photo
    2
    } from '@workspace/features/camera';
    3
    4
    @Component({ ... })
    5
    export class ProfileDetailsComponent {
    6
    7
    8
    9
    10
    11
    pictureClicked() {
    12
    this.cameraService.getPhoto()
    13
    .subscribe((result: Photo) => {
    14
    // ...
    15
    });
    16
    }
    17
    }
    18
    pictureClicked() {
    this.cameraService.getPhoto()
    .subscribe((result: Photo) => {
    // ...
    });
    }
    import {
    1
    AbstractCameraService, Photo
    2
    } from '@workspace/features/camera';
    3
    4
    @Component({ ... })
    5
    export class ProfileDetailsComponent {
    6
    7
    constructor(
    8
    private cameraService: AbstractCameraService
    9
    ) {}
    10
    11
    12
    13
    14
    15
    16
    17
    }
    18

    View Slide

  123. View Slide

  124. Securit

    View Slide

  125. Mobil
    Desktop
    Web

    View Slide

  126. OAut 2
    OIDC

    View Slide

  127. https://bit.ly/3mMHyKY

    View Slide

  128. Progressiv
    Web
    App

    View Slide

  129. Dem

    View Slide

  130. Realtim Cr
    Platfor App
    Angular, ASP.NET
    Cor & Signa R
    wit

    View Slide

  131. an yo !

    View Slide

  132. Fabian
    Gosebrink

    View Slide