1 of 161

The rise of web developers

@loicknuchel

2 of 161

?

3 of 161

Loïc Knuchel

Geek passionné

Freelance

Développeur web full-stack

Entrepreneur

Cookers / SalooN

loicknuchel@gmail.com

@loicknuchel

http://loic.knuchel.org/

4 of 161

L’histoire du smartphone

5 of 161

Juin 2007

Lancement de l’iPhone 1

6 of 161

Septembre 2008

Sortie du HTC G1

avec Android

7 of 161

8 of 161

  • Nouvelles possibilités
    • UX
    • Technique
    • Business

  • Un écosystème à bâtir

9 of 161

10 of 161

  • Nouvelle stack technique

  • Peu de personnes formées

  • Développer plusieurs fois la même chose

  • Environnements très spécifiques (offline, puissance, versions, diversité...)

11 of 161

12 of 161

La WebView

13 of 161

Idée

Coder des applications

entièrement dans la WebView

14 of 161

Idée

Coder des applications

entièrement dans la WebView

Avantages :

  • Cross-platform
  • Technologies et environnements connus

15 of 161

PhoneGap / Cordova

Mars 2009 :

Lancement de PhoneGap par Nitobi

Octobre 2011 :

Rachat de Nitobi par Adobe

Séparation de la technologie (Cordova) et des services commerciaux (PhoneGap)

16 of 161

17 of 161

Oups...

18 of 161

Oups...

  • UI moche
  • UI peu réactive
  • loin du look & feel natif

19 of 161

Oups...

  • UI moche
  • UI peu réactive
  • loin du look & feel natif

Mauvais support des standards du web dans la WebView

Peu d’outils / librairies

Téléphones peu puissants

20 of 161

Oups...

  • UI moche
  • UI peu réactive
  • loin du look & feel natif

Mauvais support des standards du web dans la WebView

Peu d’outils / librairies

Téléphones peu puissants

  • App de mauvaise qualité
  • Bugs

21 of 161

Oups...

  • UI moche
  • UI peu réactive
  • loin du look & feel natif

Mauvais support des standards du web dans la WebView

Peu d’outils / librairies

Téléphones peu puissants

  • App de mauvaise qualité
  • Bugs

Souvent pour des projets à petit budget...

22 of 161

Aujourd’hui

  • Téléphones puissants (et de + en +)
  • Très bon support des standards web dans la WebView
  • Beaucoup d’outils / librairies

23 of 161

24 of 161

25 of 161

26 of 161

Native develoment is ...

27 of 161

way ...

28 of 161

29 of 161

Performance

30 of 161

Code re-write

31 of 161

Complicated

32 of 161

Android Menu (Nav Drawer)

33 of 161

...

@Override

public boolean onCreateOptionsMenu(Menu menu) {

MenuInflater inflater = getMenuInflater();

inflater.inflate(R.menu.main, menu);

return super.onCreateOptionsMenu(menu);

}

@Override

public boolean onPrepareOptionsMenu(Menu menu) {

boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);

menu.findItem(R.id.action_websearch).setVisible(!drawerOpen);

return super.onPrepareOptionsMenu(menu);

}

@Override

public boolean onOptionsItemSelected(MenuItem item) {

if (mDrawerToggle.onOptionsItemSelected(item)) {

return true;

}

switch(item.getItemId()) {

case R.id.action_websearch:

Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);

intent.putExtra(SearchManager.QUERY, getActionBar().getTitle());

if (intent.resolveActivity(getPackageManager()) != null) {

startActivity(intent);

} else {

Toast.makeText(this, R.string.app_not_available, Toast.LENGTH_LONG).show();

}

return true;

default:

return super.onOptionsItemSelected(item);

}

}

private class DrawerItemClickListener implements ListView.OnItemClickListener {

@Override

public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

selectItem(position);

}

}

private void selectItem(int position) {

Fragment fragment = new PlanetFragment();

Bundle args = new Bundle();

args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position);

fragment.setArguments(args);

FragmentManager fragmentManager = getFragmentManager();

fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();

mDrawerList.setItemChecked(position, true);

setTitle(mPlanetTitles[position]);

mDrawerLayout.closeDrawer(mDrawerList);

}

...

public class MainActivity extends Activity {

private DrawerLayout mDrawerLayout;

private ListView mDrawerList;

private ActionBarDrawerToggle mDrawerToggle;

private CharSequence mDrawerTitle;

private CharSequence mTitle;

private String[] mPlanetTitles;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

mTitle = mDrawerTitle = getTitle();

mPlanetTitles = getResources().getStringArray(R.array.planets_array);

mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);

mDrawerList = (ListView) findViewById(R.id.left_drawer);

mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);

mDrawerList.setAdapter(new ArrayAdapter<String>(this, R.layout.drawer_list_item, mPlanetTitles));

mDrawerList.setOnItemClickListener(new DrawerItemClickListener());

getActionBar().setDisplayHomeAsUpEnabled(true);

getActionBar().setHomeButtonEnabled(true);

mDrawerToggle = new ActionBarDrawerToggle(

this,

mDrawerLayout,

R.drawable.ic_drawer,

R.string.drawer_open,

R.string.drawer_close

) {

public void onDrawerClosed(View view) {

getActionBar().setTitle(mTitle);

invalidateOptionsMenu();

}

public void onDrawerOpened(View drawerView) {

getActionBar().setTitle(mDrawerTitle);

invalidateOptionsMenu();

}

};

mDrawerLayout.setDrawerListener(mDrawerToggle);

