Angular2 – Stepan Suvorov Blog https://stepansuvorov.com/blog Release 2.0 Thu, 23 May 2019 15:10:16 +0000 en-US hourly 1 https://wordpress.org/?v=6.3.1 Серверный рендеринг с Angular4 https://stepansuvorov.com/blog/2017/03/server-side-rendering-angular4/ https://stepansuvorov.com/blog/2017/03/server-side-rendering-angular4/#comments Mon, 13 Mar 2017 01:17:22 +0000 http://stepansuvorov.com/blog/?p=3291 Continue reading ]]> UPD: Статься обновлена 2017-04-24

Статья написана на основе перевода/переработки Server Side Rendering With Angular 4.

Что нам дает серверный рендеринг?

  • более быструю загрузку “первого экрана”
  • поисковая оптимизация (предоставление статики для поисковых ботов)
  • социальные превью для страничек

С первых же дней Angular2 Angular включал поддержку этой опции в виде отдельного проекта Angular Universal. Так как серверный пререндеринг одна из ключевых киллер-фич, то начиная с Angular 4 было принято решение включить доработанную версию непосредственно во фремворк.

В этой статье мы описываем необходимые шаги чтобы расширить существующее Angular 4 приложение добавив серверный рендеринг. Мы будем использовать конфигурацию webpack сгенерированную с помощью Angular CLI, полный пример можно найти тут.

Достаем Webpack конфиг из Angular CLI

Если вы используете Angular CLI, то вам необходимо извлечь webpack конфиг для возможной дальнейшей настройки:

ng eject

Внимание! Убедитесь в том, что вы понимаете что делает команда ng eject.

Вариант конфигурации(webpack.config.js) предложенный CLI не включает Uglify плагина, а AOT компиляция отключена, давайте включим обе опции:

[javascript]
[...]
"plugins": [
	[...],
	new AotPlugin({
          [...]
	  // влючаем AOT
	  "skipCodeGeneration": false
	}),

	// добавляем UglifyJsPlugin
	new webpack.optimize.UglifyJsPlugin()
]
[...]
[/javascript]

В то время как AOT совсем не обязательно для серверного рендеринга, комбинируя эти опции мы можем добиться прекрасного результата в плане скорости загрузки.

Необходимые пакеты

Все необходимое для Angular уже подключено с помощью angular-cli, кроме пакета @angular/animations, который поставим вручную:

npm install --save @angular/animations

теперь подумаем о сервере.  В представленном решении используются node.js вместе с express. Поэтому мы установим express c его типами вместе с @angular/platform-server:

npm i @angular/platform-server@next --save
npm i express @types/express --save

Создание корневого модуля для серверного рендеринга

Для использования серверного рендеринга нам нужен корневой модуль, который включает ServerModule. Согласно примеру от Rob Wormald, мы также включаем корневой модуль нашего основного приложения (тот что отрисовывается в браузере):

[javascript]
// app.server.module.ts

import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';

@NgModule({
  imports: [
	  ServerModule,
	  AppModule
  ],
  bootstrap: [
	  AppComponent
  ],
  providers: [ ]
})
export class AppServerModule {}
[/javascript]

Далее мы должны расширить BrowserModule основного приложения указав идентификатор приложения (просто строка):

[javascript]// app.module.ts

@NgModule({
    imports: [
        BrowserModule.withServerTransition({
            appId: 'demo-app'
        }),
        HttpModule,
        FormsModule,
	    [...]
    ],
    [...]
})
export class AppModule {}[/javascript]

AOT для сервера

Так как AotPlugin не поддерживает серверный AOT, мы используем компилятор Angular напрямую, для этого создадим tsconfig.server.json (копию tsconfig.json) со следующими настройками angularCompilerOptions:

"compilerOptions": {
    [...]
},
[...]
"angularCompilerOptions": {
  "genDir": "src/aot",
  "entryModule": "./src/app.server.module#AppServerModule"
}

Настроим алиас команду в package.json:

[...]
"scripts": {
    [...]
    "ngc:server": "ngc -p tsconfig.server.json"
}
[...]

После вызова

npm run ngc:server

компилятор создаст дополнительные TypeScript файлы для проекта.

Внимание! До выполнения команды если вы используете структуру файлов сгенерированную Angular CLI (<1.0.0-rc.1), то вам еще прийдется стереть содержимое файла ./src/typings.d.ts:

Иначе получите следующую ошибку: Error at ./src/typings.d.ts:2:13: Subsequent variable declarations must have the same type. Variable ‘module’ must be of type ‘NodeModule’, but here has type ‘{ id: string; }’.

Теперь можем создать главный файл для сервера – main.server.ts, в котором используем сгенерированный AppServerModuleNgFactory:

[javascript]
//main.server.ts
import 'zone.js/dist/zone-node';
import { renderModuleFactory } from '@angular/platform-server';
import { enableProdMode } from '@angular/core';
import { AppServerModuleNgFactory } from './aot/src/app/app.server.module.ngfactory';
import * as express from 'express';
import * as fs from 'fs';

