Skip to content
This repository has been archived by the owner on Feb 2, 2019. It is now read-only.

Commit

Permalink
feat(universal): export node and browser compatible services
Browse files Browse the repository at this point in the history
 - decouple components from dependency on window, to allow them to be rendered on the server.
 - export two distinct provider sets for usage in node/browser settings. MATERIAL_NODE_PROVIDERS can be used while rendering on the server, and MATERIAL_BROWSER_PROVIDERS when running in the browser.

BREAKING CHANGE:

Remove static `Media.hasMedia` method and utility `rAF` function. Inject the `ViewportHelper` service for rAF, and the `Media` service for hasMedia. This static method referred to the window directly.

The use of `MATERIAL_PROVIDERS` has been deprecated. Please use either `MATERIAL_NODE_PROVIDERS` or `MATERIAL_BROWSER_PROVIDERS` as is appropriate for your platform.
  • Loading branch information
justindujardin committed Apr 2, 2016
1 parent ee23417 commit 67e8054
Show file tree
Hide file tree
Showing 29 changed files with 4,696 additions and 84 deletions.
99 changes: 98 additions & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,12 @@ module.exports = function (grunt) {
},
webpack: {
singleJs: require('./webpack.config.js')
},

universal: {
examples: {
src: 'index.html'
}
}

});
Expand Down Expand Up @@ -355,6 +361,97 @@ module.exports = function (grunt) {
});
});

// NOTE: This task does not work. It is WIP.
grunt.registerMultiTask('universal', 'Prerender examples app as static HTML', function () {
var done = this.async();
/*
* based on angular2-grunt-prerender
* https://github.com/angular/universal
*
* Copyright (c) 2016 Wassim Chegham
* Licensed under the MIT license.
*/
try {
var proxyquire = require('proxyquire');
var zone = require('zone.js');
var reflect = require('reflect-metadata');
var provide = require('angular2/core');
var router = require('angular2/router');
var ng2material = require('./ng2-material/all');
ng2material['@global'] = true;
ng2material['@noCallThru'] = true;
var app = proxyquire('./examples/app', {
'ng2-material/all': ng2material
});
var all = proxyquire('./examples/all', {
'ng2-material/all': ng2material
});
var universal = require('angular2-universal-preview');
var options = this.options({
component: [app.DemosApp],
providers: ng2material.MATERIAL_NODE_PROVIDERS,
platformProviders: [
universal.NODE_LOCATION_PROVIDERS,
],
directives: ng2material.MATERIAL_DIRECTIVES.concat(all.DEMO_DIRECTIVES),
preboot: false,
separator: '\r\n'
});
var angular2Prerender = function (file) {
var clientHtml = file.toString();
// bootstrap and render component to string
var bootloader = options.bootloader;
if (!options.bootloader) {
options.bootloader = {
component: options.component,
document: universal.parseDocument(clientHtml),
providers: options.providers,
componentProviders: options.componentProviders,
platformProviders: options.platformProviders,
directives: options.directives,
preboot: options.preboot
};
}
bootloader = universal.Bootloader.create(options.bootloader);
return bootloader.serializeApplication().then(function (html) {
return new Buffer(html);
});
};
this.files.forEach(function (f) {
var src = f.src.filter(function (filepath) {
if (!grunt.file.exists(filepath)) {
grunt.log.warn('Source file "' + filepath + '" not found.');
return false;
}
else {
return true;
}
})
.map(function (filepath) {
return grunt.file.read(filepath);
})
.join(grunt.util.normalizelf(options.separator));
// Handle options.
angular2Prerender(src)
.then(function (buffer) {
return src = buffer;
})
.then(function (_src) {
return grunt.file.write(f.dest, _src);
})
.then(function (_) {
return grunt.log.writeln('File "' + f.dest + '" created.');
done();
});
});

}
catch (e) {
console.error(e.stack);
return;
}

});