if (savedInstanceState == null) {

selectItem(0);

}

}

...

34 of 161

<android.support.v4.widget.DrawerLayout

xmlns:android="http://schemas.android.com/apk/res/android"

android:id="@+id/drawer_layout"

android:layout_width="match_parent"

android:layout_height="match_parent">

<FrameLayout

android:id="@+id/content_frame"

android:layout_width="match_parent"

android:layout_height="match_parent" />

<ListView

android:id="@+id/left_drawer"

android:layout_width="240dp"

android:layout_height="match_parent"

android:layout_gravity="start"

android:choiceMode="singleChoice"

android:divider="@android:color/transparent"

android:dividerHeight="0dp"

android:background="#111"/>

</android.support.v4.widget.DrawerLayout>

...

@Override

public void setTitle(CharSequence title) {

mTitle = title;

getActionBar().setTitle(mTitle);

}

@Override

protected void onPostCreate(Bundle savedInstanceState) {

super.onPostCreate(savedInstanceState);

mDrawerToggle.syncState();

}

@Override

public void onConfigurationChanged(Configuration newConfig) {

super.onConfigurationChanged(newConfig);

mDrawerToggle.onConfigurationChanged(newConfig);

}

public static class PlanetFragment extends Fragment {

public static final String ARG_PLANET_NUMBER = "planet_number";

public PlanetFragment() {}

@Override

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

View rootView = inflater.inflate(R.layout.fragment_planet, container, false);

int i = getArguments().getInt(ARG_PLANET_NUMBER);

String planet = getResources().getStringArray(R.array.planets_array)[i];

int imageId = getResources().getIdentifier(planet.toLowerCase(Locale.getDefault()),

"drawable", getActivity().getPackageName());

((ImageView) rootView.findViewById(R.id.image)).setImageResource(imageId);

getActivity().setTitle(planet);

return rootView;

}

}

}

35 of 161

<android.support.v4.widget.DrawerLayout

xmlns:android="http://schemas.android.com/apk/res/android"

android:id="@+id/drawer_layout"

android:layout_width="match_parent"

android:layout_height="match_parent">

<FrameLayout

android:id="@+id/content_frame"

android:layout_width="match_parent"

android:layout_height="match_parent" />

<ListView

android:id="@+id/left_drawer"

android:layout_width="240dp"

android:layout_height="match_parent"

android:layout_gravity="start"

android:choiceMode="singleChoice"

android:divider="@android:color/transparent"

android:dividerHeight="0dp"

android:background="#111"/>

</android.support.v4.widget.DrawerLayout>

...

@Override

public void setTitle(CharSequence title) {

mTitle = title;

getActionBar().setTitle(mTitle);

}

@Override

protected void onPostCreate(Bundle savedInstanceState) {

super.onPostCreate(savedInstanceState);

mDrawerToggle.syncState();

}

@Override

public void onConfigurationChanged(Configuration newConfig) {

super.onConfigurationChanged(newConfig);

mDrawerToggle.onConfigurationChanged(newConfig);

}

public static class PlanetFragment extends Fragment {

public static final String ARG_PLANET_NUMBER = "planet_number";

public PlanetFragment() {}

@Override

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

View rootView = inflater.inflate(R.layout.fragment_planet, container, false);

int i = getArguments().getInt(ARG_PLANET_NUMBER);

String planet = getResources().getStringArray(R.array.planets_array)[i];

int imageId = getResources().getIdentifier(planet.toLowerCase(Locale.getDefault()),

"drawable", getActivity().getPackageName());

((ImageView) rootView.findViewById(R.id.image)).setImageResource(imageId);

getActivity().setTitle(planet);

return rootView;

}

}

}

36 of 161

37 of 161

App Store installs are broken !

38 of 161

App Store installs are broken !

?

39 of 161

App Store installs are broken !

?

40 of 161

App Store installs are broken !

?

41 of 161

App Store installs are broken !

42 of 161

App Store installs are broken !

43 of 161

App Store installs are broken !

44 of 161

45 of 161

46 of 161

47 of 161

48 of 161

Ionic c’est quoi ?

+

=

49 of 161

Ionic c’est quoi ?

+

=

50 of 161

Stack technologique

Natif

Web

Téléphone & APIs natives

Cordova : webview +

JavaScript bridges

Angular

Ionic

Application

51 of 161

DX

We want to cater to the 99% who just want to build something functional quickly and not break the bank to do it. - Max Lynch

52 of 161

DX

#Platform continuity

53 of 161

DX

#Platform continuity

#CLI

54 of 161

DX

#Platform continuity

#CLI

#Ionic Market

55 of 161

DX

#Platform continuity

#CLI

#Backend services

#Ionic Market

56 of 161

DX

#Platform continuity

#CLI

#Backend services

#Push

#Deploy

#Package

#Auth

#Analytics

#Ionic Market

57 of 161

DX

#Platform continuity

#CLI

#Backend services

#Push

#Deploy

#Package

#Auth

#Analytics

#Ionic View

#Native plugins

#Ionic Market

#Ionic Creator

58 of 161

@ionitron

59 of 161

Dév web = Dév mobile

60 of 161

Ionic is a complete ecosystem !

61 of 161

One more thing...

62 of 161

Hybrid apps have superpowers...

63 of 161

Hybrid superpowers

64 of 161

Hybrid superpowers

Web App :

  • Cross-platform
  • Searchable
  • Accès instantané
  • Deep link

65 of 161

Hybrid superpowers