function ngExpressEngine() {
  return function (filePath, options, callback) {
    renderModuleFactory(AppServerModuleNgFactory, {
      document: fs.readFileSync(filePath).toString(),
      url: options.req.url
    }).then(string =&amp;gt; {
        callback(null, string);
      });
  };
}

enableProdMode();

const app = express();
app.engine('html', ngExpressEngine());

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

app.get('/', (req, res) =&amp;gt; {
  res.render('index', { req });
});

app.get('/page2*', (req, res) =&amp;gt; {
  res.render('index', { req });
});

app.use(express.static('.'));

app.listen(8000, () =&amp;gt; console.log('listening...'));
[/javascript]

Убедимся что в роутере основного модуля также есть данные стейты:

[javascript]
//app.module.ts
    RouterModule.forRoot([
      { path: '', component: HomeComponent, pathMatch: 'full' },
      { path: 'page2', component: Page2Component }
    ])
[/javascript]

Webpack для серверного рендеринга

Давайте создадим отдельный webpack конфиг(webpack.server.config.js) для сборки бандла серверного рендеринга, отличием будут следующие строчки:

  // main.server.ts
  
  [...]
  target: 'node',
  [...]
  "entry": {
    "main": [
      "./src/main.server.ts"
    ]
  },

Так как подход использует всего один бандл, то CommonsChunkPlugin можно вырезать, также можно убрать NoEmitOnErrorsPlugin.

Чтобы один бандл не перетирал другой, мы сделаем вывод в другой файл:

  "output": {
    "path": path.join(process.cwd(), "dist"),
    "filename": "[name].server.bundle.js",
    "chunkFilename": "[id].server.chunk.js"
  },

и сконфигурируем специальным образом AotPlugin:

new AotPlugin({
  "entryModule": __dirname + "/src/app/app.server.module.ts#AppServerModule",
  "hostReplacementPaths": {
    "environments\\environment.ts": "environments\\environment.ts"
  },
  "exclude": [],
  "tsConfigPath": "./tsconfig.server.json",
  "skipCodeGeneration": false
}),

Итого

Компилируем клиент:

webpack

Компилируем сервер:

ngc -p tsconfig.server.json && webpack --config webpack.server.config.js

Запускаем

cd dist
node main.server.bundle.js

UPD: Кеширование ngExpressEngine

Чтобы не перегружать файловую систему, мы можем кешировать результат чтения файла:

[javascript]
let templateCache = {};
function ngExpressEngine() {
  return function (filePath, options, callback) {
    if (!templateCache[filePath]) {
      let file = fs.readFileSync(filePath);
      templateCache[filePath] = file.toString();
    }
    renderModuleFactory(AppServerModuleNgFactory, {
      document: fs.readFileSync(filePath).toString(),
      url: options.req.url
    }).then(string =&amp;gt; {
      callback(null, string);
    });
  };
}
[/javascript]

UPD2: Удобные команды для запуска

Мы можем прописать следующие алиас-команды в package.json:

"scripts": {
	[...]
	"build": "npm run build:client",
	"build:client": "webpack",
	"build:server": "ngc -p tsconfig.server.json && webpack --progress --config webpack.server.config.js",
	"build:all": "npm run build:client && npm run build:server",
	[...]
}

UPD3: Проблемы с APP_BASE_HREF

Для серверного модуля(app.server.module) необходимо указать APP_BASE_HREF:

providers: [{provide: APP_BASE_HREF, useValue : '/' }]

иначе будет следующая ошибка:

Error: No base href set. Please provide a value for the APP_BASE_HREF token or add a base element to the document.

UPD4: 2 модуля в одной папке ломают Angular CLI

И вы получаете следующую ошибку:

Error locating module for declaration
 SilentError: Multiple module files found:

Чтобы починить пришлось во всех местах переименовать app.server.module.ts в app.server-module.ts

]]>
https://stepansuvorov.com/blog/2017/03/server-side-rendering-angular4/feed/ 25
Концептуальный разбор маршрутизатора Angular2 с примерами https://stepansuvorov.com/blog/2017/02/%d0%ba%d0%be%d0%bd%d1%86%d0%b5%d0%bf%d1%82%d1%83%d0%b0%d0%bb%d1%8c%d0%bd%d1%8b%d0%b9-%d1%80%d0%b0%d0%b7%d0%b1%d0%be%d1%80-%d0%bc%d0%b0%d1%80%d1%88%d1%80%d1%83%d1%82%d0%b8%d0%b7%d0%b0%d1%82%d0%be%d1%80/ https://stepansuvorov.com/blog/2017/02/%d0%ba%d0%be%d0%bd%d1%86%d0%b5%d0%bf%d1%82%d1%83%d0%b0%d0%bb%d1%8c%d0%bd%d1%8b%d0%b9-%d1%80%d0%b0%d0%b7%d0%b1%d0%be%d1%80-%d0%bc%d0%b0%d1%80%d1%88%d1%80%d1%83%d1%82%d0%b8%d0%b7%d0%b0%d1%82%d0%be%d1%80/#comments Sat, 18 Feb 2017 09:32:18 +0000 http://stepansuvorov.com/blog/?p=3217 Continue reading ]]>
  • подключение и базовая настройка
    • use hash
  • стейты (states)
  • специальные директивы роутера
  • параметры стейта
  • параметры запроса(query params)
  • статические параметры стейта
  • перенаправление на другой стейт
  • события (events)
  • хуки (guards)
  • резолв асинхронных данных
  • вложенные стейты (nested states)
  • множественные вью (multiple views)
  • ленивая загрузка (lazy loading)
  • Подключение и базовая настройка

    Для использования маршрутизатора нам необходимо в первую очередь импортировать его модуль:

    import { RouterModule } from '@angular/router'

    и прописать в зависимостях:

    [javascript]
    imports: [

    RouterModule,

    ],
    [/javascript]

    После чего мы можем настроить состояния(стейты). Добавим корневой стейт:

    [javascript]
    const routes = [
    {path: ”, component: HomeComponent},
    ];
    [/javascript]

    синтаксис, как вы видите, довольно простой. Нам достаточно указать путь и компонент, который будет отображаться по этому пути. Можем таким же способом добавить еще один стейт:

    [javascript]
    const routes = [
    {path: ”, component: HomeComponent},
    { path: ‘user’, component: UsersComponent},
    ];
    [/javascript]

    Теперь, чтобы подключить описание стейтов к модулю роутера сделаем:

    [javascript]
    imports: [

    RouterModule.forRoot(routes),

    ],
    [/javascript]

    По умолчанию используются HTML5 пути, но если мы хотим переключить на хеш, то нам нужно указать дополнительный параметр:

    [javascript]
    imports: [

    RouterModule.forRoot(routes, {useHash: true}),

    ],
    [/javascript]

    Специальные директивы роутера

    RouterOutlet – для задание места вывода представления:

    [html]
    <router-outlet></router-outlet>;
    [/html]

    RouterLink – хелпер для удобного вывода ссылки:

    [html]
    <a routerLink="user">User</a>
    [/html]

    RouterLinkActive – для выделения(задания специального css класса) активных(указывающих на текущий стейт) ссылок:

    [html]
    <a routerLink="user" routerLinkActive="active-class">User</a>
    [/html]

    также можно задавать параметр

    [html]
    [routerLinkActiveOptions]="{exact: true}"
    [/html]

    Параметры стейта

    Определяем параметр в настройках стейта:

    [javascript]
    { path: ‘user/:userId’, component: UsersComponent},
    [/javascript]

    Получаем этот параметр с помощью специального сервиса ActivatedRoute:

    [javascript]
    constructor(private route: ActivatedRoute) {
    this.route.params.subscribe(params => console.log(params.userId));
    }
    [/javascript]

    также можно подписаться на конкретный параметр с помощью метода pluck:

    [javascript]
    constructor(private route: ActivatedRoute) {
    this.route.params.pluck(‘userId’).subscribe(userId => console.log(userId));
    }
    [/javascript]

    Параметры запроса(query params)

    Кроме параметров стейта, можем также добавлять в url произвольное количество параметров запроса:

    [html]
    <a routerLink="search" [queryParams]="{ city: ‘Amsterdam’ }">Filter by Amsterdam</a>
    [/html]

    либо если изменяем стейт с помощью метода navigate:

    [javascript]
    this.router.navigate([‘/search’], { queryParams: { city:’Amsterdam’ } });
    [/javascript]

    Чтобы получить параметры стейта подписываемся на queryParams свойство:

    [javascript]
    constructor(private route: ActivatedRoute) {
    this.route.queryParams.subscribe(params => console.log(params.city));
    }
    [/javascript]

    Cтатические параметры стейта

    Стейт также можно дополнять статическими параметрами:

    [javascript]
    { path: ‘user’, component: UsersComponent,
    data: {userName: ‘John’}},
    [/javascript]

    И получить доступ через сервис ActivatedRoute:

    [javascript]
    constructor(private route: ActivatedRoute) {
    this.route.data.subscribe(console.log);
    }
    [/javascript]

    Перенаправление на другой стейт

    Перенаправление может быть либо автоматическим (постоянный редирект) либо условным (по какому-то действию).

    Чтобы сделать автоматический редирект в настройках стейта мы прописываем свойство redirectTo, где указываем стейт, на который хотим перенаправить:

    [javascript]
    const routes = [
    { path: ”, redirectTo: ‘dashboard’, pathMatch: ‘full’ },
    { path: ‘dashboard’, component: DashboardComponent },
    ];
    [/javascript]

    обратите также внимание, что в случае path: ” мы должны указать строгую стратегию разбора URL – pathMatch: ‘full’.

    Для условного перенаправления мы используем методы router.navigate() и router.navigateByUrl():

    [javascript]
    router.navigate([‘users’, userId]);
    router.navigateByUrl(`users/${userId}`);
    [/javascript]

    – отличие только в том, что в первом случае вы подаете как параметр набор команд, а во втором строку URL.

    Cобытия роутера

    Мы можем подписываться не только на данные, но и на события происходящие в роутере, в этом нам поможет сервис Router:

    [javascript]
    constructor(router: Router) {
    router.events.subscribe((event: Event) => {
    if (event instanceof NavigationStart) {
    //…
    }
    });
    }
    [/javascript]

    кроме события NavigationStart мы также можем слушать:

    • NavigationEnd
    • NavigationCancel
    • NavigationError
    • RoutesRecognized

    Хуки (guards)

    На ряду с событиями в роутере есть хуки(guards), которые в отличие от событий выполняются ДО действия и в данном случае мы можем вклиниться в поток выполнения, и если нужно остановить выполнение события.

    Существуют следующие хуки:

    • CanActivate – определяет возможность загрузки стейта

    • CanActivateChild – аналогично предыдущего только для вложенного стейта

    • CanDeactivate – запускается при смене стейта, хорошим примером будет подтверждение не сохраненных данных перед уходом со страницы:

      CanDeactivate(){
            return window.confirm("You have unsaved changes. Still want to leave?");
      }
      
    • CanLoad – определяет может ли стейт быть загружен асинхронно

    • Resolve – для резолвинга данных до загрузки стейта (см. отдельный пункт о резолвинге данных)

    Для реализации хука нам нужно 2 момента: определить его в стейте:

    [javascript]
    { path: ‘user’, component: UsersComponent, canActivate: AuthService},
    [/javascript]

    и создать сам хук-сервис, который будет реализовывать соотвествующий интерфейс:

    [javascript]
    import {CanActivate} from ‘@angular/router’;
    export class AuthService implements CanActivate{
    canActivate(): boolean {
    return true;
    }
    }
    [/javascript]

    В методе canActivate мы можем возвращать как просто булевое значение, так и асинхронные Promise и Observable.

    Резолвинг асинхронных данных

    Для подгрузки асинхронных данных до загрузки стейта также используется хук – Resolve:

    [javascript]
    { path: ‘user’, component: UsersComponent, resolve:{ user: UserDataResolveService }},
    [/javascript]

    теперь создадим резолв-сервис:

    [javascript]
    import { Resolve } from ‘@angular/router’;
    export class UserDataResolveService implements Resolve<any> {
    resolve() {
    return { name: ‘Bob’ };
    }
    }
    [/javascript]

    и можно будет подписаться на эти данные, также как и на статические:

    [javascript]
    constructor(route: ActivatedRoute) {
    route.data.subscribe(data => {
    console.log(data.user);
    });
    }
    [/javascript]

    Вложенные стейты (nested states)

    Для задание вложенных/дочерних стейтов мы используем свойство children, в котором определяем массив:

    [javascript]
    { path: ‘user’, children: [
    {path: ”, component: UserProfileComponent},
    {path: ‘settings’, component: UserSettingsComponent}
    ]},
    [/javascript]

    синтаксис вложенных стейтов идентичен основным. Вложенные стейты также могут иметь свои вложенные.

    Множественные вью (multiple views)

    Роутер ангуляра поддерживает множественные вью, то есть наличие нескольких именованных RouterOutlet компонентов:

    [html]
    <router-outlet></router-outlet>
    <router-outlet name="popup"></router-outlet>
    [/html]

    Неименованный – основной. Теперь для настройки прописываем специальное свойство outlet:

    [javascript]
    { path: ‘user’, component: HomeComponent },
    { path: ‘user’, component: UsersComponent, outlet: ‘popup’},
    [/javascript]

    Ссылка routerLink будет выглядеть для такого случая следующим образом:

    [html]
    <a [routerLink]="[{ outlets: { primary: ‘user’, popup: ‘user’ } }]"></a>
    [/html]

    а если мы хотим деактивировать второстепенный аутлет:

    [html]
    <a [routerLink]="[{ outlets: { popup: null } }]">Close user</a>
    [/html]

    Ленивая загрузка (lazy loading)

    Роутер также дает нам возможность организовать отложенную(ленивую) загрузку модулей. Для этого нам необходимо прописать в стейт специальное свойство loadChildren и указать в нем путь и имя модуля, который собираемся загрузить:

    [javascript]
    { path: ‘lazy’, loadChildren: ‘./lazy/lazy.module#LazyModule’ }
    [/javascript]

    и инициализировать RouterModule внутри LazyModule:

    [javascript]
    const routes: Routes = [
    { path: ”, component: AdminComponent }
    ];

    @NgModule({
    imports: [
    CommonModule,
    RouterModule.forChild(routes)
    ],
    declarations: [AdminComponent]
    })
    export class LazyModule { }
    [/javascript]

    мы можем определить сколько захотим состояний для LazyModule, но я для простоты определил один, который загрузит компонент AdminComponent.

    ]]>
    https://stepansuvorov.com/blog/2017/02/%d0%ba%d0%be%d0%bd%d1%86%d0%b5%d0%bf%d1%82%d1%83%d0%b0%d0%bb%d1%8c%d0%bd%d1%8b%d0%b9-%d1%80%d0%b0%d0%b7%d0%b1%d0%be%d1%80-%d0%bc%d0%b0%d1%80%d1%88%d1%80%d1%83%d1%82%d0%b8%d0%b7%d0%b0%d1%82%d0%be%d1%80/feed/ 2
    Angular2: Changing default component properties. https://stepansuvorov.com/blog/2017/01/angular2-changing-default-component-properties/ https://stepansuvorov.com/blog/2017/01/angular2-changing-default-component-properties/#comments Sat, 28 Jan 2017 12:05:15 +0000 http://stepansuvorov.com/blog/?p=3258 Continue reading ]]> Attention! Please keep in mind that this post is not manual, it’s research that helps you to understand Angular better and don’t try to apply code examples in your enterprise applications.

    If you look into all the angular exports you could find specific super private(double low dash) property __core_private__ :

    [javascript]import { __core_private__ } from ‘@angular/core’;[/javascript]

    it provides us access to a method makeDecorator that is kind of internal factory to make all the decorators, so to create new decorator you just need to do:

    [javascript]__core_private__.makeDecorator(‘myNewDecorator’, {});[/javascript]

    so if you wanna create your own Component decorator it’s just:

    [javascript]const MyComponent = __core_private__.makeDecorator(‘MyComponent’, {});[/javascript]

    there is also 3rd parameter that allows us to make inheritance from another decorator, so let’s use existing Component decorator:

    [javascript]
    const MyComponent = __core_private__.makeDecorator(‘MyComponent’, {}, Component);
    [/javascript]

    we need to set required properties(it will not work without them):

    [javascript]
    const MyComponent = __core_private__.makeDecorator(‘myComponent’,{
    selector: undefined,
    template: ”,
    styleUrls: undefined},Component))
    [/javascript]

    and you could also extend some extra, for example switch off encapsulation:

    [javascript]
    const MyComponent = __core_private__.makeDecorator(‘myComponent’,{
    selector: undefined,
    template: ”,
    styleUrls: undefined
    encapsulation: ViewEncapsulation.None
    },Component))
    [/javascript]

    All code for module(mycomponent.ts):

    [javascript]
    import {__core_private__, Component, ViewEncapsulation} from ‘@angular/core’;

    let MyComponent = __core_private__.makeDecorator(‘myComponent’, {
    selector: undefined,
    template: ”,
    styleUrls: undefined,
    encapsulation: ViewEncapsulation.None},
    Component);

    export {MyComponent as Component}
    [/javascript]

    that can be use instead of core Component:

    [javascript]
    import {Component} from ‘./mycomponent’;
    [/javascript]

     

    ]]>
    https://stepansuvorov.com/blog/2017/01/angular2-changing-default-component-properties/feed/ 1
    Что нового в Angular 2.3 и Angular 2.4 https://stepansuvorov.com/blog/2017/01/new-in-angular-2-3-2-4/ https://stepansuvorov.com/blog/2017/01/new-in-angular-2-3-2-4/#respond Sun, 22 Jan 2017 08:37:30 +0000 http://stepansuvorov.com/blog/?p=3239 Continue reading ]]> Пройдемся по основным моментам:

    • Наследование компонентов
    • Сервис языка шаблонов Ангулар ( Angular Language Service )
    • Улучшеный стек zone.js
    • Доступ к версии Ангуляра
    • Обновление rxjs

    Наследование компонентов

    То, что мы все ждем еще с первой версии Ангуляра, наконец-то пришло, и нам не нужно придумывать своих велосипедов:

    [javascript]
    @Component({
    selector: ‘person’,
    template: `
    <h4>Person: {{name}}</h4>

    `
    })
    export class Person {
    @Input() name: string;
    }

    @Component({
    selector: ’employee’,
    template: `
    <h4>Employee: {{name}}, id: {{id}}</h4>

    `
    })
    export class Employee extends Person {
    @Input() id: string;
    }

    <div>
    <person name="John"></person>
    <employee name="Tom" id="45231"></employee>
    </div>

    [/javascript]

    То есть мы удобно можем расширить существующий компонент, при этом переопределив селектор и шаблон.

    * привер взят из Angular 2 — New features in Angular 2.3

    Сервис языка шаблонов Ангулар

    Сервис поможет средствам разработки(IDE) лучше понимать шаблоны, делать проверку и подсвечивать ошибки. Сервис будет редактор-независимый, но при этом приоритет отдается VS Code (plugin). По факту это только проба пера и полноценную доработанную версию можно ждать в Angular 4.

    Улучшеный стек zone.js

    Стек трейс для ошибок стал более четкий

    Доступ к версии Ангуляра

    Версию Ангуляра мы можем получить как импортировав специальную константу VERSION:

    [javascript]
    import {VERSION} from ‘@angular/core’;
    console.log(VERSION);
    [/javascript]

    так и из атрибута корневого элемента:

    Обновление rxjs

    Теперь(начиная с версии 2.4) под капотом будет стабильная 5.0.1. До этого долгое время оставалась 5.0.0-beta.12.

    ]]>
    https://stepansuvorov.com/blog/2017/01/new-in-angular-2-3-2-4/feed/ 0
    Angular2: Opaque токены и мультипровайдеры https://stepansuvorov.com/blog/2017/01/angular2-opaque-%d1%82%d0%be%d0%ba%d0%b5%d0%bd%d1%8b-%d0%b8-%d0%bc%d1%83%d0%bb%d1%8c%d1%82%d0%b8%d0%bf%d1%80%d0%be%d0%b2%d0%b0%d0%b9%d0%b4%d0%b5%d1%80%d1%8b/ https://stepansuvorov.com/blog/2017/01/angular2-opaque-%d1%82%d0%be%d0%ba%d0%b5%d0%bd%d1%8b-%d0%b8-%d0%bc%d1%83%d0%bb%d1%8c%d1%82%d0%b8%d0%bf%d1%80%d0%be%d0%b2%d0%b0%d0%b9%d0%b4%d0%b5%d1%80%d1%8b/#comments Sat, 21 Jan 2017 18:35:12 +0000 http://stepansuvorov.com/blog/?p=3232 Continue reading ]]> При определении провайдеров рано или поздно у всех у нас возникает вопрос “А что если будет 2 провайдера с одним и тем же именем?” – Ну тут все довольно просто: последний определенный перезатрет все определенные до него.

    Но что если вы подключаете сторонний модуль, а в нем уже определен провайдер с таким же именем? Очевидно, что вы его перезатрете своим. Как избежать этого?

    Opaque токен

    На помощь нам спешит opaque токен, который выглядит немного как костыль архитектуры. Чтобы избежать коллизии, мы заворачиваем имя нашего сервиса в объект с помощью класса OpaqueToken:

    [javascript]
    const MY_HTTP_TOKEN: OpaqueToken = new OpaqueToken(‘Http’);
    [/javascript]

    Если мы посмотрим на исходный код этого класса, то увидим всего одну строчку реализации:

    [javascript]
    export class OpaqueToken {
    constructor(protected _desc: string) {}

    toString(): string { return `Token ${this._desc}`; }
    }
    [/javascript]

    То есть мы просто  заворачиваем наше название в объект, который в случае чего его вернет.

    Что это дает? Какое бы мы дурацкое уникальное имя не придумали для своего провайдера, все равно есть вероятность, что кто-то придумает что-то подобное. А вот при создании нового объекта мы всегда гарантируем что он будет уникальный. И теперь коллизии нам не страшны – можем спокойно внедрять нашу сущность:

    [javascript]
    constructor(@Inject(MY_HTTP_TOKEN) private myHttp){
    [/javascript]

     

    Мультипровайдеры

    Но представим другую ситуацию: вам внезапно захотелось запихнуть в провайдер несколько классов. Зачем? Ну вот захотелось. И чтобы для каждого класса отрабатывал механизм внедрения зависимости и получал сущность. И, да, разработчики предусмотрели и такой случай. Для этого вам нужно использовать мультипровайдеры, а именно специальное свойство multi при определении провайдера:

    [javascript]
    providers: [
    { provide: ‘SuperProvider’, useClass: class A {}, multi: true },
    { provide: ‘SuperProvider’, useClass: class B {}, multi: true}]
    [/javascript]

    И теперь можем инжектить два-в-одном:

    [javascript]
    constructor(@Inject(‘SuperProvider’) private testInjection) {
    [/javascript]

    и получить в testInjection массив из 2х сущностей: экземпляров класса A и B.

    Так, еще раз – зачем это нужно? Если вы пишете библиотеку/плагин и хотите сделать его расширяемым для разработчиков, которые будут подключать ваше решение в свои приложения, думаю это именно то, что вам нужно. Именно так реализованы 2 сервиса хранящие валидаторы в ангуляре, а именно NG_VALIDATORS и NG_ASYNC_VALIDATORS, что позволяет вам добавить свои валидаторы в стандартную коллекцию:

    [javascript]
    { provide: NG_VALIDATORS, useValue: (formControl) => {}, multi: true }
    [/javascript]

    По прежнему остается вопрос: зачем использовать задание провайдеров через строки, если это может привести к коллизии? Оставим его на совести разработчиков Angular 2.

    ]]>
    https://stepansuvorov.com/blog/2017/01/angular2-opaque-%d1%82%d0%be%d0%ba%d0%b5%d0%bd%d1%8b-%d0%b8-%d0%bc%d1%83%d0%bb%d1%8c%d1%82%d0%b8%d0%bf%d1%80%d0%be%d0%b2%d0%b0%d0%b9%d0%b4%d0%b5%d1%80%d1%8b/feed/ 3
    Angular2: Синхронная асинхронность MockBackend https://stepansuvorov.com/blog/2017/01/angular2-%d1%81%d0%b8%d0%bd%d1%85%d1%80%d0%be%d0%bd%d0%bd%d0%b0%d1%8f-%d0%b0%d1%81%d0%b8%d0%bd%d1%85%d1%80%d0%be%d0%bd%d0%bd%d0%be%d1%81%d1%82%d1%8c-mockbackend/ https://stepansuvorov.com/blog/2017/01/angular2-%d1%81%d0%b8%d0%bd%d1%85%d1%80%d0%be%d0%bd%d0%bd%d0%b0%d1%8f-%d0%b0%d1%81%d0%b8%d0%bd%d1%85%d1%80%d0%be%d0%bd%d0%bd%d0%be%d1%81%d1%82%d1%8c-mockbackend/#respond Fri, 20 Jan 2017 08:57:46 +0000 http://stepansuvorov.com/blog/?p=3230 Continue reading ]]> Решил сделать заметку по не очевидному моменту юнит-тестирования Angular2, а именно: когда вы мокаете Http сервис, заменяя XHRBackend на MockBackend:

    [javascript]
    TestBed.configureTestingModule({
    imports: [HttpModule],
    providers: [{provide: XHRBackend, useClass: MockBackend}]
    });
    [/javascript]

    а потом с помощью mockBackend подменяете ответ от сервера:

    [javascript]
    mockBackend.connections.subscribe((connection: MockConnection) => {
    connection.mockRespond(new Response(new ResponseOptions({body: JSON.stringify(mockUser)})));
    })
    [/javascript]

    subscribe на http Observable становится внезапно синхронным:

    [javascript]
    console.log(1);
    this._http.get(‘/’).subscribe((data) => {
    console.log(2);
    });
    console.log(3);
    [/javascript]

    соотвественно выведет: 1 2 3.

    Подробно вопрсос освещен на стеке.

     

    ]]>
    https://stepansuvorov.com/blog/2017/01/angular2-%d1%81%d0%b8%d0%bd%d1%85%d1%80%d0%be%d0%bd%d0%bd%d0%b0%d1%8f-%d0%b0%d1%81%d0%b8%d0%bd%d1%85%d1%80%d0%be%d0%bd%d0%bd%d0%be%d1%81%d1%82%d1%8c-mockbackend/feed/ 0
    Angular2: Что такое AoT? https://stepansuvorov.com/blog/2017/01/angular2-aot/ https://stepansuvorov.com/blog/2017/01/angular2-aot/#respond Wed, 18 Jan 2017 20:54:08 +0000 http://stepansuvorov.com/blog/?p=3221 Continue reading ]]> Что это такое?

    AOT или Ahead-of-Time – вариант компиляции приложения(альтернатива JIT или Just-in-time), которая выполняется один раз при сборке приложения. JIT выполняется каждый раз при запуске приложения в браузере.

     

    Зачем это делать?

    • компиляция шаблонов до сборки (выявление ошибок при сборке)
    • более быстрый запуск приложения
    • скомпилированный файл получается меньше (не нужно включать компилятор в сборку)
    • вопрос безопасности (меньше вероятность внедрения)

     

    Больше цифр

    * взято из презентации Optimizing Angular 2 Apps от Martin Probst.

    Живые цифры (сам проверил 2017-01-18)

    angular-cli стартер без AOT(ng build):

    angular-cli стартер c AOT (ng build –aot):

    Больше информации?

     

     

    ]]>
    https://stepansuvorov.com/blog/2017/01/angular2-aot/feed/ 0
    Angular2: Possible parameters for @HostBinding and @HostListener decorators https://stepansuvorov.com/blog/2017/01/angular2-possible-parameters-for-hostbinding-and-hostlistener-decorators/ https://stepansuvorov.com/blog/2017/01/angular2-possible-parameters-for-hostbinding-and-hostlistener-decorators/#respond Sat, 14 Jan 2017 21:16:53 +0000 http://stepansuvorov.com/blog/?p=3214 Continue reading ]]> @HostBinding(?)
    • propertyName: references a property of the host with the propertyName name.
    • attr.attributeName: references an attribute of the host with the attributeName name. The initial value is set to the associated directive property. Setting a value in the property updates the attribute on the corresponding HTML element. Using the null value at this level removes the attribute on the HTML element.
    • style.styleName: links a directive property to a style of the HTML element.
    • class.className: links a directive property to a class name of the HTML element. If the value is true, the class is added otherwise removed.

    @HostListener(?)

    • eventName: the name of the event to register a method callback on.

    Example for all the cases:

    [javascript]
    @Directive({
    selector: ‘mydir’
    })
    export class SomeDirective {
    @HostBinding(‘value’) value:string;
    @HostBinding(‘attr.role’) role:string;
    @HostBinding(‘style.width.px’) width:number;
    @HostBinding(‘class.someClass’) condition:boolean;
    @HostListener(‘input’) onInput() {
    console.log(‘on input in directive’);
    }
    }
    [/javascript]

     

    ]]>
    https://stepansuvorov.com/blog/2017/01/angular2-possible-parameters-for-hostbinding-and-hostlistener-decorators/feed/ 0
    Шпаргалка по синтаксису шаблонов Angular2 https://stepansuvorov.com/blog/2017/01/%d1%88%d0%bf%d0%b0%d1%80%d0%b3%d0%b0%d0%bb%d0%ba%d0%b0-%d0%bf%d0%be-%d1%81%d0%b8%d0%bd%d1%82%d0%b0%d0%ba%d1%81%d0%b8%d1%81%d1%83-%d1%88%d0%b0%d0%b1%d0%bb%d0%be%d0%bd%d0%be%d0%b2-angular2/ https://stepansuvorov.com/blog/2017/01/%d1%88%d0%bf%d0%b0%d1%80%d0%b3%d0%b0%d0%bb%d0%ba%d0%b0-%d0%bf%d0%be-%d1%81%d0%b8%d0%bd%d1%82%d0%b0%d0%ba%d1%81%d0%b8%d1%81%d1%83-%d1%88%d0%b0%d0%b1%d0%bb%d0%be%d0%bd%d0%be%d0%b2-angular2/#respond Sat, 07 Jan 2017 20:26:30 +0000 http://stepansuvorov.com/blog/?p=3197 Continue reading ]]> взято с официальной документации, переведено/переработано.

    <input [value]=”firstName”> задание свойства value
    <div [attr.role]=”myAriaRole”> задание атрибута role.
    <div [class.extra-sparkle]=”isDelightful”> Выставляет класс extra-sparkle по условию isDelightful
    <div [style.width.px]=”mySize”> задает значение width в пикселях.
    <button (click)=”readRainbow($event)”> Привязывает обраобтчик клика readRainbow
    <p>Hello {{ponyName}}</p>
    <div title=”Hello {{ponyName}}“>
    Подставляет значение выражения
    <my-cmp [(title)]=”name”> Двойное связывание, альтернатива: <my-cmp [title]=”name” (titleChange)=”name=$event”>
    <video #movieplayer …>
    <button (click)=”movieplayer.play()”>
    </video>
    Создает локальную переменную movieplayer, которая является ссылкой на элемент video
    <p *myUnless=”myExpression”>…</p> Символ * превращает текущий элмент во втроенный шаблон: :<template [myUnless]=”myExpression”><p>…</p></template>
    <p>Card No.: {{cardNumber | myCardNumberFormatter}}</p> Применение myCardNumberFormatter пайпа.
    <p>Employer: {{employer?.companyName}}</p> Указание не обязательного поля с помощью оператора “?
    ]]>
    https://stepansuvorov.com/blog/2017/01/%d1%88%d0%bf%d0%b0%d1%80%d0%b3%d0%b0%d0%bb%d0%ba%d0%b0-%d0%bf%d0%be-%d1%81%d0%b8%d0%bd%d1%82%d0%b0%d0%ba%d1%81%d0%b8%d1%81%d1%83-%d1%88%d0%b0%d0%b1%d0%bb%d0%be%d0%bd%d0%be%d0%b2-angular2/feed/ 0
    Как запустить Angular2 ChangeDetection вручную https://stepansuvorov.com/blog/2016/12/%d0%ba%d0%b0%d0%ba-%d0%b7%d0%b0%d0%bf%d1%83%d1%81%d1%82%d0%b8%d1%82%d1%8c-angular2-changedetection-%d0%b2%d1%80%d1%83%d1%87%d0%bd%d1%83%d1%8e/ https://stepansuvorov.com/blog/2016/12/%d0%ba%d0%b0%d0%ba-%d0%b7%d0%b0%d0%bf%d1%83%d1%81%d1%82%d0%b8%d1%82%d1%8c-angular2-changedetection-%d0%b2%d1%80%d1%83%d1%87%d0%bd%d1%83%d1%8e/#respond Tue, 06 Dec 2016 21:40:52 +0000 http://stepansuvorov.com/blog/?p=3175 Continue reading ]]> Небольшая заметка по мотивам ответа на stackoverflow.

    Внедрив следующие сервисы в компонент

    ApplicationRefNgZoneChangeDetectorRef,

    мы можем добиться следующего:

    • ApplicationRef.tick() – то есть запуск changeDetection на корневом элементе, то есть соотвественно запуск на всех элементах дерева(аналог  $rootScope.$digest() )
    • NgZone.run(callback) – тоже самое что и предыдущий вариант, только с выполнением колбэка (аналог $rootScope.$apply(callback) )
    • ChangeDetectorRef.detectChanges() – запуск механизма обнаружения изменений только на текущем элементе ( аналог $scope.$digest() )

    Ну и так, бонусом: отсоединение и присоединение обратно детектора изменений конкретного компонента:

    [javascript]
    constructor(private cd: ChangeDetectorRef) {
    this.cd.detach();
    setInterval(() => {
    this.cd.detectChanges();
    }, 5000);
    }
    [/javascript]

     

     

     

    ]]>
    https://stepansuvorov.com/blog/2016/12/%d0%ba%d0%b0%d0%ba-%d0%b7%d0%b0%d0%bf%d1%83%d1%81%d1%82%d0%b8%d1%82%d1%8c-angular2-changedetection-%d0%b2%d1%80%d1%83%d1%87%d0%bd%d1%83%d1%8e/feed/ 0