grunt.registerTask('site-meta', 'Build metadata files describing example usages', function (tag) {
var done = this.async();
Expand All @@ -379,7 +476,7 @@ module.exports = function (grunt) {

tasks.push(function buildCoverage() {
// Parse Lcov report and generate `coverage.json` file for site.
var parse = require('lcov-parse');
var parse = require('lcov-parse');
parse('.coverage/lcov.info', function (err, data) {
if (err) {
grunt.log.ok('skipping code coverage because lcov.info is missing');
Expand Down
35 changes: 6 additions & 29 deletions examples/app.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,14 @@
import {Component, enableProdMode, bind, Input, OnDestroy, ApplicationRef} from "angular2/core";
import {bootstrap} from "angular2/platform/browser";
import {
ROUTER_PROVIDERS,
ROUTER_DIRECTIVES,
RouteConfig,
HashLocationStrategy,
LocationStrategy,
Router
} from "angular2/router";
import {MATERIAL_DIRECTIVES, MATERIAL_PROVIDERS} from "../ng2-material/all";
import {Component, Input, OnDestroy, ApplicationRef} from "angular2/core";
import {ROUTER_DIRECTIVES, RouteConfig, Router} from "angular2/router";
import {DEMO_DIRECTIVES} from "./all";
import Example from "./example";
import {Http, Response, HTTP_PROVIDERS} from "angular2/http";
import {Http, Response} from "angular2/http";
import {IndexPage} from "./routes/index";
import {ComponentPage} from "./routes/component";
import {ComponentsService, IComponentMeta} from "./services/components";
import {NavigationService} from "./services/navigation";
import {VersionService} from "./services/version";
import {SidenavService} from "ng2-material/components/sidenav/sidenav_service";
import {Media} from "ng2-material/core/util/media";
import {Media, MATERIAL_DIRECTIVES, SidenavService} from "ng2-material/all";
// import {bootstrap} from "angular2/bootstrap";

/**
* Describe an example that can be dynamically loaded.
Expand All @@ -35,7 +25,6 @@ export interface IExampleData {
{path: '/', name: 'Index', component: IndexPage, useAsDefault: true},
{path: '/components/:id', name: 'Component', component: ComponentPage}
])

@Component({
selector: 'demos-app',
templateUrl: 'examples/app.html',
Expand All @@ -49,7 +38,7 @@ export class DemosApp implements OnDestroy {
static SIDE_MENU_BREAKPOINT: string = 'gt-md';

@Input()
fullPage: boolean = Media.hasMedia(DemosApp.SIDE_MENU_BREAKPOINT);
fullPage: boolean = this.media.hasMedia(DemosApp.SIDE_MENU_BREAKPOINT);

public site: string = 'Angular2 Material';

Expand Down Expand Up @@ -96,15 +85,3 @@ export class DemosApp implements OnDestroy {
}

}

let appProviders = [
HTTP_PROVIDERS, ROUTER_PROVIDERS, MATERIAL_PROVIDERS,
ComponentsService, NavigationService, VersionService,
bind(LocationStrategy).toClass(HashLocationStrategy)
];

if (window.location.href.indexOf('github.com') !== -1) {
enableProdMode();
}

bootstrap(DemosApp, appProviders);
10 changes: 5 additions & 5 deletions examples/components/dialog/basic_usage.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import {Component, ElementRef} from "angular2/core";
import {MATERIAL_DIRECTIVES, MdDialog} from "ng2-material/all";
import {MATERIAL_DIRECTIVES, MdDialog, Media, MdDialogConfig, MdDialogBasic, MdDialogRef} from "ng2-material/all";
import {DOM} from "angular2/src/platform/dom/dom_adapter";
import {MdDialogConfig, MdDialogBasic, MdDialogRef} from "ng2-material/components/dialog/dialog";
import {Media} from "../../../ng2-material/core/util/media";

@Component({
selector: 'dialog-basic-usage',
Expand All @@ -13,9 +11,11 @@ import {Media} from "../../../ng2-material/core/util/media";
export default class DialogBasicUsage {

status = ' ';
customFullscreen = Media.hasMedia('xs') || Media.hasMedia('sm');
customFullscreen = this.media.hasMedia('xs') || this.media.hasMedia('sm');

constructor(public dialog: MdDialog, public element: ElementRef) {
constructor(public dialog: MdDialog,
public media: Media,
public element: ElementRef) {

}

Expand Down
5 changes: 3 additions & 2 deletions examples/components/sidenav/basic_usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import {MATERIAL_DIRECTIVES, Media, SidenavService} from "ng2-material/all";
})
export default class SidenavBasicUsage {

constructor(public sidenav: SidenavService) {
constructor(public sidenav: SidenavService,
public media: Media) {

}

hasMedia(breakSize: string): boolean {
return Media.hasMedia(breakSize);
return this.media.hasMedia(breakSize);
}

open(name: string) {
Expand Down
3 changes: 1 addition & 2 deletions examples/example.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import {Component, Input, DynamicComponentLoader, ElementRef, ComponentRef, Query, QueryList} from "angular2/core";
import {IExampleData} from "./app";
import {DEMO_DIRECTIVES} from "./all";
import {MATERIAL_DIRECTIVES} from "ng2-material/all";
import {MATERIAL_DIRECTIVES, MdTabs} from "ng2-material/all";
import {Http, Response} from "angular2/http";
import {Highlight} from "./highlight";
import {TimerWrapper} from "angular2/src/facade/async";
import {MdTabs} from "ng2-material/components/tabs/tabs";


export interface ISourceFile {
Expand Down
18 changes: 18 additions & 0 deletions examples/platform/browser/bootstrap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {bind, enableProdMode} from "angular2/core";
import {bootstrap} from "angular2/platform/browser";
import {ROUTER_PROVIDERS, HashLocationStrategy, LocationStrategy} from "angular2/router";
import {MATERIAL_BROWSER_PROVIDERS} from "ng2-material/all";
import {HTTP_PROVIDERS} from "angular2/http";
import {ComponentsService} from "../../services/components";
import {NavigationService} from "../../services/navigation";
import {VersionService} from "../../services/version";
import {DemosApp} from "../../app";

if (window.location.href.indexOf('github.com') !== -1) {
enableProdMode();
}
bootstrap(DemosApp, [
HTTP_PROVIDERS, ROUTER_PROVIDERS, MATERIAL_BROWSER_PROVIDERS,
ComponentsService, NavigationService, VersionService,
bind(LocationStrategy).toClass(HashLocationStrategy)
]);
13 changes: 13 additions & 0 deletions examples/platform/node/bootstrap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {bootstrap} from "angular2-universal-preview";
import {ROUTER_PROVIDERS} from "angular2/router";
import {MATERIAL_NODE_PROVIDERS} from "ng2-material/all";
import {HTTP_PROVIDERS} from "angular2/http";
import {ComponentsService} from "../../services/components";
import {NavigationService} from "../../services/navigation";
import {VersionService} from "../../services/version";
import {DemosApp} from "../../app";

bootstrap(DemosApp, [
HTTP_PROVIDERS, ROUTER_PROVIDERS, MATERIAL_NODE_PROVIDERS,
ComponentsService, NavigationService, VersionService
]);
3 changes: 1 addition & 2 deletions examples/routes/component.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import {Component, OnInit} from "angular2/core";
import {RouteParams, ROUTER_DIRECTIVES} from "angular2/router";
import {ComponentsService, IComponentMeta} from "../services/components";
import {MATERIAL_DIRECTIVES} from "../../ng2-material/all";
import {MATERIAL_DIRECTIVES, SidenavService} from "ng2-material/all";
import Example from "../example";
import {NavigationService} from "../services/navigation";
import {DOM} from "angular2/src/platform/dom/dom_adapter";
import {SidenavService} from "../../ng2-material/components/sidenav/sidenav_service";
import {TimerWrapper} from "angular2/src/facade/async";

@Component({
Expand Down
3 changes: 1 addition & 2 deletions examples/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ import {Component, OnInit} from "angular2/core";
import {ROUTER_DIRECTIVES} from "angular2/router";
import {ComponentsService, IComponentMeta} from "../services/components";
import {NavigationService} from "../services/navigation";
import {MATERIAL_DIRECTIVES} from "ng2-material/all";
import {MATERIAL_DIRECTIVES, SidenavService} from "ng2-material/all";
import {DOM} from "angular2/src/platform/dom/dom_adapter";
import {Highlight} from "../highlight";
import {SidenavService} from "../../ng2-material/components/sidenav/sidenav_service";
import {TimerWrapper} from "angular2/src/facade/async";
import {Http, Response} from "angular2/http";

Expand Down
3 changes: 2 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
<script src="./node_modules/highlightjs/highlight.pack.js"></script>
<script src="./config.js"></script>
<script>
System.import('examples/app.js')
var type = typeof process === 'undefined' ? 'browser' : 'node';
System.import('examples/platform/' + type + '/bootstrap.js')
.then(function () {
console.log("loaded app");
})
Expand Down
26 changes: 23 additions & 3 deletions ng2-material/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ export * from './components/tabs/tabs';

export * from './core/util/media';

import {ViewportHelper, BrowserViewportHelper} from "./core/util/viewport";
import {provide} from "angular2/core";
export * from './core/util/viewport';
export * from './core/util/animate';

/**
Expand Down Expand Up @@ -105,12 +108,29 @@ export const MATERIAL_DIRECTIVES: Type[] = CONST_EXPR([
]);

/**
* Collection of Material Design component providers.
* Material Design component providers for use in a Node.JS environment.
*/
export const MATERIAL_PROVIDERS: any[] = [
export const MATERIAL_NODE_PROVIDERS: any[] = CONST_EXPR([
MdDialog,
Media,
SidenavService,
MdRadioDispatcher,
INPUT_VALIDATORS
];
]);

/**
* Material Design component providers for use in the browser.
*/
export const MATERIAL_BROWSER_PROVIDERS: any[] = CONST_EXPR([
MATERIAL_NODE_PROVIDERS,
provide(ViewportHelper, {useClass: BrowserViewportHelper})
]);


/**
* Please use {@see MATERIAL_NODE_PROVIDERS} or {@see MATERIAL_BROWSER_PROVIDERS}
* as appropriate.
*
* @deprecated
*/
export const MATERIAL_PROVIDERS = MATERIAL_BROWSER_PROVIDERS;
Loading

0 comments on commit 67e8054

Please sign in to comment.