Mostrando entradas con la etiqueta javascript. Mostrar todas las entradas
Mostrando entradas con la etiqueta javascript. Mostrar todas las entradas

martes, 29 de mayo de 2018

How to convert your Angular 5 app to Universal with ngUniversal

Hi there again! Today I come with a new situation that I solve using Universal in my Angular app with ngUniversal.

The situation:

I developed an application (here is the repository). When I begin using google search engine and other engines to index my web, I've found the problem that my web is not indexed correctly.

When I change the language of the web, the title, the description and the keywords don't change. So google and other engines only index my main index.html file.

With Universal, you can change it, and I'm going to explain how.

The code you have to change:

In my package.json file, I have these dependencies and I show the run you have to define:

"scripts": {
"universal": "ng build --prod client && ng build --prod --app server --output-hashing=false && webpack --config webpack.server.config.js --progress --colors && node dist/server.js",
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
...
"dependencies": {
"@angular/animations": "^5.0.0",
"@angular/common": "^5.0.0",
"@angular/compiler": "^5.0.0",
"@angular/core": "^5.0.0",
"@angular/forms": "^5.0.0",
"@angular/http": "^5.0.0",
"@angular/language-service": "^5.0.0",
"@angular/platform-browser": "^5.0.0",
"@angular/platform-browser-dynamic": "^5.0.0",
"@angular/platform-server": "^5.0.0",
"@angular/router": "^5.0.0",
"@nguniversal/common": "^5.0.0-beta.5",
"@nguniversal/express-engine": "^5.0.0-beta.5",
"@nguniversal/module-map-ngfactory-loader": "^5.0.0-beta.5",
"@ngx-translate/core": "8.0.0",
"@ngx-translate/http-loader": "2.0.0"
...

The trick of Universal in Angular is to separate what the browser shows and what the server needs to compile for shows the browser.

You need to create an AppServerModule.ts that use the AppModule.ts of your application:

import { NgModule } from '@angular/core';
import { ServerModule, ServerTransferStateModule } from '@angular/platform-server';
import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';

import { AppModule } from './app.module';
import { TemplateComponent } from '../template/template.component';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TransferState } from '@angular/platform-browser';
import { HttpClient } from '@angular/common/http';
import { translateFactory } from './translate-universal-loader.service';

@NgModule({
imports: [
// The AppServerModule should import your AppModule followed
// by the ServerModule from @angular/platform-server.
AppModule,
ServerModule,
ModuleMapLoaderModule,
ServerTransferStateModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: translateFactory
}
})
],
// Since the bootstrapped component is not inherited from your
// imported AppModule, it needs to be repeated here.
bootstrap: [TemplateComponent],
})
export class AppServerModule {
constructor() {
console.log('AppServerModule');
}
}

So Universal is separating the Server and the Browser, you have to indicate to your application which parts will be and those parts.

Here you can see my TemplateComponent (that is like an AppComponent):

constructor(@Inject(PLATFORM_ID) private platformId: Object,
public authService: AuthService,
private translate: TranslateService,
private route: ActivatedRoute,
private languageService: LanguageService,
private titleService: Title,
private metaService: Meta) {
this.getLanguanges();
}

ngOnInit() {
if (isPlatformBrowser(this.platformId)) {
// Client only code.
this.loadLanguage();
this.loadMusic();
}
if (isPlatformServer(this.platformId)) {
// Server only code.
this.loadServerLanguage();
}
}

You have to know that some HTML access components are not permitted in Server Part. It's logic, so you don't have access to the dom of the browser. These not permitted uses are for example the access to DOM objects of the HTML or to the navigator.

So this code has to be on the browser part:

if(!userLang || userLang == "") {
userLang = navigator.language;
if(userLang.startsWith("zh")) {
userLang = "zh";
}
}

Also, you need to create a main.server.ts file in the same directory of main.ts with this line:

export { AppServerModule } from './app/core/app.server.module';

Also, you need a tsconfig.server.json file with this content and in the same directory of tsconfig.app.json:

{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"baseUrl": "./",
"module": "commonjs",
"types": []
},
"exclude": [
"test.ts",
"**/*.spec.ts"
],
"angularCompilerOptions": {
"entryModule": "app/core/app.server.module#AppServerModule"
}
}

Also, you need a webpack.server.config.js file like this (just copy-paste):