Web App :

  • Cross-platform
  • Searchable
  • Accès instantané
  • Deep link

100 %

66 of 161

Hybrid superpowers

Web App :

  • Cross-platform
  • Searchable
  • Accès instantané
  • Deep link

Progressive Web App :

  • Installation instantanée
  • Lancement depuis la Home
  • Offline
  • Push notifications

67 of 161

Hybrid superpowers

Web App :

  • Cross-platform
  • Searchable
  • Accès instantané
  • Deep link

Progressive Web App :

  • Installation instantanée
  • Lancement depuis la Home
  • Offline
  • Push notifications

Native App :

  • Accès complet au téléphone

68 of 161

Hybrid superpowers

69 of 161

App Store deployment is broken !

Apple : ~ 2 jours

Google : ~ 2 heures

70 of 161

Hybrid superpowers

71 of 161

72 of 161

73 of 161

74 of 161

75 of 161

76 of 161

77 of 161

78 of 161

79 of 161

80 of 161

81 of 161

82 of 161

83 of 161

Nouveautés depuis Ionic 1

  • Angular 2 (+ TypeScript)
  • Navigation push/pop
  • Nouveaux composants
    • DateTime
    • Toast
    • Searchbar
    • Segment

84 of 161

Getting started

Install nodejs & npm

85 of 161

Getting started

Install nodejs & npm

$ npm install -g ionic@beta

86 of 161

Getting started

Install nodejs & npm

$ npm install -g ionic@beta

$ ionic start demoApp --v2 --ts

87 of 161

Getting started

Install nodejs & npm

$ npm install -g ionic@beta

$ ionic start demoApp --v2 --ts

$ cd demoApp && ionic serve

♬ ♫ ♬ ♫ Your Ionic app is ready to go! ♬ ♫ ♬ ♫

88 of 161

Getting started

Install nodejs & npm

$ npm install -g ionic@beta

$ ionic start demoApp --v2 --ts

$ cd demoApp && ionic serve

♬ ♫ ♬ ♫ Your Ionic app is ready to go! ♬ ♫ ♬ ♫

89 of 161

Getting started

Install mobile sdk (Android ou iOS)

90 of 161

Getting started

Install mobile sdk (Android ou iOS)

$ sudo npm install -g cordova

91 of 161

Getting started

Install mobile sdk (Android ou iOS)

$ sudo npm install -g cordova

$ ionic platform add android

92 of 161

Getting started

Install mobile sdk (Android ou iOS)

$ sudo npm install -g cordova

$ ionic platform add android

$ ionic run android

93 of 161

Structure de fichiers

94 of 161

Structure de fichiers

95 of 161

Structure de fichiers

96 of 161

index.html

<!DOCTYPE html>

<html lang="en" dir="ltr">

<head>

<meta charset="UTF-8">

<title>Ionic</title>

<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">

<meta name="format-detection" content="telephone=no">

<meta name="msapplication-tap-highlight" content="no">

<link ios-href="build/css/app.ios.css" rel="stylesheet">

<link md-href="build/css/app.md.css" rel="stylesheet">

<link wp-href="build/css/app.wp.css" rel="stylesheet">

</head>

<body>

<!-- this Ionic's root component and where the app will load -->

<ion-app></ion-app>

<!-- cordova.js required for cordova apps -->

<script src="cordova.js"></script>

<!-- Polyfill needed for platforms without Promise and Collection support -->

<script src="build/js/es6-shim.min.js"></script>

<!-- Zone.js and Reflect-metadata -->

<script src="build/js/Reflect.js"></script>

<script src="build/js/zone.js"></script>

<!-- the bundle which is built from the app's source code -->

<script src="build/js/app.bundle.js"></script>

</body>

</html>

97 of 161

index.html

<!DOCTYPE html>

<html lang="en" dir="ltr">

<head>

<meta charset="UTF-8">

<title>Ionic</title>

<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">

<meta name="format-detection" content="telephone=no">

<meta name="msapplication-tap-highlight" content="no">

<link ios-href="build/css/app.ios.css" rel="stylesheet">

<link md-href="build/css/app.md.css" rel="stylesheet">

<link wp-href="build/css/app.wp.css" rel="stylesheet">

</head>

<body>

<!-- this Ionic's root component and where the app will load -->

<ion-app></ion-app>

<!-- cordova.js required for cordova apps -->

<script src="cordova.js"></script>

<!-- Polyfill needed for platforms without Promise and Collection support -->

<script src="build/js/es6-shim.min.js"></script>

<!-- Zone.js and Reflect-metadata -->

<script src="build/js/Reflect.js"></script>

<script src="build/js/zone.js"></script>

<!-- the bundle which is built from the app's source code -->

<script src="build/js/app.bundle.js"></script>

</body>

</html>

98 of 161

index.html

<!DOCTYPE html>

<html lang="en" dir="ltr">

<head>

<meta charset="UTF-8">

<title>Ionic</title>

<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">

<meta name="format-detection" content="telephone=no">

<meta name="msapplication-tap-highlight" content="no">

<link ios-href="build/css/app.ios.css" rel="stylesheet">

<link md-href="build/css/app.md.css" rel="stylesheet">

<link wp-href="build/css/app.wp.css" rel="stylesheet">

</head>

<body>

<!-- this Ionic's root component and where the app will load -->

<ion-app></ion-app>

<!-- cordova.js required for cordova apps -->

<script src="cordova.js"></script>

<!-- Polyfill needed for platforms without Promise and Collection support -->

