From the Ionic framework to market(sort of) part 1 && 2
Sk
Posted on August 12, 2021
Ionic is awesome and easy to learn, especially if you have experience with other frameworks and libraries like react, angular or vue, with that said I mean a little experience with Ionic is a prerequisite to follow along “successfully”, although I will try to explain everything as much as I can.
I will break these articles into parts, hopefully by the end we will have a working Hybrid mobile app and a website/webapp, both interfacing with supabase.
We will create a Template editor(some form of IDE for creating and managing newsletters, email like templates) and a mobile app to “visualize” the templates. Usually newsletters are sent via emails and look designed and stuff, well most of them. What we are going to do is remove email as a middleman create an editor and a mobile app to "interpret" the templates into HTML and render them for the user.
To explain the “well sort of” in the title, the reason I am creating this application is to pitch it to a certain organization to kinda score points or even a job opportunity, I noticed they do not have a mobile app, also they send out newsletters and emails like everyday, so this app will probably make their lives easier. so the market cannot be google play, however there are great tutorials out there covering this topic, we will build a signed apk etc, but not publish it.
The plan:
First the Editor(for the admin) which we will probably host on netlify:
features: create newsletter templates, save drafts, publish to supabase.
You can also apply for jobs in this particular organization, and they answer everyone by emails, we could add a job board also, along the editor so users can apply thru the mobile app, also get responses, because this is a prototype we really do not need authentication, but we will see.
Mobile app(for users):
features: pull new newsletters from supabase(Probably add comments section), check job application status.
we will separate the two which is kinda weird because it defeats the reason for ionic: which is one code base all platforms thingy, however i have a reason for it, to learn a tool(s) by building, I thought why not learn vue and supabase while doing this, cause this could go south, so why not come out with something new out of it, plus we will both learn how to learn thru building, I am also aware you can use vue with ionic so why separate them, well I learned this the hard way:
I once built a mobile app with ionic and angular with few of my friends called Money Manager, published it, with many features, for our first full blown app, at this point I thought I pretty much knew angular put it on my linkedin description, long story short was approached asked to show my angular skills, needless to say it was a disaster pure angular feels so much different compared to angular wheeled by ionic, hence learning a framework in it's pure form is the best way to learn it, because ionic brings it's own stuff and the framework is sort of enhanced by ionic.
Another reason: we will be using the Editorjs module which comes with a lot of plugins and the mobile app does not need them at all, so combining the two will make the apk large, there is absolutely no overlap between the editor and mobile app. With that said let's start.
Quick Vue review/intro
I am assuming you are aware what components are, maybe from the use of react, I am not going to be explaining what components are, if we start from the beginning these articles will be very long, so I suggest before you continue: having basic knowledge of the mentioned, or just the basics of react which will be good cause we will use react in the mobile app, so in short if you are an absolute beginner this will probably go over your head, so i suggest taking a basic react tut.
The .vue file
<template>
<div>
<button @click="sayHello"></button>
</div>
</template>
<script>
// I will refer to the below {} as exported object
export default {
name: "Hello",
props: {
msg: String
},
methods: {
sayHello: function() {
console.log(`hello ${this.msg}`)
}
}
}
</script>
<style scoped>
</style>
our markup goes in the template tag(just normal HTML mark up), so do our JavaScript, and styles in their respective tags
Template tag - Vue like any other frameworks/libs has "it's own" binding and directive styles, for example binding the click event to a button with @click, creating a controlled element(element bound to a value) with v-bind, conditional rendering with v-if etc, there is nothing really magical especially if you come from the react world or angular with the ng directives.
Script tag - the logic, functionality and data related to the component lives here, in the exported object. Methods, props, name of the component, data etc are defined inside this object, maybe there are variations but as far as I can tell, most things happen in this object, we could cover them all but since this post is not about vue, rather an intro we will cover what is relevant, in terms of imports they are outside the exported object.
name - is used to identify the component, when it is exported
props - is an object with properties that must be passed when using the component, and it seems types are allowed in a vue file, although using JavaScript, so far props are accessible directly without this.msg
in any methods as seen in the say hello method.
methods - functions defined here can be accessed by the template, to access any property inside the object you use the
this
keyword as usual(which refers to the current object you are in)
Style tag - scoped means the style is only limited to the current component, and will not apply outside the component,
Importing the component
remember the name property in the exported object, it is the identifier for the component, to import any component to others, we use the name given to the component in the exported object, e.g
import Hello from "./components/Editor.vue"; // in the script tag
// then we can use Hello as a component in the template
//msg here is the prop we difined in the Hello component
<Hello msg="world"/> // template
The generated app (with @vue/cli)
Now I think we can easily follow the flow of the generated app, when I approach a new framework or module I always look for the entry point and follow the flow, in vue its main.js. which looks like this:
file : main.js
// createApp mounts a component to an HTML element (in index.html)
import { createApp } from "vue";
// importing App component from app.vue
import App from "./App.vue";
// mounting App component(or generated element(s) to an element on our index.html with the id app) which is a div in this case
// simply mounting app component to the div in index.html
createApp(App).mount("#app");
file: App.vue (imported in main.js)
basically this is the entry component mounted in main.js, the root component
<template>
<Ed msg="Editor" />
</template>
<script>
// custom component for the Editor we will create
import Ed from "./components/Editor.vue";
export default {
name: "App",
components: {
Ed,
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* text-align: center; */
color: #2c3e50;
margin-top: 60px;
}
</style>
I changed it a bit, imported our custom Ed component, which is the Editor, you can have as many components in the component property, which in turn you can use in the template, for now we will only have one, the Editor component, name: Ed
File: Editor.vue (create this file in the components folder)
we will leave the msg prop, just as an excuse to use props early on, to get use to them.
file: Editor.vue
<template>
// div to append the editor later on
<div id="editor">
<!-- usage of the prop -->
h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
name: "Ed",
//props the component receives
props: {
msg: String,
},
}
</script>
<style scoped>
</style>
and now we begin, we will work on the editor first, then the preview window. When i work on personal projects I generally follow a simple pattern, functionality first, design last because it's super easy to get lost in the design details, so our goal is getting a working prototype.
Coding The Editor
Editorjs is simple, extensible and powerful at the same time, we will develop our own plugin also. We are in the editor.vue file, I will not show the above snippets, .... dots mean the previous code , I will only show new code, inside a dedicated tag
<script>
import Editorjs from "@editorjs/editorjs"
export default {
....
methods: {
initEd: function(){
/**
* @type {EditorJS}
*/
window.editor = new Editorjs({
holder: "editor",
onready: function(){
console.log(window.editor)
}
})
}
}
}
</script>
Firstly we are importing Editorjs which we installed along the plugins earlier, and then inside our "exported object" in methods we define an initEd method - to initialize the editor,
if you are not using JavaScript or an IDE you can ignore this line:
/**
* @type {EditorJS}
*/
this tells visual studio code the type of the following variable, which in our case window.editor, this is for code completion.
Here we are initializing a new editor object and storing it in the global window object(you can also store it as a property of the exported object, so you can only access it in that scope)
window.editor = new Editor()
Editor() takes an object as a param, where we pass options or configuration for the new created editor
Editor({
holder: "editor", // Id of the HTLM element to append the created editor
onready: function(){
// called when the Editor is ready
}
})
We've got a simple setup of the editor, however we need to initialize it, under initEd method let's create a new function mount that will call the initEd function, we can of course call initEd directly but we are planning for the future, let's say maybe one day we want to do something before initializing our editor, we can place that logic into mount, separate from initEd logic which actually setups the editor, it is good practice to separate functionality to separate methods
methods: {
initEd: function(){
.....
},
mount: function() {
this.initEd(); // calling initEd
}
}
Vue also has life cycle methods, methods that are fired based on the component state, for example when the component has finished mounting, which we will use to mount our editor.
Mounted is one of those cycle methods, place it outside our methods and call mount
export dafault {
methods: {
.....
},
mounted(){
this.mount()
}
}
And now we have The editor, if you open the console you will see a message saying The Editor is ready and if you click anywhere on the div with id editor, the Editor will focus. if you are aware of jupyter notebooks, the Editor is like that, but for editing HTML, so we can create templates.
It's a bit hard to see the Editor because everything is white, let's change the background of our page or simply the border of our Editor holder which I will do, you can change it to any color, it is temporary so we can see what we are working with
In Editor.vue style tag :
#editor{
width: 80%; // width of the editor
border: solid 3px lightblue;
}
in the template tag put the Editor holder inside a div with class container
<div class="container">
<div id="editor">
<!-- <h1>{{ msg }}</h1> --> // the msg prop commented out we do need it yet
</div>
</div>
center the Editor
.container {
display: flex;
justify-content: center;
}
With this done you should be able to see the editor, with the border color of your choice
When you click on the Editor you should see on your left side a plus icon, on the other side a square icon thingy, the plus icon is used to select a tool, if you think of any editor you know let's say gimp, krita etc, they have drawing tools and selecting a tool determines how the editor will behave, think of it as an HTML element, which determines what content goes in the active block(we will setup tools don't worry)
active block means selected block which is between the plus and square icon
Quick tip: if you do not see changes automatically in the Editor when you change code, refresh, i think it has to do with how the mounted method is called and the refresh after doing changes!
when you click the plus icon a toolbar of this sort will appear(which we will setup next), as you can see we have text tool(for writing text), Header, Table etc, the tool you choose will determine what the behavior of the current block(between plus and square icon) will be( a table, Header etc)
after tool selection, the square icon is used to make variations to the block for the selected tool, let's say you select the Header tool, clicking the square will show all available header types from H1 to 6, this square icon is also used to move the block up or down if there are many blocks, or delete a block.
okay enough about the Editor for now, let's setup the tools, create a new Folder called utilities and inside create a file Tool.js | ts if you chose typescript (for typescript you have to create the env.d.ts file I believe and declare all the tools we are about to import because they do not have types)
Tools
import Checklist from "@editorjs/checklist"
import Delimeter from "@editorjs/delimiter"
import Header from "@editorjs/header"
import List from "@editorjs/list"
import Marker from "@editorjs/marker"
import Quote from "@editorjs/quote"
import Raw from "@editorjs/raw"
import Table from "@editorjs/table"
import Warning from "@editorjs/warning"
export const tools = {
Checklist,
Delimeter,
Header,
List,
Marker,
Quote,
Raw,
Table,
Warning
}
Import the tools in the Editor.vue file in the script tag
<script>
import {tools} from "../utillity/Tools"
export default {
...
}
<script>
Then hook up the tools to the editor in the setup object, just after holder add this line
window.editor = new EditorJs({
...
tools: tools,
...
})
If you refresh and use the Editor, now you will see all the tools hooked up and working, play with it a little bit and get use to it, because we will create our own tool later on, there are also great tools via npm
Ok lastly at least for part 1, let's add a way to save the template(Editor) data,
The data is an object representing the template order, type of elements, their data and so on
we will use this data in the mobile app to rebuild the template, when you really think about it, its a very efficient way to transfer visual data: looking at size compared to transferring actual components.
add a button, no need to style it, to save(console) the data from the editor
<template>
<div class="container">
<button @click="save">Save</button>
</div>
...
</template>
also add padding to the container class
.container{
...
padding: 1em;
}
add the save method, inside methods object
methods: {
...,
save: function(){
window.editor.save().then((data)=> {
console.log(data, "saved succesfully")
})
},
}
We call the editors save method which returns a promise, upon resolve returns the blocks data as we pass it in our callback
fill the editor with dummy data, click save, in the console you will see the Editor represented as an object, this is the data we will pass around, to rebuild our UI, take a look at it, it's very easy to understand, we more interested in the blocks property
Coming up next
- we will create our custom plugin an image tool
- a simple express local server to establish communication between our editor and ionic app(for rapid development and testing) - the server will pass block data
- setup the ionic app
questions or want to to say hi, the best way is twitter:
Part 2
Custom Plugin
Image plugin
create a new plugins folder in the src directory, and inside plugins create image.js || ts depending on what you are using, also create image.css
src/
plugins/
image.js || image.ts
image.css
In image.js:
import the css file and we also need an icon for our tool, we will use an open source image icon(svg)
import "./image.css";
const icon =
`<svg version="1" xmlns="http://www.w3.org/2000/svg" width="17" height="15" viewBox="0 0 48 48" enable-background="new 0 0 48 48">
<path d="M40,41H8c-2.2,0-4-1.8-4-4V11c0-2.2,1.8-4,4-4h32c2.2,0,4,1.8,4,4v26C44,39.2,42.2,41,40,41z"/>
<circle fill="#fff" cx="35" cy="16" r="3"/>
<polygon fill="#000" points="20,16 9,32 31,32"/>
<polygon fill="#eee" points="31,22 23,32 39,32"/>
</svg>
we can import css directly in our code files because the code get's transpiled, and some transpilers understand the css import and are able to work with it.
now we done with setting up, create an exported class(we will import it in our tools.js)
export default class Image{
static get toolbox(){
return {
name: "Image",
icon
}
}
}
A static method is a method of a class that can be accessed and called without instantiating the class, and cannot be accessed in instances, but only thru the class name, to access toolbox :
Image.toolbox() // static method
this method is called by the editor to get information about the tool, as you can see toolbox returns an object with name and icon of our custom image tool, which will be rendered in the Editor, let's hook up our image tool, note you can change the fills in the svg to your liking, I chose random colors, we will size the svg later,
in the Tools file :
...
import Image from "../plugins/Image"
export const tools{
...,
Image
}
we are importing the Image tool and adding it to the const tools, if you refresh the Editor and open tools you will see an extra tool, if you click it thou nothing will happen because we have not told the Editor what to do with it on click, we only told it, basically how to render it in the tools section not in the block, for that we need a render method, let's go back to our plugin file and add a render method, let's render the classic hello world, note a constructor function runs on object creation(executing the class)
export default Image{
div = document.createElement("div")
/**
* @type {HTMLHeadingElement}
*/
h1 = document.createElement("h1")
constructor(){
this.h1.innerText = "Hello World"
this.div.appendChild(this.h1)
}
static get toolbox{
...
}
//new
render(){
return this.div;
}
}
I believe this is self explanatory, we create a div and h1 element and assign them to global vars of the class, in the constructor we assign a text of hello world to the h1 element and append h1 to the div element, and most importantly we return the div in the classes render method, this tells the Editor what to render when the image tool is clicked, in this case the div with h1 of hello world, if you test the tool now, you will see hello world displayed in the block
If you try to save, you'll see an error, because we have not told the Editor what to do with the block of "type our tool", we have to define what to do on save
export default Image{
...
constructor(){
...
}
static get toolbox{
...
}
render(){
...
}
save(blockContent){
let h1 = blockContent.firstElementChild;
// data to be saved on saving the editor data
return {
text: h1.innerText
}
}
}
In the save method we have the param of blockContent, which equals the element returned in the render method, in this case the div element, and we access the div's first child and return an object(which is our data) with the text of h1 and the key of text, this object will be saved in the blocks data
{
"time": 1628364784438,
"blocks": [
{
"id": "6eiD2XG4CS",
"type": "Image",
"data": {
"text": "Hello World"
}
}
],
"version": "2.22.2"
}
simple as that we have a full working tool, you can do a lot with this simple idea, you can create an entire webpage and return it in the render method, now that we have the basics of creating a custom tool, clear everything and leave the relevant methods (constructor, toolbox, render and save), to create the image tool, I assume now you have the basics of tool creation covered, so I will go a bit faster(meaning less explanation) we merely repeating the above process but creating an image element.
Image tool
creating elements & setting up public(class variables)
export default class Image{
// to construct the image holder
div = document.createElement("div")
input = document.createElement("input");
img = document.createElement("img")
label = document.createElement("label");
paragraph = document.createElement("p");
// handle data
data = undefined;
file = undefined;
base64Img = undefined
constructor(data){
}
}
First we will use the input type file to select the image, we will only support local image selection(mainly to avoid CORS policy which can frustrate the user), you can implement online retrieval of the image, for now we will use input type file,
the data param in the constructor, is used when you pass data to the editor before initialization, for things like editing etc, we will handle it in the upcoming parts ignore for now.
export default Image{
...
constructor(data){
// adding classes to the elements(classes will be difined in the imported css)
this.div.classList.add('img__container')
this.input.classList.add("file")
this.paragraph.classList.add("file-name")
// making the input of type file
this.input.type = "file";
// label for file input
this.label.htmlFor = "file";
this.label.textContent = "Select Image";
}
}
Building the elements structure(Putting the pieces together in the div)
export default Image{
...
constructor(data){
...
this.div.appendChild(this.input)
this.div.appendChild(this.label)
this.div.appendChild(this.img)
// binding a click event to the label(Select Image) on click it will trigger
// and open file selector (for img selction)
this.label.onclick = () => {
this.input.click();
}
// handling file select
this.input.onchange = (event)=> {
}
}
}
On file change:
export default Image{
...
constructor(data){
...
this.input.onchange = (event)=> {
// getting the selected file from the input element
const file = this.input.files[0];
// creating a file reader object(we are using a file reader so it will turn the //image to base64 for easy storage and transport)
let reader = new FileReader()
// read data as a blob
reader.readAsDataURL(this.input.files[0])
// when the reader finishes reading the file
// asign the result to img.src and public base64img var for use in saving stage
reader.onloadend = (ev) => {
// console.log(typeof ev.target.result)
this.img.src = ev.target.result
this.base64Img = ev.target.result
}
}
}
Updating render() to return the correct div and save() also
export default class Image{
...
constructor(){
...
}
static get toolbox(){
...
}
render(){
// the div which we appended the img element
return this.div
}
save(blockContent){
// saving the base64
return {
img: this.base64Img
}
}
}
If you test the image tool now it should be working able to select an image and save, however there are no styles yet, let's add them
styling the file element: the file element is pretty hard to style, the css I am about to give you I learned from stack-overflow a long time ago, I don't really remember the post, if I do I will reference. and note I am not going to be explaining css, if I start doing that these post will be very long, as you have noticed already I can't help but explain everything, so I'll just give you the css, maybe in the future I can do like an intermediate css thingy or post
so open the image.css file:
input{
width: 100%;
border: 3px solid aquamarine;
}
// hiding the ugly native file element
.file {
opacity: 0;
width: 0.1px;
height: 0.1px;
position: absolute;
}
// i chose flex dir column(for adding elements later under the img, maybe captions etc)
.img__container{
display: flex;
flex-direction: column;
align-items: center;
margin: 1em 0;
}
//making sure the img is confined within the parent element
.img__container img {
width: 100%;
height: auto;
}
// the custom (Select Image) label that triggers file select
.img__container label{
display: block;
position: relative;
width: 200px;
height: 50px;
border-radius: 25px;
background: linear-gradient(40deg, #ff6ec4,#7873f5);
box-shadow: 0 4px 7px rgba(0, 0,0, 0.4);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-weight: bold;
cursor: pointer;
transition: transform .2s ease-out;
padding: .5em;
margin: .4em;
}
// moving stuff around
input:hover + label,
input:focus + label
{
transform: scale(1.02);
}
input:focus + label {
outline: 1px solid #000;
outline: -webkit-focus-ring-color auto 2px;
}
.file-name {
position: absolute;
bottom: -50px;
left: 10px;
font-size: 0.85rem;
color: #555;
}
img {
margin-top: 50px;
}
That's all for css, the tool on select now should present a select Image button, which on select will open file dialogue, on image select, the image will be displayed under the button, on save, the image base64 will be saved under blocks
With that we are done with the Image tool for now.
Let's put a stop on the editor for now and setup the server and start the ionic app, the next part will be dedicated to finishing the editor (implementing template create, edit, delete, draft, publish etc)
Simple server
In terms of backend I am new too, but don't worry I will explain everything, I did experiment with express a bit, it is simple, except the middleware part, including the CORS policies etc, and it is really something I don't want to learn right now, so a work around I used json-server to implement a server(one of the few tricks I learned from the Hong Kong uni Fron-tend Coursera course), json-server as the name says serves json files, but also allows creation of servers from "scratch", and the good news is: it setups the middleware for us, it is super awesome and a great tool to know,
create a new folder somewhere to hold our server files, and install json-server I am hoping you know how to init an npm project and install packages, so I won't go over it
This is a node project: nodejs and the browser are two different environments, but you don't need to know a lot about node
Server setup
create a main.js file or index whatever you want to call it and import json-server
// using require cause it's node
const jsonServer = require("json-server")
If you got this far in the articles I assume you have heard of servers or even have a basic knowledge of what they are, routes etc, if not please acquaint yourself, a 5 minute video on YouTube will suffice. but shortly the server acts as a middle man for front-end applications whether to fetch correct data, authentication, communication(of which we are doing, linking our web app with the ionic app to pass block data and test in real time), think of it as a way to establish communication between two different worlds and knows both of them, it's job to is broker between the two,
Think of a front-end app that depends on a database, and constantly fetches data, we could introduce a server as a middleman, with logic, now the front-end talks to the server the server does a little bit more than fetching data for example sifting data and returning relevant data to the front-end, cleaning data, caching data so not to tax the database etc
setup
...
//since this is json server it can write to a json file( database)
// which we will not do, but pass it as a param of router so you know it can
// you can also pass an empty object to create an in memory DB instead of a json file
const router = jsonServer.router("db.json")
// middlewares involves setting cross origin, cors etc without them we cannot communicate with the server from a different ip(CROSS ORIGIN policy) for security reasons so the server must "allow cross orgin"
const middlewares = jsonServer.defaults()
// this underneath simply creates an express app
const server = jsonServer.create()
json-server underneath is so cool to explore, so I encourage you to look at the source code it's a great learning experience, or maybe in the future I will do an article doing just that and possibly going under nedb also, that would be cool, to look at the source, any way.
...
// telling the server(express app to use middlewares)
server.use(middlewares)
from here on you can use the normal methods (get, put, delete etc),
we will implement more methods as we go, for now these are sufficient
...
// get all templates
server.get('/home', (req, res) => {
})
// get specific template
server.get("/temp/:id", (req, res)=> {
})
// add a template
server.post("/temp/new/:data", (req, res)=> {
} )
in the routes "/temp/:id" :id means it's a parameter we can pass
finally
server.use(router)
// important bodyParser allows use of POST, PATCH etc
// so we need to tell our server to use a bodyParser
server.use(jsonServer.bodyParser)
// i believe this is used by the db, which we will not use
// avoiding unecessary complexity, we will store our templates in a map object
server.use((req, res, next) => {
if (req.method === 'POST') {
req.body.createdAt = Date.now()
}
// Continue to JSON Server router
next()
})
//finally start the server and listen at port 3000
server.listen(3000, ()=> {
console.log(`listening on port 3000`)
})
Req and Res are objects handling incoming request and sending response respectively
for example to get parameters sent by the client you can use req.params, to send message back to the client, you can use res.send
an example update the /home get method to send a simple response back:
// if the client visits this path, respond with the object
server.get("/home", (req, res)=> {
res.jsonp({ user: 'sk' });
})
start the server in node
node main.js
if you did everything correctly you should see
listening on port 3000
In a webpage's js file or even a web console:
fetch("http://localhost:3000/home").then(res => {
console.log(res.text().then(data => console.log(data)))
})
you should see the response logged.
This is the basic skeleton of the server, we will come back to it, as the need arises
Ionic
I assume you have basic knowledge of ionic, but if you don't it's okay I think ionic is one of those frameworks you can easily pick up, I will use Ionic with react, however you don't have to, but I do recommend you do, so you can follow along smoothly especially if you are new to ionic, you can use angular or vue, I chose react cause I am more familiar and want to work a bit faster, vue has already slowed things down, as I am still learning
In different folder start a new ionic project, I usually start my ionic projects using npx so i can get the latest updates, you can install the ionic cli globally and start from there, it's super easy and well documented
npx ionic start [name of your app]
you will be prompted to choose preferred framework and a starter template, for the starter template choose sidemenu,
if they ask you: do you want to enable capacitor choose yes, capacitor is the framework or bridge between the webview and native apis, it's how we are basically able to run native tasks or apis using web tech
if you installed the ionic cli globally omit npx in all the commands followed by ionic
we will only be building for android, however you can add ios, to add a platform you use capacitor, we will not focus on native platforms a lot because we do not need native plugins yet, we will test everything in the browser for now, we are just setting up platforms for now so we can build an apk later on.
npx ionic cap add android
cap stands for capacitor, you can add ios the same way
lastly run
ionic s -l
meaning ionic serve lab
lab is a phone looking webpage so you can basically see what the app will look like in a phone, it is not an emulator! but something of this sort, I am aware that Firefox has something of this sort but please allow the install of lab, it is way more accurate, and accounts for all phones sizes going up, I tried using Firefox only in one of my projects, on a mobile it did not look as expected
That is all for ionic setup, I am tempted to go over the app, but no I will dedicate an article for it, after the next one dedicated to the Editor, in the mean time you can acquaint yourself with the basics of ionic
until then you can close the ionic app, we are done with setting up, now we code.
brace yourself we are about to move faster now, and implement more cool stuff, and if you are reading this thank you for coming back,
Upcoming: The Editor
questions or want to to say hi, the best way is twitter:
Part 3 && 4 is coming soon, maybe in two or three days time
Posted on August 12, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 29, 2024