All notes
Meteorjs

Basics

Start with React

guide.meteor.com: react.


# To install meteorjs:
curl https://install.meteor.com/ | sh

meteor create simple-todos
cd simple-todos
meteor # Run the app.
# -p, --port
meteor -p 3000

meteor npm install --save react react-dom

Static files

so: how to serve static content.

Just put the files under your public directory. If you add the file "myApp/public/images/kitten.png", you can access it from your templates like:


<img src="/images/kitten.png">

How to add 3rd party JS lib

forums.meteor.com.

The solution that is working is placing the js file in public/js then using $.getScript in the template’s onCreated.

Commands

debug

The server process will be suspended just before the first statement of server code that would normally execute.

In order to continue execution of server code, use either the web-based Node Inspector or the command-line debugger.

Breakpoints can be set using the debugger keyword, or through the web UI of Node Inspector (“Sources” tab).

The server process debugger will listen for incoming connections from debugging clients, such as node-inspector, on port 5858 by default. To specify a different port use the --debug-port option.

Attach client

nodejs.org: inspector.

For Chrome DevTools 55+:

Option 1: Open "chrome://inspect" in a Chromium-based browser. Click the Configure button and ensure your target host and port are listed. Then select your Node.js app from the list.
Option 2: Install the Chrome Extension NIM (Node Inspector Manager).

Debugger did not attach after 5 minutes; continuing

github.com.

try meteor --inspect or meteor --inspect-brk.

mongo


meteor mongo

# This opens a console into your app's local development database. Into the prompt, type:
# db.tasks.insert({ text: "Hello world!", createdAt: new Date() });

npm

docs.meteor.com.

Installing:


meteor npm install lodash --save

To use it:



import moment from 'moment';
// this is equivalent to the standard node require:
const moment = require('moment');

// import specific functions from a package using the destructuring syntax:
import { isArray } from 'lodash';
// You can also import other files or JS entry points from a package:
import { parse } from 'graphql/language';

Atmosphere VS npm

guide.meteor.com: atmosphere vs npm.

Atmosphere packages depends on core Meteor packages, such as ddp and blaze. It may include pre-built binary code for different server architectures, such as Linux or Windows.


// Use atmosphere
meteor add maxkfranz:cytoscape

Why use "meteor npm" instead of just "npm"

shell

When meteor shell is executed in an application directory where a server is already running, it connects to the server and starts an interactive shell for evaluating server-side code.

Import modules



import { CT } from './imports/ct';
CT.findOne(someId); // Success!

Blaze

Templates

Template tags

Double braced template tags

Double-braced template tags, e.g. {{pageTitle}}. When they are within block template tags, most likely they refer to properties of the current item. Or they could refer to template helpers.



{{frob a b c verily=true}}
// calls:
frob(a, b, c, Spacebars.kw({verily: true}))

Inclusion template tags