<script src="build/js/es6-shim.min.js"></script>

<!-- Zone.js and Reflect-metadata -->

<script src="build/js/Reflect.js"></script>

<script src="build/js/zone.js"></script>

<!-- the bundle which is built from the app's source code -->

<script src="build/js/app.bundle.js"></script>

</body>

</html>

99 of 161

index.html

<!DOCTYPE html>

<html lang="en" dir="ltr">

<head>

<meta charset="UTF-8">

<title>Ionic</title>

<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">

<meta name="format-detection" content="telephone=no">

<meta name="msapplication-tap-highlight" content="no">

<link ios-href="build/css/app.ios.css" rel="stylesheet">

<link md-href="build/css/app.md.css" rel="stylesheet">

<link wp-href="build/css/app.wp.css" rel="stylesheet">

</head>

<body>

<!-- this Ionic's root component and where the app will load -->

<ion-app></ion-app>

<!-- cordova.js required for cordova apps -->

<script src="cordova.js"></script>

<!-- Polyfill needed for platforms without Promise and Collection support -->

<script src="build/js/es6-shim.min.js"></script>

<!-- Zone.js and Reflect-metadata -->

<script src="build/js/Reflect.js"></script>

<script src="build/js/zone.js"></script>

<!-- the bundle which is built from the app's source code -->

<script src="build/js/app.bundle.js"></script>

</body>

</html>

100 of 161

app.ts

import {Component} from '@angular/core';

import {Platform, ionicBootstrap} from 'ionic-angular';

import {StatusBar} from 'ionic-native';

import {TabsPage} from './pages/tabs/tabs';

@Component({

template: '<ion-nav [root]="rootPage"></ion-nav>'

})

export class MyApp {

private rootPage: any = TabsPage;

constructor(private platform:Platform) {

platform.ready().then(() => {

StatusBar.styleDefault();

});

}

}

ionicBootstrap(MyApp);

101 of 161

about page

import {Component} from '@angular/core';

import {NavController} from 'ionic-angular';

@Component({

templateUrl: 'build/pages/about-page/about-page.html'

})

export class AboutPage {

constructor(private navCtrl: NavController){}

}

<ion-navbar *navbar>

<ion-title>About</ion-title>

</ion-navbar>

<ion-content padding>

<p>

Ionic was founded in 2012, when using web technologies as a means to build native apps was still in its infancy. When we started, we just wanted to create a better way for web developers to use their existing skillsets to build apps for the app stores.

</p>

</ion-content>

102 of 161

about-page.ts

import {Component} from '@angular/core';

import {NavController} from 'ionic-angular';

@Component({

template: `

<ion-navbar *navbar>

<ion-title>About</ion-title>

</ion-navbar>

<ion-content padding>

<p>

...

</p>

</ion-content>

`

})

export class AboutPage {

constructor(private navCtrl: NavController){}

}

103 of 161

about-page.ts

import {Component} from '@angular/core';

import {NavController} from 'ionic-angular';

@Component({

styles: [`

.page1 h2 {

color: red;

}

`],

template: `...`

})

export class AboutPage {

constructor(private navCtrl: NavController){}

}

104 of 161

105 of 161

Angular 1 vs Angular 2

  • bootstrap manuel
  • Filter => Pipe
  • Controllers => Components
  • Directives => Components
  • $scope => Class properties
  • Services => Injectables

106 of 161

Template

  • Bindings :

Your favorite hero is: {{favoriteHero}}

107 of 161

Template

  • Bindings :
  • Pipes :

Your favorite hero is: {{favoriteHero}}

<span>{{movie.title | uppercase}}</span>

108 of 161

Template

  • Bindings :
  • Pipes :
  • Propriétés / Input :

Your favorite hero is: {{favoriteHero}}

<span>{{movie.title | uppercase}}</span>

<img [src]="movie.imageurl">

109 of 161

Template

  • Bindings :
  • Pipes :
  • Propriétés / Input :
  • Evénements / Output :

Your favorite hero is: {{favoriteHero}}

<span>{{movie.title | uppercase}}</span>

<img [src]="movie.imageurl">

<button (click)="toggleMovie()"></button>

110 of 161

Template

  • Bindings :
  • Pipes :
  • Propriétés / Input :
  • Evénements / Output :
  • Double binding :

Your favorite hero is: {{favoriteHero}}

<span>{{movie.title | uppercase}}</span>

<img [src]="movie.imageurl">

<button (click)="toggleMovie()"></button>

<input [(ngModel)]="favoriteMovie" />

111 of 161

Template

  • Bindings :
  • Pipes :
  • Propriétés / Input :
  • Evénements / Output :
  • Double binding :
  • Templates :

Your favorite hero is: {{favoriteHero}}

<span>{{movie.title | uppercase}}</span>

<img [src]="movie.imageurl">

<button (click)="toggleMovie()"></button>

<input [(ngModel)]="favoriteMovie" />

<div *ngIf="movies.length === 0">No movies</div>

112 of 161

Template

  • Bindings :
  • Pipes :
  • Propriétés / Input :
  • Evénements / Output :
  • Double binding :
  • Templates :
  • Local variables :

Your favorite hero is: {{favoriteHero}}

<span>{{movie.title | uppercase}}</span>

<img [src]="movie.imageurl">

<button (click)="toggleMovie()"></button>

<input [(ngModel)]="favoriteMovie" />

<div *ngIf="movies.length === 0">No movies</div>

<video-player #player></video-player>

<button (click)="player.pause()">Pause</button>