const path = require('path');
const webpack = require('webpack');

module.exports = {
entry: { server: './server.ts' },
resolve: { extensions: ['.js', '.ts'] },
target: 'node',
// this makes sure we include node_modules and other 3rd party libraries
externals: [/(node_modules|main\..*\.js)/],
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js'
},
module: {
rules: [
{ test: /\.ts$/, loader: 'ts-loader' }
]
},
plugins: [
// Temporary Fix for issue: https://github.com/angular/angular/issues/11580
// for "WARNING Critical dependency: the request of a dependency is an expression"
new webpack.ContextReplacementPlugin(
/(.+)?angular(\\|\/)core(.+)?/,
path.join(__dirname, 'src'), // location of your src
{} // a map of your routes
),
new webpack.ContextReplacementPlugin(
/(.+)?express(\\|\/)(.+)?/,
path.join(__dirname, 'src'),
{}
)
]
}

And you have to modify your .angular-cli.json file adding a new app. So you are going to have two apps (the client app and the server app). This separation is used on the run script defined on the package.json.

This will be the .angular-cli.json file:

{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"project": {
"name": "davidmartinezros.com"
},
"apps": [
{
"name": "client",
"root": "src",
"outDir": "dist/browser",
"assets": [
"assets",
"favicon.ico",
"assets/particlesjs-config.json"
],
"index": "index.html",
"main": "main.ts",
"polyfills": "polyfills.ts",
"test": "test.ts",
"tsconfig": "tsconfig.app.json",
"testTsconfig": "tsconfig.spec.json",
"prefix": "app",
"styles": [
"../node_modules/bootstrap/dist/css/bootstrap.min.css",
"../node_modules/font-awesome/css/font-awesome.css",
"assets/styles/animations.css",
"assets/styles/buttons.css",
"assets/styles/cookie-warning.css",
"assets/styles/experience.css",
"assets/styles/footer.css",
"assets/styles/loader.css",
"assets/styles/main.css",
"assets/styles/media-queries.css",
"assets/styles/navbar.css",
"assets/styles/projects.css",
"assets/styles/scrollbar.css",
"assets/styles/corner-ribbon.css"
],
"scripts": [
"../node_modules/jquery/dist/jquery.min.js",
"../node_modules/tether/dist/js/tether.js",
"../node_modules/popper.js/dist/umd/popper.min.js",
"../node_modules/bootstrap/dist/js/bootstrap.min.js",
"../node_modules/requirejs/require.js",
"assets/scripts/particles.js",
"assets/scripts/particles-loader.js",
"assets/scripts/check-is-on-viewport.js",
"assets/scripts/cookie-warning.js",
"assets/scripts/experience.js",
"assets/scripts/jarallax.js",
"assets/scripts/navigate.js",
"assets/scripts/typewriter-animation.js"
],
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
}
},
{
"name": "server",
"platform": "server",
"root": "src",
"outDir": "dist/server",
"assets": [
"assets",
"favicon.ico"
],
"index": "index.html",
"main": "main.server.ts",
"test": "test.ts",
"tsconfig": "tsconfig.server.json",
"testTsconfig": "tsconfig.spec.json",
"prefix": "app",
"styles": [
],
"scripts": [
],
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
}
}
],
"e2e": {
"protractor": {
"config": "./protractor.conf.js"
}
},
"lint": [
{
"project": "src/tsconfig.app.json",
"exclude": "**/node_modules/**"
},
{
"project": "src/tsconfig.spec.json",
"exclude": "**/node_modules/**"
},
{
"project": "e2e/tsconfig.e2e.json",
"exclude": "**/node_modules/**"
}
],
"test": {
"karma": {
"config": "./karma.conf.js"
}
},
"defaults": {
"styleExt": "css",
"component": {}
}
}

And finally, you need a server.ts file in the same directory of the package.json file. My server.ts looks like this:

// These are important and needed before anything else
import 'zone.js/dist/zone-node';
import 'reflect-metadata';

import { renderModuleFactory } from '@angular/platform-server';
import { enableProdMode } from '@angular/core';

import * as express from 'express';
import { join } from 'path';
import { readFileSync } from 'fs';

(global as any).WebSocket = require('ws');
(global as any).XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;


// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();

// Express server
const app = express();

//app.urlencoded({extended: false});