Inclusion template tags {{> templateName}. Everything inside "template" tags is compiled into Meteor templates, which can be included inside HTML with {{> templateName}} or referenced in your JavaScript with Template.templateName.

Block template tags

Block template tags, e.g. {{#each}}, {{#each}} and {{#if}}. They let you add logic and data to your views.

Also, the body section can be referenced in your JavaScript with Template.body.

Triple-braced template tags

{{{content}}} - Triple-braced template tags are used to insert raw HTML.

imports/ui/body.html:



<body>
  <div class="container">
    <header>
      <h1>Todo List</h1>
      <form class="new-task">
        <input type="text" name="text" placeholder="Type to add new tasks" />
      </form>
    </header>
 
    <ul>
      {{#each tasks}}
        {{> task}}
      {{/each}}
    </ul>
  </div>
</body>
 
<template name="task">
  <li>{{text}}</li>
</template>


////////// imports/ui/body.js:

import { Template } from 'meteor/templating';

import { Tasks } from '../api/tasks.js';
 
import './body.html';

//----- We can pass data into templates from JavaScript code by defining "helpers".
 
Template.body.helpers({
// Method 1: use array.
//  tasks: [
//    { text: 'This is task 1' },
//    { text: 'This is task 2' },
//    { text: 'This is task 3' },
//  ],

// Method 2: use MongoDB:
  tasks() {
    // Show newest tasks at the top
    return Tasks.find({}, { sort: { createdAt: -1 } });
  },
});

Template.body.events({
  'submit .new-task'(event) {
    // Prevent default browser form submit
    event.preventDefault();
 
    // Get value from form element
    const target = event.target;
    const text = target.text.value;
 
    // Insert a task into the collection
    Tasks.insert({
      text,
      createdAt: new Date(), // current time
    });
 
    // Clear form
    target.text.value = '';
  },
});

////////// imports/api/tasks.js

import { Mongo } from 'meteor/mongo';
 
export const Tasks = new Mongo.Collection('tasks');

////////// server/main.js

import '../imports/api/tasks.js';

////////// client/main.js

import '../imports/ui/body.js';

Template helpers

Server-side calculation

ibm.com



//---------- Template
<template name="analytics">
  {{#with analyticResult}}
    ... Do something ...
  {{/with}}
</template>

//---------- server/main.js
Meteor.methods({ 
  serverSideComputation: function() {
    var result = {};
    result.foo = "Hello ";
    result.bar = "World!";
    return result;
  },
});

//---------- Using the Session
Template.analytics.helpers({
  analyticResult: function() {
    if (Session.get('someIdentifier') === undefined) {
      Meteor.call('serverSideComputation', function(err, data) {
        Session.set('someIdentifier', data);
      });
    }
    return Session.get('someIdentifier);
  }
});

//---------- Using a Reactive Variable
Template.analytics.created = function() {
  this.someIdentifier = new ReactiveVar(false);
}
Template.analytics.helpers({
  analyticResult: function() {
    if (Template.instance().someIdentifier.get()) === false) {
      Meteor.call('serverSideComputation', function(err, data) {
        Template.instance().someIdentifier.set(data.foo+data.bar);
      });
    }
    return Template.instance().someIdentifier.get();
  }
});

Mongo

Collections

docs.meteor.com: collections.



// Common code on client and server declares a DDP-managed Mongo collection.
const Chatrooms = new Mongo.Collection('chatrooms');
const Messages = new Mongo.Collection('messages');

// Return an array of my messages.
const myMessages = Messages.find({ userId: Meteor.userId() }).fetch();
// Create a new message.
Messages.insert({ text: 'Hello, world!' });
// Mark my first message as important.
Messages.update(myMessages[0]._id, { $set: { important: true } });

If you pass a name when you create the collection:

* On the server, a collection with that name is created on a backend Mongo server.
* On the client, a Minimongo instance is created. Minimongo is essentially an in-memory, non-persistent implementation of Mongo in pure JavaScript.
* When you write to the database on the client (insert, update, remove), the command is executed locally immediately, and, simultaneously, it’s sent to the server and executed there too. This happens via stubs, because writes are implemented as methods.

find, findOne

"find" returns a cursor. It does not immediately access the database or return documents.

Cursors provide fetch() to return all matching documents, map() and forEach() to iterate over all matching documents, and observe() and observeChanges() to register callbacks when the set of matching documents changes. Cursors also implement ES2015’s iteration protocols.

Cursors are a reactive data source.

Any change to the collection that changes the documents in a cursor will trigger a recomputation. To disable this behavior, pass {reactive: false} as an option to find.

"findOne" finds the first document that matches the selector, as ordered by "sort" and "skip" options. Returns undefined if no matching document is found.

Equivalent to find(selector, options).fetch()[0] with options.limit = 1.

ReactiveDict

meteor.com: temporary UI state.


meteor add reactive-dict

Example



<label class="hide-completed">
  <input type="checkbox" />
  Hide Completed Tasks
</label>


import { Template } from 'meteor/templating';
import { ReactiveDict } from 'meteor/reactive-dict';

Template.body.onCreated(function bodyOnCreated() {
  this.state = new ReactiveDict();
});

Template.body.events({
  'submit .new-task'(event) {
    // Prevent default browser form submit
    event.preventDefault();

    // Get value from form element
    const target = event.target;
    const text = target.text.value;

    // Insert a task into the collection
    Tasks.insert({
      text,
      createdAt: new Date(), // current time
    });

    // Clear form
    target.text.value = '';
  },
  'change .hide-completed input'(event, instance) {
    instance.state.set('hideCompleted', event.target.checked);
  },
});

Template.body.helpers({
  tasks() {
    const instance = Template.instance();
    if (instance.state.get('hideCompleted')) {
      // If hide completed is checked, filter tasks
      return Tasks.find({ checked: { $ne: true } }, { sort: { createdAt: -1 } });
    }
    // Otherwise, return all of the tasks
    return Tasks.find({}, { sort: { createdAt: -1 } });
  },
});

User account

Tutorial

meteor.com: tutorials: adding user accounts.


meteor add accounts-ui accounts-password


{{> loginButtons}}

{{#if currentUser}}
  <form class="new-task">
    <input type="text" name="text" placeholder="Type to add new tasks" />
  </form>
{{/if}}
<!-- In your HTML, you can use the built-in {{currentUser}} helper to check if a user is logged in and get information about them. For example, {{currentUser.username}} will display the logged in user's username. -->

<!-- //---------- imports/ui/task.html -->
<template name="task">
  <li class="{{#if checked}}checked{{/if}}">
    <button class="delete">&times;</button>

    <input type="checkbox" checked="{{checked}}" class="toggle-checked" />

    <span class="text">{{text}}</span>
    <span class="text"><strong>{{username}}</strong> - {{text}}</span>
  </li>
</template>


//---------- imports/startup/accounts-config.js
import { Accounts } from 'meteor/accounts-base';
 
Accounts.ui.config({
  passwordSignupFields: 'USERNAME_ONLY',
});

//---------- client/main.js
import '../imports/startup/accounts-config.js';
import '../imports/ui/body.js';

//----------imports/ui/body.js
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { ReactiveDict } from 'meteor/reactive-dict';

//... 
    Tasks.insert({
      text,
      createdAt: new Date(), // current time
      owner: Meteor.userId(),
      username: Meteor.user().username,
    });
// In your JavaScript code, you can use Meteor.userId() to get the current user's _id, or Meteor.user() to get the whole user document.
//...

API

Accounts

docs.meteor.com: accounts.

The Meteor Accounts system builds on top of the "userId" support in publish and methods.

The basic Accounts system is in the accounts-base package, but applications typically include this automatically by adding one of the login provider packages: accounts-password, accounts-facebook, accounts-github, accounts-google, accounts-meetup, accounts-twitter, or accounts-weibo.

Meteor.user(), userId(), users



import { Meteor } from 'meteor/meteor'

Meteor.user();
// Get the current user record, or null if no user is logged in. A reactive data source.

Meteor.userId();
// Get the current user id, or null if no user is logged in. A reactive data source.

Meteor.users;
// A Mongo.Collection containing user documents.

{{currentUser}}

Calls Meteor.user(). Use {{#if currentUser}} to check whether the user is logged in.

Accounts.ui



Accounts.ui.config({
  requestPermissions: {
    facebook: ['user_likes'],
    github: ['user', 'repo']
  },
  requestOfflineToken: {
    google: true
  },
  passwordSignupFields: 'USERNAME_AND_OPTIONAL_EMAIL'
});

accounts-password

The accounts-password package contains a full system for password-based authentication. In addition to the basic username and password-based sign-in process, it also supports email-based sign-in including address verification and password recovery emails.


meteor add accounts-password

Core

Meteor.isClient(), isServer()


import { Meteor } from 'meteor/meteor'

// On server startup, if the database is empty, create some initial data.
if (Meteor.isServer) {
  Meteor.startup(function () {
    if (Rooms.find().count() === 0) {
      Rooms.insert({name: "Initial room"});
    }
  });
}

Meteor.isDevelopment(), isProduction()

TBD

Check

check()



// meteor add check

import { check } from 'meteor/check'

Meteor.publish("chats-in-room", function (roomId) {
  // Make sure roomId is a string, not an arbitrary mongo selector object.
  check(roomId, String);
  return Chats.find({room: roomId});
});

Meteor.methods({addChat: function (roomId, message) {
  check(roomId, String);
  check(message, {
    text: String,
    timestamp: Date,
    // Optional, but if present must be an array of strings.
    tags: Match.Maybe([String])
  });

  // ... do something with the message ...
}});

If the match fails, check throws a Match.Error describing how it failed. If this error gets sent over the wire to the client, it will appear only as Meteor.Error(400, "Match Failed"). The failure details will be written to the server logs but not revealed to the client.

Match.test()



import { Match } from 'meteor/check'

// will return true for {foo: 1, bar: "hello"} or similar
Match.test(value, {foo: Match.Integer, bar: String});

// will return true if value is a string
Match.test(value, String);

// will return true if value is a String or an array of Numbers
Match.test(value, Match.OneOf(String, [Number]));

// This can be useful if you have a function that accepts several different kinds of objects, and you want to determine which was passed in.

Collections

docs.meteor.com: collections.

Mongo.Collection()

Meteor stores data in collections. To get started, declare a collection with new Mongo.Collection.

Each document is a EJSON object.



import { Mongo } from 'meteor/mongo'

// Common code on client and server declares a DDP-managed mongo collection.
// If you pass a name when you create the collection, then you are declaring a persistent collection — one that is stored on the server and seen by all users.
// If you pass null as the name, then you’re creating a local collection. It’s not synchronized anywhere; it’s just a local scratchpad that supports Mongo-style find, insert, update, and remove operations. (On both the client and the server, this scratchpad is implemented using Minimongo.)
// By default, Meteor automatically publishes every document in your collection to each connected client. To turn this behavior off, remove the autopublish package, in your terminal: meteor remove autopublish.
Chatrooms = new Mongo.Collection("chatrooms");
Messages = new Mongo.Collection("messages");

// return array of my messages
var myMessages = Messages.find({userId: Session.get('myUserId')}).fetch();

// create a new message
Messages.insert({text: "Hello, world!"});

// mark my first message as "important"
Messages.update(myMessages[0]._id, {$set: {important: true}});

Mongo.Collection#find(), findOne()

find() returns a cursor. It does not immediately access the database or return documents. Cursors provide fetch() to return all matching documents, map() and forEach() to iterate over all matching documents, and observe() and observeChanges() to register callbacks when the set of matching documents changes.

Cursors are a reactive data source. On the client, the first time you retrieve a cursor’s documents with fetch(), map(), or forEach() inside a reactive computation (eg, a template or autorun), Meteor will register a dependency on the underlying data. Any change to the collection that changes the documents in a cursor will trigger a recomputation. To disable this behavior, pass {reactive: false} as an option to find().

findOne() finds the first document that matches the selector, as ordered by 'sort' and 'skip' options. Equivalent to find(selector, options).fetch()[0] with options.limit = 1.

Cursors

forEach



// Print the titles of the five top-scoring posts.
const topPosts = Posts.find({}, { sort: { score: -1 }, limit: 5 });
let count = 0;
topPosts.forEach((post) => {
  console.log(`Title of post ${count}: ${post.title}`);
  count += 1;
});

Methods

Methods are remote functions that Meteor clients can invoke.

They should return an EJSON-able value or throw an exception.

The database mutators (insert, update, remove) are implemented as "methods". When you call any of these functions on the client, you’re invoking their stub version that update the local cache, and sending the same write request to the server. When the server responds, the client updates the local cache with the writes that actually occurred on the server.

If a client calls a method and is disconnected before it receives a response, it will re-call the method when it reconnects. This means that a client may call a method multiple times when it only means to call it once. If this behavior is problematic for your method, consider attaching a unique ID to each method call on the client, and checking on the server whether a call with this ID has already been made. Alternatively, you can use "Meteor.apply" with the "noRetry" option set to true.



import { Meteor } from 'meteor/meteor'

// Defines functions that can be invoked over the network by clients.
Meteor.methods({
  foo: function (arg1, arg2) {
    check(arg1, String);
    check(arg2, [Number]);

    // .. do stuff ..

    if (/* you want to throw an error */) {
      throw new Meteor.Error("pants-not-found", "Can't find my pants");
    }

    return "some return value";
  },

  bar: function () {
    // .. do other stuff ..
    return "baz";
  }
});

Publish and Subscribe

Meteor.publish()

To publish records to clients, call Meteor.publish() on the server with two parameters: the name of the record set, and a publish function that Meteor will call each time a client subscribes to the name.



import { Meteor } from 'meteor/meteor'

// server: publish the rooms collection, minus secret info...
Meteor.publish("rooms", function () {
  return Rooms.find({}, {fields: {secretInfo: 0}});
});

// ... and publish secret info for rooms where the logged-in user
// is an admin. If the client subscribes to both streams, the records
// are merged together into the same documents in the Rooms collection.
// Note that currently object values are not recursively merged, so the
// fields that differ must be top level fields.
Meteor.publish("adminSecretInfo", function () {
  return Rooms.find({admin: this.userId}, {fields: {secretInfo: 1}});
});

// publish dependent documents and simulate joins
Meteor.publish("roomAndMessages", function (roomId) {
  check(roomId, String);
  return [
    Rooms.find({_id: roomId}, {fields: {secretInfo: 0}}),
    Messages.find({roomId: roomId})
  ];
});

Meteor.subscribe()

When you subscribe to a record set, it tells the server to send records to the client. The client stores these records in local Minimongo collections, with the same name as the collection argument used in the publish handler’s added, changed, and removed callbacks. Meteor will queue incoming records until you declare the Mongo.Collection on the client with the matching collection name.



import { Meteor } from 'meteor/meteor'

// okay to subscribe (and possibly receive data) before declaring
// the client collection that will hold it.  assume "allplayers"
// publishes data from server's "players" collection.
Meteor.subscribe("allplayers");
...
// client queues incoming players records until ...
...
Players = new Mongo.Collection("players");

ReactiveVar

docs.meteor.com: reactive-var.


meteor add reactive-var

// import { ReactiveVar } from 'meteor/reactive-var'

A ReactiveVar holds a single value that can be get and set, such that calling set() will invalidate any Computations that called get(), according to the usual contract for reactive data sources.

A ReactiveVar is similar to a Session variable, with a few differences:

An important property of ReactiveVars — which is sometimes a reason for using one — is that setting the value to the same value as before has no effect; it does not trigger any invalidations.

Session, ReactiveVar, ReactiveDict

blog.meteor.com.

A good rule of thumb is to always look at ReactiveVar and ReactiveDict first. Because we can tie these to a template instance making them local, we don’t have to worry about polluting the global namespace like we would with a Session variable.

ReactiveDict is best thought of as a combination between Session and ReactiveVar. While the behavior and usage of ReactiveDict is identical to ReactiveVar, where it differs is in how much it can store. Like the name implies, a ReactiveDict can store a dictionary of values.

Sessions is a good way to handle form state/data, which needs to exist after a hot code push, so if a user is filling out a form and we deploy some code, they don’t lose their work.

Session variable survives a hot code push. This means that if we change the underlying code for the application and save it, whatever value that session variable currently has will remain intact when Meteor refreshes with the new code. This applies when we push new code to the server, too, not just when saving locally.

STILL RESETS ON REFRESH. Although session variables survive a hot code push they do not survive the browser being refreshed by the user. Keep this in mind when designing the behavior of your interface.

Session


// meteor add session

import { Session } from 'meteor/session'

Tracker.autorun(function () {
  Meteor.subscribe("chat-history", {room: Session.get("currentRoomId")});
});

// Causes the function passed to Tracker.autorun to be re-run, so that the chat-history subscription is moved to the room "home".
Session.set("currentRoomId", "home");

Session.equals()


// Set our variable somewhere.
Session.set( "currentPizza", "Cheese");
Session.equals( "currentPizza", "Pepperoni");
// Returns false;
Session.equals( "currentPizza", "Cheese");
// Returns true;

Session.setDefault()

Session.setDefault(key, value): Set a variable in the session if it hasn't been set before. Otherwise works exactly the same as Session.set.

Templates

docs.meteor.com: templates.

For every <template name="foo"> ... </template> in an HTML file in your app, Meteor generates a “template object” named "Template.foo".

Template.myTemplate.events()



// The handler function receives two arguments: "event", an object with information about the event, and "template", a template instance. The handler also receives some additional context data in "this".
{
  // Fires when any element is clicked
  'click'(event, template) { ... },

  // Fires when any element with the 'accept' class is clicked
  'click .accept'(event, template) { ... },

  // Fires when 'accept' is clicked or focused, or a key is pressed
  'click .accept, focus .accept, keypress'(event, template) { ... }
}

// 'click p' catches a click anywhere in a paragraph, even if the click originated on a link, span, or some other element inside the paragraph.
// The originating element of the event is available as the "target" property, while the element that matched the selector and is currently handling it is called "currentTarget".
{
  'click p'(event) {
    //---------- members of event:
    var paragraph = event.currentTarget; // always a P
    var clickedElement = event.target; // could be the P or a child element
    var keyPressed = event.which; // For mouse events, the number of the mouse button (1=left, 2=middle, 3=right). For key events, a character or key code.
  }
}

Event types

click
dblclick
focus, blur
change
mouseenter, mouseleave
mousedown, mouseup
keydown, keypress, keyup

Template.myTemplate.helpers()



Template.myTemplate.helpers({
  foo() {
    return Session.get("foo");
  }
});
// Now you can invoke this helper with {{foo}} in the template defined with <template name="myTemplate">.

Template.myTemplate.helpers({
  displayName(firstName, lastName, keyword) {
    var prefix = keyword.hash.title ? keyword.hash.title + " " : "";
    return prefix + firstName + " " + lastName;
  }
});
// Then you can call this helper from template like this:
// {{displayName "John" "Doe" title="President"}}

Template.myTemplate.onRendered(), onCreated(), onDestroyed()

TBD

Template.instance()

A template instance object represents an occurrence of a template in the document. It can be used to access the DOM and it can be assigned properties that persist as the template is reactively updated.

Template instance objects are found as the value of this in the onCreated, onRendered, and onDestroyed template callbacks, and as an argument to event handlers. You can access the current template instance from helpers using Template.instance().

In addition to the properties and functions described below, you can assign additional properties of your choice to the object. Use the onCreated and onDestroyed methods to add callbacks performing initialization or clean-up on the object.

You can only access findAll(), find(), firstNode(), and lastNode() from the onRendered callback and event handlers, not from onCreated and onDestroyed, because they require the template instance to be in the DOM.

this.autorun()

You can use this.autorun from an onCreated or onRendered callback to reactively update the DOM or the template instance. You can use Template.currentData() inside of this callback to access reactive data context of the template instance. The Computation is automatically stopped when the template is destroyed.

Alias for "template.view.autorun".

Tracker

docs.meteor.com: tracker.

Meteor has a simple dependency tracking system which allows it to automatically rerun templates and other computations whenever Session variables, database queries, and other data sources change.

A change to a data dependency does not cause an immediate rerun, but rather “invalidates” the computation, causing it to rerun the next time a flush occurs. A flush will occur automatically as soon as the system is idle if there are invalidated computations. You can also use "Tracker.flush" to cause an immediate flush of all pending reruns.

autorun()

Run a function now and rerun it later whenever its dependencies change. Returns a Computation object that can be used to stop or observe the rerunning.

The function to run receives one argument: the Computation object that will be returned.



// Wait for a session variable to have a certain value, and do something the first time it does, calling stop() on the computation to prevent further rerunning:
Tracker.autorun(function (c) {
  if (! Session.equals("shouldAlert", true))
    return;

  c.stop();
  alert("Oh no!");
});
// The function is invoked immediately, at which point it may alert and stop right away if shouldAlert is already true. If not, the function is run again when shouldAlert becomes true.

Dependency

Typically, a data value will be accompanied by a Dependency object that tracks the computations that depend on it.



var weather = "sunny";
var weatherDep = new Tracker.Dependency;

var getWeather = function () {
  weatherDep.depend()
  return weather;
};
// The getter records that the current computation depends on the weatherDep dependency using depend().

var setWeather = function (w) {
  weather = w;
  // (could add logic here to only call changed()
  // if the new value is different from the old)
  weatherDep.changed();
};
// The setter signals the dependency to invalidate all dependent computations by calling changed().

bootstrap

Install bootstrap 4

forums.meteor.com.



meteor show --show-all twbs:bootstrap
                                            
# Versions:                                     
#   3.3.1         November 28th, 2014           
#   3.3.1_1       December 11th, 2014
#   3.3.1_2       December 12th, 2014
#   3.3.2         January 26th, 2015
#   3.3.4         March 16th, 2015
#   3.3.5         June 16th, 2015
#   3.3.6         November 24th, 2015
#   4.0.0-alpha   August 25th, 2015
#   4.0.0-alpha2  December 30th, 2015

# Remove old one
meteor remove bootstrap
meteor add twbs:[email protected]

error: Bootstrap tooltips require Tether

github.com.


meteor remove twbs:bootstrap
meteor add coursierprive:tether
meteor add twbs:[email protected]=4.0.0-alpha2