113 of 161

Template

  • Bindings :
  • Pipes :
  • Propriétés / Input :
  • Evénements / Output :
  • Double binding :
  • Templates :
  • Local variables :
  • Boucles :

Your favorite hero is: {{favoriteHero}}

<span>{{movie.title | uppercase}}</span>

<img [src]="movie.imageurl">

<button (click)="toggleMovie()"></button>

<input [(ngModel)]="favoriteMovie" />

<div *ngIf="movies.length === 0">No movies</div>

<video-player #player></video-player>

<button (click)="player.pause()">Pause</button>

<ul *ngFor="let movie of movies">

<li>{{movie.title}}</li>

</ul>

114 of 161

$ ionic start todo-app --v2 --ts

115 of 161

Créer une nouvelle page

116 of 161

todo-page.ts todo-page.html todo-page.scss

import {Component} from '@angular/core';

@Component({

templateUrl: 'build/pages/todo-page/todo-page.html'

})

export class TodoPage {

constructor() {}

}

<ion-navbar *navbar>

<ion-title>Todos</ion-title>

</ion-navbar>

<ion-content class="todo-page">

</ion-content>

.todo-page {

.done {

text-decoration: line-through;

}

}

117 of 161

app.ts theme/app.core.scss

import {Component} from '@angular/core';

import {Platform} from 'ionic-angular';

import {StatusBar} from 'ionic-native';

import {TabsPage} from './pages/tabs/tabs';

import {TodoPage} from "./pages/todo-page/todo-page";

@Component({

template: '<ion-nav [root]="rootPage"></ion-nav>'

})

export class MyApp {

rootPage: any = TodoPage;

constructor(platform: Platform) {

platform.ready().then(() => {

StatusBar.styleDefault();

});

}

}

ionicBootstrap(MyApp);

// http://ionicframework.com/docs/v2/theming/

// App Shared Imports

// --------------------------------------------------

// These are the imports which make up the design of this app.

// By default each design mode includes these shared imports.

// App Shared Sass variables belong in app.variables.scss.

@import "../pages/about-page/about-page";

@import "../pages/contact-page/contact-page";

@import "../pages/home-page/home-page";

@import "../pages/scientific-facts-page/scientific-facts-page";

@import "../pages/selected-fact-page/selected-fact-page";

@import "../pages/todo-page/todo-page";

118 of 161

Ionic2 TODO list

import {Component} from '@angular/core';

export class Todo {

constructor(

public name: string,

public done: boolean = false

) {}

}

@Component({

templateUrl: 'build/pages/todo-page/todo-page.html'

})

export class TodoPage {

constructor() {}

}

<ion-navbar *navbar>

<ion-title>Todos</ion-title>

</ion-navbar>

<ion-content class="todo-page">

</ion-content>

119 of 161

Ionic2 TODO list

import {Component} from '@angular/core';

export class Todo {

constructor(

public name: string,

public done: boolean = false

) {}

}

@Component({

templateUrl: 'build/pages/todo-page/todo-page.html'

})

export class TodoPage {

todos: Todo[] = [

new Todo('todo 1'),

new Todo('todo 2'),

new Todo('todo 3')

];

constructor() {}

}

<ion-navbar *navbar>

<ion-title>Todos</ion-title>

</ion-navbar>

<ion-content class="todo-page">

</ion-content>

120 of 161

Ionic2 TODO list

<ion-navbar *navbar>

<ion-title>Todos</ion-title>

</ion-navbar>

<ion-content class="todo-page">

<ion-list>

<ion-item *ngFor="let todo of todos">

{{todo.name}}

</ion-item>

</ion-list>

</ion-content>

121 of 161

Ionic2 TODO list

<ion-navbar *navbar>

<ion-title>Todos</ion-title>

</ion-navbar>

<ion-content class="todo-page">

<ion-list>

<ion-item *ngFor="let todo of todos">

<ion-label>{{todo.name}}</ion-label>

<ion-checkbox [(ngModel)]="todo.done"></ion-checkbox>

</ion-item>

</ion-list>

</ion-content>

122 of 161

Ionic2 TODO list

<ion-navbar *navbar>

<ion-title>Todos</ion-title>

</ion-navbar>

<ion-content class="todo-page">

<ion-list>

<ion-item *ngFor="let todo of todos" [hidden]="todo.done">

<ion-label>{{todo.name}}</ion-label>

<ion-checkbox [(ngModel)]="todo.done"></ion-checkbox>

</ion-item>

</ion-list>

</ion-content>

123 of 161

Ionic2 TODO list

<ion-navbar *navbar>

<ion-title>Todos</ion-title>

</ion-navbar>

<ion-content class="todo-page">

<ion-list>

<ion-item *ngFor="let todo of todos" [hidden]="todo.done">

<ion-label>{{todo.name}}</ion-label>

<ion-checkbox [(ngModel)]="todo.done"></ion-checkbox>

</ion-item>

<ion-item *ngFor="let todo of todos" [hidden]="!todo.done">

<ion-label>{{todo.name}}</ion-label>

<ion-checkbox [(ngModel)]="todo.done"></ion-checkbox>

</ion-item>

</ion-list>

</ion-content>

124 of 161

Ionic2 TODO list

<ion-navbar *navbar>

<ion-title>Todos</ion-title>

</ion-navbar>

<ion-content class="todo-page">

<ion-list>

<ion-item *ngFor="let todo of todos" [hidden]="todo.done">

<ion-label>{{todo.name}}</ion-label>