const PORT = process.env.PORT || 4000;
const HTTPS_PORT = process.env.HTTPS_PORT || 4443;

const KEY_CERTIFICATE = process.env.KEY_CERTIFICATE;
const CRT_CERTIFICATE = process.env.CRT_CERTIFICATE;
const PASSWORD_CERTIFICATE = process.env.PASSWORD_CERTIFICATE;

const DIST_FOLDER = join(process.cwd(), 'dist');

// Our index.html we'll use as our template
const template = readFileSync(join(DIST_FOLDER, 'browser', 'index.html')).toString();

// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main.bundle');

// Express Engine
import { ngExpressEngine } from '@nguniversal/express-engine';

// Import module map for lazy loading
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';
/*
app.engine('html', (_, options, callback) => {
const opts = { document: template, url: options.req.url };

renderModuleFactory(AppServerModuleNgFactory, opts)
.then(html => callback(null, html));
});
*/
app.engine('html', ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [
provideModuleMap(LAZY_MODULE_MAP)
]
}));

app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));

// Our page routes
export const routes: string[] = [
'main',
'dashboard',
'dashboard/contact',
'dashboard/blog',
'project/:lang/:nom'
];

// All regular routes use the Universal engine
app.get('/', (req, res) => {
console.log(req.query.lang);
console.time(`GET: ${req.originalUrl}`);
res.render(join(DIST_FOLDER, 'browser', 'index.html'), { req, res } );
console.timeEnd(`GET: ${req.originalUrl}`);
});

routes.forEach(route => {
app.get(`/${route}`, (req, res) => {
//res.json({'lang': req.query.lang});
console.log(req.query.lang);
console.time(`GET: ${req.originalUrl}`);
res.render(join(DIST_FOLDER, 'browser', 'index.html'), { req, res } );
console.timeEnd(`GET: ${req.originalUrl}`);
});
app.get(`/${route}/*`, (req, res) => {
//res.json({'lang': req.query.lang});
console.log(req.query.lang);
console.time(`GET: ${req.originalUrl}`);
res.render(join(DIST_FOLDER, 'browser', 'index.html'), { req, res } );
console.timeEnd(`GET: ${req.originalUrl}`);
});
});

// Server static files from /browser
app.get('/web', express.static(join(DIST_FOLDER, 'browser'), { 'index': false }));

app.get('/**', express.static(join(DIST_FOLDER, 'browser')));

// All other routes must be resolved if exist
/*
app.get('*', function(req, res) {
res.render(join(req.url), { req });
});
*/

var http = require('http');

var httpServer = http.createServer(app);

// Start up the Node server at PORT
httpServer.listen(PORT, () => {
console.log(`Node server listening on http://localhost:${PORT}`);
});

if(KEY_CERTIFICATE && CRT_CERTIFICATE && PASSWORD_CERTIFICATE) {

var fs = require('fs');
var https = require('https');

var privateKey = fs.readFileSync(KEY_CERTIFICATE, 'utf8');
var certificate = fs.readFileSync(CRT_CERTIFICATE, 'utf8');

var credentials = {
key: privateKey,
cert: certificate,
passphrase: PASSWORD_CERTIFICATE
};
var httpsServer = https.createServer(credentials, app);

// Start up the Node server at HTTP_PORT
httpsServer.listen(HTTPS_PORT, () => {
console.log(`Node server listening on http://localhost:${HTTPS_PORT}`);
});
}

I've defined some URLs of my application that use the ServerModule and the rest of the URLs in the domain access statically. Also, I've defined an HTTP and an HTTPS server for secure connections defined on my node server.

And that's all! Is not easy, but works very good.

I hope you enjoy my tutorial and ask me any question you have with this trick.

You can see my project on production in this URL: https://davidmartinezros.com

Have a nice day!

viernes, 9 de marzo de 2018

How to add music to your Website in Javascript

Hi there!

Today I want to talk about "how to add music to your website with the Html Web API".

We are going to use the Html Audio object instead of other proposed solutions on the net, like use Howler.js or other audio frameworks.

For use this solutions, you need to use Javascript or other language compile Javascript, like Angular or Ionic.

Declaration of variables:

loaded: boolean = false;
playing: boolean = false;
sound: any;

First, create the Audio object and define the default properties:

this.sound = new Audio();
this.sound.autoplay = false;
this.sound.preload = 'auto';
this.sound.autobuffer = true;


Before you define the events, you must declare a local variable pointing to this:

let parent = this;

Then, you have to define the audio event listeners:

this.sound.addEventListener('loadeddata', function() {
console.log("music loaded");
parent.loaded = true;
parent.playTrack();
}, false);


this.sound.addEventListener('play', function() {
console.log("music play");
parent.playing = true;
}, false);


this.sound.addEventListener('pause', function() {
console.log("music pause");
parent.playing = false;
}, false);

And for last you need to define the music src and load it:

this.sound.src = './assets/audio/Rhodesia_MkII.mp3';
this.sound.load();

Now, you need the actions and functions called from the html:

isLoadedTrack() {
return this.loaded;
}

isPlayingTrack() {
return this.playing;
}

playTrack() {
if(this.sound) {
this.sound.play();
}
}

pauseTrack() {
if(this.sound) {
this.sound.pause();
}
}

And the content of the html will look like this:

<div *ngIf="isLoadedTrack()">
      <div class="music" *ngIf="!isPlayingTrack()">
          <button (click)="playTrack()">
<i class="fa fa-play"></i> <i class="fa fa-music"></i>
</button>
      </div>
      <div class="music" *ngIf="isPlayingTrack()">
          <button (click)="pauseTrack()">
<i class="fa fa-pause"></i> <i class="fa fa-music"></i>
</button>
      </div>
</div>

And the result will show those two state:



I hope you find the talk interesting!

You can see the Production use of the Component on https://davidmartinezros.com
And you can take a look to the Portfolio repository: https://github.com/davidmartinezros/portfolio

Any doubt, ask me and I will answer as soon as possible.

miércoles, 8 de febrero de 2017

How to obtain the language in the browser and externalice with it in Angular 2?

Hi everybody!

Here we comes with another question.

Today the theme is Angular 2, Javascript and the navigator class.

With this code we will obtain the language from the browser:

loadLanguage() {
  let idioma = navigator.language;
  if(idioma === 'ca' || idioma == 'es' || idioma == 'gl' || idioma == 'va' || idioma == 'eu' || idioma == 'en' || idioma == 'fr') {
    this.idioma = idioma;
  } else {
    this.idioma = "en";
  }
}


Navigator is a Javascript class that returns the language and other interesting things.

Show the nagivator object with console.log(navigator); to see what are you interested in.

Another question related with this theme is to externalice the content of your web in Angular2.

For do that I pass you my example code:

This method loads the dictionary with all the traductions and the possible languages:

loadDictionary() {
  this.dictionary = { titol: {ca: 'El Temps a Espanya!', es: '¡El Tiempo en España!', en: 'The Weather in Spain!', fr: 'String1InSpanish', gl: 'O tempo en España!', va: 'El Temps a Espanya!', eu: 'Eguraldia Espainian!'}, descripcio: { ca: 'Troba les prediccions meteorològiques de tots els municipis espanyols durant els propers quatre dies.', es: 'Encuentra las prediciones meteorológicas de todos los municipios españoles durante los próximos cuatro días.', en: 'Find weather forecasts for all Spanish municipalities over the next four days.', fr: 'Trouvez les prévisions météo pour toutes les municipalités espagnoles au cours des quatre prochains jours.', gl: 'Atopar as previsións do tempo para todos os municipios españois durante os próximos catro días.', va: 'Troba les prediccions meteorològiques de tots els municipis espanyols durant els propers quatre dies.', eu: 'Aurki eguraldi Espainiako udalerri guztietako iragarpenak hurrengo lau egunetan zehar.'} };
}


This method obtaions the translation of a key string in a language:

getTranslation(inStringId: string, inLanguageId: string) {
  let labelTexts = this.dictionary[inStringId];
  let translation;
  if (labelTexts) { translation = labelTexts[inLanguageId]; }
  if (translation == null) {
    translation = inStringId;
    console.error(inStringId + ' has no defined text for language ' + inLanguageId);
  }
  return translation;
}


And this method loads the texts you need in your web:

loadTexts() {
  this.titol = this.getTranslation("titol", this.idioma);
  this.descripcio = this.getTranslation("descripcio", this.idioma);
}

This is my way! There are another possibilities! For any questio, don't doubt ask me!

Try it and have fun!