The rise of web developers
@loicknuchel
?
Loïc Knuchel
Geek passionné
Freelance
Développeur web full-stack
Entrepreneur
Cookers / SalooN
L’histoire du smartphone
Juin 2007
Lancement de l’iPhone 1
Septembre 2008
Sortie du HTC G1
avec Android
La WebView
Idée
Coder des applications
entièrement dans la WebView
Idée
Coder des applications
entièrement dans la WebView
Avantages :
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)
Oups...
Oups...
Oups...
Mauvais support des standards du web dans la WebView
Peu d’outils / librairies
Téléphones peu puissants
Oups...
Mauvais support des standards du web dans la WebView
Peu d’outils / librairies
Téléphones peu puissants
Oups...
Mauvais support des standards du web dans la WebView
Peu d’outils / librairies
Téléphones peu puissants
Souvent pour des projets à petit budget...
Aujourd’hui
Native develoment is ...
way ...
Performance
Code re-write
Complicated
Android Menu (Nav Drawer)
...
@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);
}
}
...
<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;
}
}
}
<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;
}
}
}
App Store installs are broken !
App Store installs are broken !
?
App Store installs are broken !
?
App Store installs are broken !
?
App Store installs are broken !
App Store installs are broken !
App Store installs are broken !
Ionic c’est quoi ?
+
=
Ionic c’est quoi ?
+
=
Stack technologique
Natif
Web
Téléphone & APIs natives
Cordova : webview +
JavaScript bridges
Angular
Ionic
Application
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
DX
#Platform continuity
DX
#Platform continuity
#CLI
DX
#Platform continuity
#CLI
#Ionic Market
DX
#Platform continuity
#CLI
#Backend services
#Ionic Market
DX
#Platform continuity
#CLI
#Backend services
#Push
#Deploy
#Package
#Auth
#Analytics
#Ionic Market
DX
#Platform continuity
#CLI
#Backend services
#Push
#Deploy
#Package
#Auth
#Analytics
#Ionic View
#Native plugins
#Ionic Market
#Ionic Creator
@ionitron
Dév web = Dév mobile
Ionic is a complete ecosystem !
One more thing...
Hybrid apps have superpowers...
Hybrid superpowers
Hybrid superpowers
Web App :
Hybrid superpowers
Web App :
100 %
Hybrid superpowers
Web App :
Progressive Web App :
Hybrid superpowers
Web App :
Progressive Web App :
Native App :
Hybrid superpowers
App Store deployment is broken !
Apple : ~ 2 jours
Google : ~ 2 heures
Hybrid superpowers
Nouveautés depuis Ionic 1
Getting started
Install nodejs & npm
Getting started
Install nodejs & npm
$ npm install -g ionic@beta
Getting started
Install nodejs & npm
$ npm install -g ionic@beta
$ ionic start demoApp --v2 --ts
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! ♬ ♫ ♬ ♫
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! ♬ ♫ ♬ ♫
Getting started
Install mobile sdk (Android ou iOS)
Getting started
Install mobile sdk (Android ou iOS)
$ sudo npm install -g cordova
Getting started
Install mobile sdk (Android ou iOS)
$ sudo npm install -g cordova
$ ionic platform add android
Getting started
Install mobile sdk (Android ou iOS)
$ sudo npm install -g cordova
$ ionic platform add android
$ ionic run android
Structure de fichiers
Structure de fichiers
Structure de fichiers
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>
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>
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>
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>
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);
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>
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){}
}
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){}
}
Angular 1 vs Angular 2
Template
Your favorite hero is: {{favoriteHero}}
Template
Your favorite hero is: {{favoriteHero}}
<span>{{movie.title | uppercase}}</span>
Template
Your favorite hero is: {{favoriteHero}}
<span>{{movie.title | uppercase}}</span>
<img [src]="movie.imageurl">
Template
Your favorite hero is: {{favoriteHero}}
<span>{{movie.title | uppercase}}</span>
<img [src]="movie.imageurl">
<button (click)="toggleMovie()"></button>
Template
Your favorite hero is: {{favoriteHero}}
<span>{{movie.title | uppercase}}</span>
<img [src]="movie.imageurl">
<button (click)="toggleMovie()"></button>
<input [(ngModel)]="favoriteMovie" />
Template
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>
Template
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>
Template
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>
$ ionic start todo-app --v2 --ts
Créer une nouvelle page
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;
}
}
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";
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
Ionic2 TODO list
import {Component} from "@angular/core";
@Component({
selector: 'todo-item',
template: ``
})
export class TodoItemComponent {
}
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;
}
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;
}
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;
}
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);
}
}
}
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>
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>
Ionic2 TODO list
import {Injectable} from "@angular/core";
@Injectable()
export class TodoService {
constructor() {};
}
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);
}
}
}
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);
}
}
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]);
Ionic2 Menu
$ ionic start my-app sidemenu --v2 --ts
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);
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);
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);
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);
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);
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);
Pour tester ce weekend...
Pour tester ce weekend...
Pour tester ce weekend...
Pour tester ce weekend...
Pour tester ce weekend...
Pour tester ce weekend...
Pour tester ce weekend...
Pour tester ce weekend...
Questions ?
@loicknuchel