<ion-checkbox [(ngModel)]="todo.done"></ion-checkbox>

</ion-item>

<ion-item *ngFor="let todo of todos" [hidden]="!todo.done">

<ion-label class="done">{{todo.name}}</ion-label>

<ion-checkbox [(ngModel)]="todo.done" disabled="true"></ion-checkbox>

</ion-item>

</ion-list>

</ion-content>

125 of 161

Ionic2 TODO list

<ion-navbar *navbar>

<ion-title>Todos</ion-title>

</ion-navbar>

<ion-content class="todo-page">

<ion-list>

<ion-item *ngFor="let todo of todos" [hidden]="todo.done">

<ion-label>{{todo.name}}</ion-label>

<ion-checkbox [(ngModel)]="todo.done"></ion-checkbox>

</ion-item>

<ion-item *ngFor="let todo of todos" [hidden]="!todo.done">

<ion-label class="done">{{todo.name}}</ion-label>

<ion-checkbox [(ngModel)]="todo.done" disabled="true"></ion-checkbox>

</ion-item>

</ion-list>

</ion-content>

126 of 161

Ionic2 TODO list

import {Component} from '@angular/core';

@Component({

templateUrl: 'build/pages/todo/todo.html'

})

export class TodoPage {

todos: Todo[] = [

new Todo('todo 1'),

new Todo('todo 2'),

new Todo('todo 3')

];

constructor() {}

}

<ion-navbar *navbar>

<ion-title>Todos</ion-title>

</ion-navbar>

<ion-toolbar>

</ion-toolbar>

<ion-content class="todo-page">

<ion-list>

...

</ion-list>

</ion-content>

127 of 161

Ionic2 TODO list

import {Component} from '@angular/core';

@Component({

templateUrl: 'build/pages/todo/todo.html'

})

export class TodoPage {

newTodo: string = '';

todos: Todo[] = [

new Todo('todo 1'),

new Todo('todo 2'),

new Todo('todo 3')

];

constructor() {}

}

<ion-navbar *navbar>

<ion-title>Todos</ion-title>

</ion-navbar>

<ion-toolbar>

<ion-input type="text" [(ngModel)]="newTodo" placeholder="Nouvelle tâche">

</ion-input>

</ion-toolbar>

<ion-content class="todo-page">

<ion-list>

...

</ion-list>

</ion-content>

128 of 161

Ionic2 TODO list

import {Component} from '@angular/core';

@Component({

templateUrl: 'build/pages/todo/todo.html'

})

export class TodoPage {

newTodo: string = '';

todos: Todo[] = [

new Todo('todo 1'),

new Todo('todo 2'),

new Todo('todo 3')

];

constructor() {}

}

<ion-navbar *navbar>

<ion-title>Todos</ion-title>

</ion-navbar>

<ion-toolbar>

<ion-input type="text" [(ngModel)]="newTodo" placeholder="Nouvelle tâche">

</ion-input>

<ion-buttons end>

<button>

<ion-icon name="send"></ion-icon>

</button>

</ion-buttons>

</ion-toolbar>

<ion-content class="todo-page">

<ion-list>

...

</ion-list>

</ion-content>

129 of 161

Ionic2 TODO list

import {Component} from '@angular/core';

@Component({

templateUrl: 'build/pages/todo/todo.html'

})

export class TodoPage {

newTodo: string = '';

todos: Todo[] = [

new Todo('todo 1'),

new Todo('todo 2'),

new Todo('todo 3')

];

constructor() {}

create(text: string): void {

this.todos.push(new Todo(text));

this.newTodo = '';

}

}

<ion-navbar *navbar>

<ion-title>Todos</ion-title>

</ion-navbar>

<ion-toolbar>

<ion-input type="text" [(ngModel)]="newTodo" placeholder="Nouvelle tâche">

</ion-input>

<ion-buttons end>

<button (click)="create(newTodo)">

<ion-icon name="send"></ion-icon>

</button>

</ion-buttons>

</ion-toolbar>

<ion-content class="todo-page">

<ion-list>

...

</ion-list>

</ion-content>

130 of 161

Ionic2 TODO list

import {Component} from '@angular/core';

@Component({

templateUrl: 'build/pages/todo/todo.html'

})

export class TodoPage {

newTodo: string = '';

todos: Todo[] = [...];

constructor() { }

create(text: string): void { ... }

delete(todo: Todo): void {

const i = this.todos.indexOf(todo);

if(i >= 0) {

this.todos.splice(i, 1);

}

}

}

<ion-navbar *navbar>

<ion-title>Todos</ion-title>

</ion-navbar>

<ion-toolbar>

...

</ion-toolbar>

<ion-content class="todo-page">

<ion-list>

<ion-item *ngFor="let todo of todos" [hidden]="todo.done">

<ion-label>{{todo.name}}</ion-label>

<ion-checkbox [(ngModel)]="todo.done"></ion-checkbox>

</ion-item>

<ion-item *ngFor="..." [hidden]="!todo.done" (click)="delete(todo)">

<ion-label class="done">{{todo.name}}</ion-label>

<ion-checkbox [(ngModel)]="todo.done" disabled="true"></ion-checkbox>

</ion-item>

</ion-list>

</ion-content>

131 of 161

132 of 161

Ionic2 TODO list

import {Component} from "@angular/core";

@Component({

selector: 'todo-item',

template: ``

})

export class TodoItemComponent {

}

133 of 161

Ionic2 TODO list

import {Component, Input} from "@angular/core";

import {Todo} from "./todo-page";

@Component({

selector: 'todo-item',

template: ``

})

export class TodoItemComponent {

@Input() todo: Todo;

}

134 of 161

Ionic2 TODO list

import {Component, Input} from "@angular/core";

import {Todo} from "./todo-page";

@Component({

selector: 'todo-item',

template: `

<ion-item>

<ion-label>{{todo.name}}</ion-label>

<ion-checkbox [(ngModel)]="todo.done"></ion-checkbox>

</ion-item>

`

})

export class TodoItemComponent {

@Input() todo: Todo;

}

135 of 161

Ionic2 TODO list

import {Component, Input} from "@angular/core";

import {Todo} from "./todo-page";

@Component({

selector: 'todo-item',

template: `

<ion-item>

<ion-label [class.done]="todo.done">{{todo.name}}</ion-label>

<ion-checkbox [(ngModel)]="todo.done" [disabled]="todo.done"></ion-checkbox>

</ion-item>

`

})

export class TodoItemComponent {

@Input() todo: Todo;

}

136 of 161

Ionic2 TODO list

import {Component} from '@angular/core';

import {TodoItemComponent} from "./todo-item.component";

@Component({

directives: [TodoItemComponent],

templateUrl: 'build/pages/todo-page/todo-page.html'

})

export class TodoPage {

newTodo: string = '';

todos: Todo[] = [...];

constructor() {}

create(text: string): void {

this.todos.push(new Todo(text));

this.newTodo = '';

}

delete(todo: Todo): void {

const i = this.todos.indexOf(todo);

if(i >= 0) {

this.todos.splice(i, 1);

}

}

}

137 of 161

Ionic2 TODO list

<ion-list>

<ion-item *ngFor="let todo of todos" [hidden]="todo.done">

<ion-label>{{todo.name}}</ion-label>

<ion-checkbox [(ngModel)]="todo.done"></ion-checkbox>

</ion-item>

<ion-item *ngFor="let todo of todos" [hidden]="!todo.done" (click)="delete(todo)">

<ion-label class="done">{{todo.name}}</ion-label>

<ion-checkbox [(ngModel)]="todo.done" disabled="true"></ion-checkbox>

</ion-item>

</ion-list>

138 of 161

Ionic2 TODO list

<ion-list>

<ion-item *ngFor="let todo of todos" [hidden]="todo.done">

<ion-label>{{todo.name}}</ion-label>

<ion-checkbox [(ngModel)]="todo.done"></ion-checkbox>

</ion-item>

<ion-item *ngFor="let todo of todos" [hidden]="!todo.done" (click)="delete(todo)">

<ion-label class="done">{{todo.name}}</ion-label>

<ion-checkbox [(ngModel)]="todo.done" disabled="true"></ion-checkbox>

</ion-item>

</ion-list>

<ion-list>

<todo-item *ngFor="let todo of todos" [todo]="todo" [hidden]="todo.done"></todo-item>

<todo-item *ngFor="let todo of todos" [todo]="todo" [hidden]="!todo.done" (click)="delete(todo)"></todo-item>

</ion-list>

139 of 161

140 of 161

Ionic2 TODO list

import {Injectable} from "@angular/core";

@Injectable()

export class TodoService {

constructor() {};

}

141 of 161

Ionic2 TODO list

import {Injectable} from "@angular/core";

import {Todo} from "./todo-page";

@Injectable()

export class TodoService {

private todos: Todo[] = [

new Todo('todo 1'),

new Todo('todo 2'),

new Todo('todo 3')

];

constructor() {};

getTodos(): Todo[] {

return this.todos;

}

createTodo(text: string): void {

this.todos.push(new Todo(text));

}

deleteTodo(todo: Todo): void {

const i = this.todos.indexOf(todo);

if(i >= 0) {

this.todos.splice(i, 1);

}

}

}

142 of 161

Ionic2 TODO list

import {Component} from '@angular/core';

import {TodoItemComponent} from "./todo-item.component";

import {TodoService} from "./todo.service";

@Component({

directives: [TodoItemComponent],

templateUrl: 'build/pages/todo-page/todo-page.html'

})

export class TodoPage {

newTodo: string = '';

todos: Todo[] = [];

constructor(private todoSrv: TodoService) {

this.todos = todoSrv.getTodos();

}

create(text: string): void {

this.todoSrv.createTodo(text);

this.newTodo = '';

}

delete(todo: Todo): void {

this.todoSrv.deleteTodo(todo);

}

}

143 of 161

Ionic2 TODO list

import {Component} from '@angular/core';

import {Platform, ionicBootstrap} from 'ionic-angular';

import {StatusBar} from 'ionic-native';

import {TabsPage} from './pages/tabs/tabs';

import {TodoPage} from "./pages/todo-page/todo-page";

import {TodoService} from "./pages/todo-page/todo.service";

@Component({

template: '<ion-nav [root]="rootPage"></ion-nav>'

})

export class MyApp {

private rootPage: any = TodoPage;

constructor(private platform:Platform) {

platform.ready().then(() => {

StatusBar.styleDefault();

});

}

}

ionicBootstrap(MyApp, [TodoService]);

144 of 161

145 of 161

Ionic2 Menu

$ ionic start my-app sidemenu --v2 --ts

146 of 161

Ionic2 Menu

<ion-menu [content]="content">

<ion-toolbar>

<ion-title>Pages</ion-title>

</ion-toolbar>

<ion-content>

<ion-list>

<button ion-item *ngFor="let p of pages" (click)="openPage(p)" menuClose>

{{p.title}}

</button>

</ion-list>

</ion-content>

</ion-menu>

<ion-nav [root]="rootPage" #content></ion-nav>

@Component({

templateUrl: 'build/app.html'

})

class MyApp {

@ViewChild(Nav) nav: Nav;

rootPage: any = GettingStartedPage;

pages: Array<{title: string, component: any}> = [

{title: 'Getting Started', component: GettingStartedPage},

{title: 'List', component: ListPage}

];

constructor(private platform: Platform) {}

openPage(page) {

this.nav.setRoot(page.component);

}

}

ionicBootstrap(MyApp);

147 of 161

Ionic2 Menu

<ion-menu [content]="content">

<ion-toolbar>

<ion-title>Pages</ion-title>

</ion-toolbar>

<ion-content>

<ion-list>

<button ion-item *ngFor="let p of pages" (click)="openPage(p)" menuClose>

{{p.title}}

</button>

</ion-list>

</ion-content>

</ion-menu>

<ion-nav [root]="rootPage" #content></ion-nav>

@Component({

templateUrl: 'build/app.html'

})

class MyApp {

@ViewChild(Nav) nav: Nav;

rootPage: any = GettingStartedPage;

pages: Array<{title: string, component: any}> = [

{title: 'Getting Started', component: GettingStartedPage},

{title: 'List', component: ListPage}

];

constructor(private platform: Platform) {}

openPage(page) {

this.nav.setRoot(page.component);

}

}

ionicBootstrap(MyApp);

148 of 161

Ionic2 Menu

<ion-menu [content]="content">

<ion-toolbar>

<ion-title>Pages</ion-title>

</ion-toolbar>

<ion-content>

<ion-list>

<button ion-item *ngFor="let p of pages" (click)="openPage(p)" menuClose>

{{p.title}}

</button>

</ion-list>

</ion-content>

</ion-menu>

<ion-nav [root]="rootPage" #content></ion-nav>

@Component({

templateUrl: 'build/app.html'

})

class MyApp {

@ViewChild(Nav) nav: Nav;

rootPage: any = GettingStartedPage;

pages: Array<{title: string, component: any}> = [

{title: 'Getting Started', component: GettingStartedPage},

{title: 'List', component: ListPage}

];

constructor(private platform: Platform) {}

openPage(page) {

this.nav.setRoot(page.component);

}

}

ionicBootstrap(MyApp);

149 of 161

Ionic2 Menu

<ion-menu [content]="content">

<ion-toolbar>

<ion-title>Pages</ion-title>

</ion-toolbar>

<ion-content>

<ion-list>

<button ion-item *ngFor="let p of pages" (click)="openPage(p)" menuClose>

{{p.title}}

</button>

</ion-list>

</ion-content>

</ion-menu>

<ion-nav [root]="rootPage" #content></ion-nav>

@Component({

templateUrl: 'build/app.html'

})

class MyApp {

@ViewChild(Nav) nav: Nav;

rootPage: any = GettingStartedPage;

pages: Array<{title: string, component: any}> = [

{title: 'Getting Started', component: GettingStartedPage},

{title: 'List', component: ListPage}

];

constructor(private platform: Platform) {}

openPage(page) {

this.nav.setRoot(page.component);

}

}

ionicBootstrap(MyApp);

150 of 161

Ionic2 Menu

<ion-menu [content]="content">

<ion-toolbar>

<ion-title>Pages</ion-title>

</ion-toolbar>

<ion-content>

<ion-list>

<button ion-item *ngFor="let p of pages" (click)="openPage(p)" menuClose>

{{p.title}}

</button>

</ion-list>

</ion-content>

</ion-menu>

<ion-nav [root]="rootPage" #content></ion-nav>

@Component({

templateUrl: 'build/app.html'

})

class MyApp {

@ViewChild(Nav) nav: Nav;

rootPage: any = GettingStartedPage;

pages: Array<{title: string, component: any}> = [

{title: 'Getting Started', component: GettingStartedPage},

{title: 'List', component: ListPage}

];

constructor(private platform: Platform) {}

openPage(page) {

this.nav.setRoot(page.component);

}

}

ionicBootstrap(MyApp);

151 of 161

Ionic2 Menu

<ion-menu [content]="content">

<ion-toolbar>

<ion-title>Pages</ion-title>

</ion-toolbar>

<ion-content>

<ion-list>

<button ion-item *ngFor="let p of pages" (click)="openPage(p)" menuClose>

{{p.title}}

</button>

</ion-list>

</ion-content>

</ion-menu>

<ion-nav [root]="rootPage" #content></ion-nav>

@Component({

templateUrl: 'build/app.html'

})

class MyApp {

@ViewChild(Nav) nav: Nav;

rootPage: any = GettingStartedPage;

pages: Array<{title: string, component: any}> = [

{title: 'Getting Started', component: GettingStartedPage},

{title: 'List', component: ListPage}

];

constructor(private platform: Platform) {}

openPage(page) {

this.nav.setRoot(page.component);

}

}

ionicBootstrap(MyApp);

152 of 161

Pour tester ce weekend...

153 of 161

Pour tester ce weekend...

154 of 161

Pour tester ce weekend...

155 of 161

Pour tester ce weekend...

156 of 161

Pour tester ce weekend...

157 of 161

Pour tester ce weekend...

158 of 161

Pour tester ce weekend...

159 of 161

Pour tester ce weekend...

160 of 161

161 of 161

Questions ?

@loicknuchel