AngularJs – Stepan Suvorov Blog https://stepansuvorov.com/blog Release 2.0 Thu, 23 May 2019 15:09:41 +0000 en-US hourly 1 https://wordpress.org/?v=6.3.1 Знаю ли я AngularJS? https://stepansuvorov.com/blog/2017/05/%d0%b7%d0%bd%d0%b0%d1%8e-%d0%bb%d0%b8-%d1%8f-angularjs/ https://stepansuvorov.com/blog/2017/05/%d0%b7%d0%bd%d0%b0%d1%8e-%d0%bb%d0%b8-%d1%8f-angularjs/#comments Sun, 21 May 2017 20:06:00 +0000 http://stepansuvorov.com/blog/?p=2963 Continue reading ]]> Меня попросили составить список вопросов, которые смогут показать высокий уровень ваших знаний или же выявить какие-то пробелы.

1 Как получить конструктор-метод директивы из консоли?

2 Как создать новый скоуп снаружи ангуляра (не находясь внутри сущности)?

3 Чем отличается service, factory, provider?

4 Как “выключить” вотчер?

5 Как использовать экземпляр сервиса внутри app.config? (Ангуляр допускает использование только провайдеров, при прямом внедрении)

6 В базе данных хранится ангуляровское выражение, как вывести его результат на фронтенд?

7 Что будет если определить 2 сущности с одним и тем же именем: для модуля, сервиса, директивы?

8 Если у вас есть директива1 и вложенная в нее директива2, то в каком порядке будут запущены controller и link методы этих директив?

9 Какой тип scope создает ui-view директива? (ui-router модуль)

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

]]>
https://stepansuvorov.com/blog/2017/05/%d0%b7%d0%bd%d0%b0%d1%8e-%d0%bb%d0%b8-%d1%8f-angularjs/feed/ 6
Angular + ES6 Promise https://stepansuvorov.com/blog/2016/09/angular-es6-promise/ https://stepansuvorov.com/blog/2016/09/angular-es6-promise/#comments Mon, 12 Sep 2016 17:43:29 +0000 http://stepansuvorov.com/blog/?p=3125 Continue reading ]]> It would be really nice if we could use native ECMAScript 2015 Promises with Angular instead of $q service that is provided from box to be close to pure JavaScript:

[javascript]
//somewhere inside component controller
let promise = new Promise((resolve) => setTimeout(() => resolve(‘resolved’), 2000));
promise.then(x => this.x = x);
[/javascript]

But in this case we will have to run digest manually for each resolve(to synchronise view and model):

[javascript]
let promise = new Promise((resolve) => setTimeout(() => resolve(‘resolved’), 2000));
promise.then(x => {
$scope.apply();
this.x = x;
});
[/javascript]

But what if we hack the Promise and intercept our digest call there:

[javascript]
class SubPromise extends Promise {
constructor(executor) {
super(function(_resolve, _reject) {
var resolve = (data) => {
var res = _resolve(data);
angular.element(document.body).injector().get(‘$rootScope’).$apply();
return res;
}
return executor(resolve, _reject);
});
}
}
[/javascript]

now we just need to overwrite standard Promise:

[javascript]
window.Promise = SubPromise;
[/javascript]

to keep it simple to cover for unit-tests you can also wrap it into an angular factory:

[javascript]
factory(‘Promise’, () => Promise);
[/javascript]

Here you can play with the code.

Discussion on stackoverflow about customising ES6 Promise.

!Attention: This experiment was made just for learning purposes and it should not be applied for the real projects.

]]>
https://stepansuvorov.com/blog/2016/09/angular-es6-promise/feed/ 2
ui-router: Deprecated events and hooks https://stepansuvorov.com/blog/2016/07/ui-router-deprecated-events-and-hooks/ https://stepansuvorov.com/blog/2016/07/ui-router-deprecated-events-and-hooks/#comments Thu, 28 Jul 2016 19:28:50 +0000 http://stepansuvorov.com/blog/?p=3093 Continue reading ]]> If you are using latest version of ui-router (now it’s 1.0.0-alpha) you probably already noticed that there are no events that we all got used to: $stateChangeCancel,  $stateChangeError, $stateChangeStart$stateChangeSuccess$stateNotFound. All of them are deprecated in 1.0.0-alpha.3 and you can not use them anymore in 1.0.0-alpha.5.

If you want to move to new version of ui-router you should make such changes:

$stateChangeCancel

It was an event that was thrown when somebody called “event.preventDefault()”. Now everything is controlled by transitions so no chance to get such event.

$stateChangeError

old code

[javascript]
$rootScope.$on(‘$stateChangeError’, function(event, toState) {
//some code to handle the error
})
[/javascript]

new code

[javascript]
$transitions.onError({ to: ‘stateName’ }, function($error$) {
console.log($error$);
}
[/javascript]

in progress

$stateChangeStart

old code

[javascript]
$rootScope.$on(‘$stateChangeStart’, function(event, toState) {
event.preventDefault();
})
[/javascript]

new code

[javascript]
$transitions.onEnter({ to: ‘stateName’ }, function($state$, $transition$) {
return $q.reject()
}
[/javascript]

$stateChangeSuccess

old code

[javascript]
$rootScope.$on(‘$stateChangeSuccess’, function(event) {})
[/javascript]

new code

[javascript]
$transitions.onSuccess({}, () => {});
[/javascript]

$stateNotFound

old code

[javascript]
$rootScope.$on(‘$stateNotFound’, function(event) {})
[/javascript]

new code

[javascript]
$stateProvider.onInvalid(($to$, $from$) => {})
[/javascript]

 

Sandbox to play with all examples.

]]>
https://stepansuvorov.com/blog/2016/07/ui-router-deprecated-events-and-hooks/feed/ 4
Angular Material WYSWYG Editors https://stepansuvorov.com/blog/2016/06/angular-material-wyswyg-editors/ https://stepansuvorov.com/blog/2016/06/angular-material-wyswyg-editors/#respond Wed, 01 Jun 2016 18:42:40 +0000 http://stepansuvorov.com/blog/?p=3089 Screen Shot 2016-06-01 at 20.40.16

List of WYSWYG Editors that you can use with Angular Material. Thanks to Tim Brown.

]]>
https://stepansuvorov.com/blog/2016/06/angular-material-wyswyg-editors/feed/ 0
ui-router: Default child for abstract state https://stepansuvorov.com/blog/2016/05/ui-router-default-child-for-abstract-state/ https://stepansuvorov.com/blog/2016/05/ui-router-default-child-for-abstract-state/#comments Sat, 28 May 2016 15:08:06 +0000 http://stepansuvorov.com/blog/?p=3078 Continue reading ]]> In version 1.0.0alpha0 they finally make it possible! Child for abstract states? No! But now at least it’s possible to create own fix for it due to new $transitionsProvider, in which you could define onBefore hook. You can change the behaviour depends on state options. Let’s use “abstract” property that is boolean and extend it: to make it possible to add child state here:

[javascript]
$transitionsProvider.onBefore({
to: state => !!state.abstract
}, ($transition$, $state) => {
if (angular.isString($transition.to().abstract)) {
return $state.target($transition.to().abstract);
}
});
[/javascript]

basically if abstract param is a string we set it like a target. Example of use:

[javascript]
.state({
name: ‘abstract2’,
url: ‘/abstract2’,
abstract: ‘abstract2.foo’, // redirect to ‘abstract2.foo’
template: ‘abstract2’
})
[/javascript]

to cover more complex case we could set it like a function:

[javascript]
$transitionsProvider.onBefore({
to: state => !!state.abstract
}, ($transition$, $state, $injector) => {
if (angular.isFunction($transition.to().abstract)) {
return $state.target($injector.invoke($transition.to().abstract))
}
});
[/javascript]

and an example for an abstract state with child that is defined by function:

[javascript]
.state({
name: ‘abstract3’,
url: ‘/abstract3’,
abstract: function() {
return ‘abstract3.foo’
}, // Function redirect
template: ‘abstract3’
})
[/javascript]

All code for experiments is available on plunker.

]]>
https://stepansuvorov.com/blog/2016/05/ui-router-default-child-for-abstract-state/feed/ 1
Understanding of component/directive attribute binding types https://stepansuvorov.com/blog/2016/02/understanding-of-componentdirective-attribute-binding-types/ https://stepansuvorov.com/blog/2016/02/understanding-of-componentdirective-attribute-binding-types/#comments Tue, 23 Feb 2016 20:29:02 +0000 http://stepansuvorov.com/blog/?p=3036 Continue reading ]]> You probably know that directive component can have 4 different attribute-scope bindings:

  • @” – bind a local scope property to the value of DOM attribute
  • =” – set up a bidirectional binding between a local scope property and an expression passed via the attribute (also for collection “=*” and “=?” if attribute is optional)
  • <” – set up a one-way (one-directional) binding between a local scope property and an expression passed via the attribute
  • &” – provides a way to execute an expression in the context of the parent scope

So I decided to uncover magic of this symbols and recreate their functionality by using attributes from the link function.

Let’s take an example of a directive with all binding types:

[javascript]
app.directive(‘myDir’, function() {
return {
scope: {
x: ‘@’,
y: ‘=’,
z: ‘<‘,
f: ‘&’
},
template
};
});
[/javascript]

template could be something like this:

<div>{{x}} <input ng-model="x"></div>
<div>Hello {{y}}  <input ng-model="y"></div>
<div>Hello {{z}}  <input ng-model="z"></div>
<div>Hello {{f()}}  <input></div>

and integration is just:

<my-dir x="Hello {{name}}" y="name" z="name" f="getName()"></my-dir>

Now we will create directive without all this scope magic param notation, but with the same binding functionality, just by using attributes of link function:

<my-dir2 x="Hello{{name}}" y="name" z="name" f="getName()"></my-dir2>

and the definition:

[javascript]
app.directive(‘myDir2’, function($interpolate, $parse) {
return {
scope: {},
template,
link: function(scope, element, attrs) {}
};
});
[/javascript]

Inside the link function we will use scope.$parent – link to parent scope and attrs – directive attributes.

@

Value binding is simple, only thing that we need to do is to observe attribute and update the value:

[javascript]
attrs.$observe(‘x’, value => scope.x = value);
[/javascript]

to have immediate access inside link function we should probably also add:

[javascript]
scope.x = $interpolate(attrs.x)(scope.$parent);
[/javascript]

we are using $interpolate service here that will parse expression that you put into you attribute( attrs.$observe will do it by default).

<

One way binding. The same logic like we did for first one, only in this case we need to use $watch instead of $observe (now it’s property, not attribute change) and $parse instead of $interpolate(only one property not the expression):

[javascript]
scope.$watch(() => $parse(attrs.z)(scope.$parent), newParentValue => scope.z = newParentValue);
[/javascript]

and again to make it accessible in link function:

[javascript]
scope.z = $parse(attrs.z)(scope.$parent);
[/javascript]

=

Two-way binding. The most difficult one, because you need to synchronise value of the parent scope as well. Comparing to previous one you also need to store lastValue to understand which value has been updated: parent or directive one.

So first lest just try to parse the attribute:

[javascript]
expressionFn = $parse(attrs.y);
[/javascript]

after we can get attribute value

[javascript]
scope.y = expressionFn(scope.$parent)
[/javascript]

and store it like a lastValue:

[javascript]
lastValue = scope.y;
[/javascript]

now we should setup a watcher to check whether something was changed:

[javascript]
scope.$watch(() => {
let parentValue = expressionFn(scope.$parent);
if (angular.equals(parentValue, scope.y)) {
return;
}
});
[/javascript]

so if parentValue and scope.y are the same we finish the function, but if not we need to synchronise either parent or directive scope. How to define whine one to synchronise? We will make comparison with lastValue:

[javascript]
if (!angular.equals(parentValue, lastValue)) {
scope.y = parentValue;
}
[/javascript]

if they are not equal – we should sync directive scope property, otherwise – parent property. But how to do it? It could be done with help of special assign method:

[javascript]
expressionFn.assign(scope.$parent, parentValue = scope.y);
[/javascript]

And now all together:

[javascript]
let expressionFn = $parse(attrs.y);
let lastValue = scope.y = expressionFn(scope.$parent);
scope.$watch(() => {
let parentValue = expressionFn(scope.$parent);
if (angular.equals(parentValue, scope.y)) {
return;
}

if (!angular.equals(parentValue, lastValue)) {
scope.y = parentValue;
} else {
expressionFn.assign(scope.$parent, parentValue = scope.y);
}

return lastValue = parentValue;
});
[/javascript]

 

&

Execute with parent scope context. Like a piece of cake:

[javascript]
scope.f = (locals) => $parse(attrs.f)(scope.$parent, locals);
[/javascript]

 

All these examples were shown only to explain you what’s happening inside Angular and you should not use scope.$parent instead of attribute binding notation.

]]>
https://stepansuvorov.com/blog/2016/02/understanding-of-componentdirective-attribute-binding-types/feed/ 1
ng-nl 2016. brief review https://stepansuvorov.com/blog/2016/02/ng-nl-2016-brief-review/ https://stepansuvorov.com/blog/2016/02/ng-nl-2016-brief-review/#comments Fri, 19 Feb 2016 04:17:25 +0000 http://stepansuvorov.com/blog/?p=3024 Continue reading ]]> ng-nl2106 label

This year I also visited NG-NL Conference in Amsterdam and to keep the tradition will share my notes/thoughts about it.

Basically the conference was dedicated to looking deep in Angular2 parts and reactive programming experiments.

All the topics were split into 2 tracks. You can find all the program details here. My route was the following:

ng-nl-schedule

Because keynote talks were not separated and columns were not sync in time you could get the illusion of time jump.

Awesome Productivity with Angular 2 (video)

Martin Probst started keynote with kind of excuse why we need to do step forward -“frameworks have to move” – that sounds really funny. Probably it was soft launch or preparation not to scary developers with new features.

After it was quick introduction of all new things in Angular2. New template syntax that gets rid of tons nested directives and uses explicit attribute binding, and you always know whether it is property, event or two-way binding:

  • [property] – property binding
  • (event) – event binding
  • [(two-way)] – two-way biding (still confusing)

Some words about the component concept that will kill old directives, scopes, controllers. Exact announcement about performance: Angular2 is 4-times faster than Angular1.

Martin also said that team works not only on framework but also on the environment. To make dev tools unified and productive. So angular-cli.

And it was a part why we should use TypeScript:

  • makes code more reliable
  • much better doc generation
  • allows autocompletion
  • gives safer API changes
  • fast API renaming

And, of course, the statement that we can easily use Angular2 without TypeScript is not true, using Angular2 without TypeScript is hard pain: even examples on official site are not always up-to-date.

Well, it was quite informative, thank you!

ngUpgrade Your App to Angular 2 (slides coming soon, video coming soon)

It was light talk by Tero Parviainen who showed the step by step migration to Angular2 with help of ngUpgrade.

When you have ideal decoupled component application you can start with ngUpgrade:

[javascript]
import {UpgradeAdapter} from ‘angular2/upgrade’;

var adapter = new UpgradeAdapter();
var app = angular.module(‘myApp’, []);

adapter.bootstrap(document.body, [‘myApp’]);
[/javascript]

and to have new components in old project you should you such hack:

[javascript]
app.directive(‘productDetail’,
adapter.downgradeNg2Component(ProductDetail));
[/javascript]

and the same goes for services:

[javascript]
adapter.addProvider(ProductService);
[/javascript]

But when you code is not ideal (that we usually call real life) you have to solve all architectural issues first and only then do ngUpgrade manipulations.

Fist step could be to remove all ng-include and ng-controller statements and replace them into components. For more detailed instructions check this Tero’s article.

@Tero, the way of presenting information by commenting code videos works perfectly!

 

How to Write a Library for Angular 2 (slides, video coming soon)

Olivier Combe was making fast coding talk about steps of Angular2 library creation.

First of all it would be better to make workshop out of this talk, to let everyone do the same steps. Because when you don’t do it simultaneously all the steps looks so obvious.

I know Olivier, that’s not his first conference in speaker role, but he was nervous and all talk was in a hurry, it looked like he’d prepared presentation for an hour and was told to make it in 15 minutes.

There were some hint how to setup Karma preprocessors for TypeScript.

Did not know about .nmpignore file before.

@Olivier, it would be really nice addition if you put some code and commands into your presentation.

 

Angular 2 Change Detection Explained (slides, video)

Couldn’t miss Pascal Precht talk and switched to track2 for it. Should also say that room was overcrowded.

The talk started with explaining the problem that we want to solve – basically it’s Model – DOM synchronisation. (recommended reading “Change And Its Detection In JavaScript Frameworks” by Tero Parviainen) Model could be changed by:

  • events (click, submit)
  • ajax requests and fetching data from remote source
  • timers

and how Angular gets known about it? –  ZONES!

Some explanation about async flow magic and back to zones:

[javascript]
zone.run(() => {
foo();
setTimeout(doSth, 0);
bar();
});
[/javascript]

and for Angular2 it’s ngZone.

More details in Understanding Zones and Zones in Angular 2.

Smart change detection with immutable objects: it’s easy to check whether object has changed or not.

ChangeDetectionStrategy.OnPush – where you could setup logic and do not update component each time.

Observables to setup change “listener” only for specific component.

@Pascal, waiting for your superduper demo examples code :)

 

Lunch

During lunch brake I had a chance to speak to Igor Minar and Martin Probst, asked them about TypeScript like an extra complexity to move a project to Angular2. It looks like they are going solid for it, even with future ES7 and decorators. So if you want to use Angular2 you MUST learn TypeScript. It was a good advice from Martin to try TypeScript with current Angular1.X version and only after, when we are confident enough move to Angular2 and use it together. Let’s see how far we will go.

 

Reactive Angular 2 (slides, video)

It was life coding with Rob Wormald. He lifted the curtain of reactive programming. We made Typeahead component with Observables, it was really nice example to show how to put everything in one stream. Then we created chat app with firebase. And after it was Todo app with Redux. Third one was really difficult to follow.

Related example from Rob suddenly found on internet – plnk.

 

Introduction to RxJS 5 (slides, video)

More reactive stuff from Gerard Sans. History of streams: pipes(unix, 1973) – > streams(node.js, 2009) -> observables (Microsoft, 2012) ->observables (Angular2, 2014). Some obvious things that you can do with array in ES5 like forEach, map, filter, reduce. Examples with Observables. Subscribe. No debugger support yet.

RxJs could be implementation in Angular2 for:

  • asynchronous processing
  • http
  • forms: controls, validation
  • component events

…and again Typeahead component example (plnk), now with nicer styles.

 

Angular 2 and the Single Immutable State Tree (slides, video coming soon)

Talk of Ciro Nunes about immutables has a lot in common with Pascal’s “Angular 2 Change Detection Explained”.

pure function – returns new object.

ChangeDetectionStrategy.OnPush

Redux – unidirectional data flow.

Angular implementation

[javascript]
const appStore = createStore(rootReducer);
bootstrap(App, [
provide(‘AppStore’, {userValue: appStore}),
Actions
]);
[/javascript]

or

@Ciro, it looks like 3rd link under presentation is broken.

 

The new Component Router for Angular 2 and 1.x (slides, video coming soon)

Manfred Steyer started with some kind of fast slides, some of them with German notation.

Router LIfe Cycle Callbacks:

  • onActivate
  • onDeactivate
  • canDeactivate
  • @canActivate

Nice concept of honest authorisation with checkbox only :)

Lazy loading for components is there.

Router part inside each component:

[javascript]
app.component(‘bookFlight’, {
controller: BookFlightController,
templateUrl: ‘components/book-flight/book-flight.html’,
$routeConfig: [
{ path: ‘/’, component: ‘passenger’, name: ‘Passenger’ },
{ path: ‘/flight’, component: ‘flight’, name: ‘Flight’, useAsDefault: true },
{ path: ‘/booking’, component: ‘booking’, name: ‘Booking’ },
{ path: ‘/passenger/:id’, component: ‘passengerEdit’, name: ‘PassengerEdit’ }
]
});

[/javascript]

Access control could be done via @canActivate:

[javascript]
app.component(‘passengerEdit’, {
controller: PassengerEditController,
template: passengerEditTemplate,
$canActivate: () => {
console.debug("$canActivate");
return true;
}
});
[/javascript]

Can be installed in Angular1 via ng-controller, unfortunately did not find this place in your code.

Link to router config examples: Angular2 and Angular1.5

@Manfred, I think it should be some piece of code that goes after each model slide for better understanding. Second thing – we don’t need ControllerAs for Angular1.5 anymore.

 

Rendering in Angular 2  (slides, video)

Leonardo Zizzamia presented one more topic about trying to solve problem of synchronising Model and UI.

Render decoupling to make it possible to use different renders. Make it possible to have the same code base, same templates for different platforms.

Canvas Root Render

Angular2 Rendering is still in beta

and nice app – https://getplan.co based on Angualr1.4.9 (still migrating to 2)

 

Which Way Is Up? (video)

Todd Motto told us a nice story about Jerry, a pirat-packman-developerJerry js

I’ll not retell you the story, better wait for the video ;)

Todd just assembled all our doubts and fears about JS boom(what technology to use?), open source and participating in community life.

Some lessons from the story:

  • don’t be scared of new technologies
  • but don’t try to use them all at once, just keep an eye on them
  • contribute to open source projects – the great way of knowledge sharing
  • you know it well only when you can explain it well – write a blog, explain your thoughts
  • and keep in mind:

Nothing was achieved in comfort zone!

]]>
https://stepansuvorov.com/blog/2016/02/ng-nl-2016-brief-review/feed/ 6
AngularJS: от directive() к component() https://stepansuvorov.com/blog/2016/02/angularjs-%d0%be%d1%82-directive-%d0%ba-component/ https://stepansuvorov.com/blog/2016/02/angularjs-%d0%be%d1%82-directive-%d0%ba-component/#comments Wed, 17 Feb 2016 21:06:11 +0000 http://stepansuvorov.com/blog/?p=3019 Continue reading ]]> пост был подготовлен на основе статьи Exploring the Angular 1.5 .component() method от Todd Moto.

В Angular 1.5 нам был представлен новый метод .component(), который намного проще чем .directive() и при этом он использует все лучшее по умолчанию. Метод .component() также позволит разработчикам писать в Angular2 стиле, то есть сделает переход на вторую версию максимально безболезненным.

В этом посте мы попробуем параллельно разобрать старый и новый подходы для создания компонентов.

На примере простой директивы counter мы посмотрим как можно создать компонент с аналогичной функциональностью:

[javascript]
.directive(‘counter’, function counter() {
return {
scope: {},
bindToController: {
count: ‘=’
},
controller: function () {
function increment() {
this.count++;
}
function decrement() {
this.count–;
}
this.increment = increment;
this.decrement = decrement;
},
controllerAs: ‘counter’,
template: [

<div class="todo">’,
‘<input type="text" ng-model="counter.count">’,
‘<button type="button" ng-click="counter.decrement();">-</button>’,
‘<button type="button" ng-click="counter.increment();">+</button>’,
‘</div>

].join(”)
};
});
[/javascript]

вот как это будет выглядеть:

Объект вместо Функции

Начнем наш анализ со способа задания и обратим внимание на то, что параметры в компонент передаются как объект (а не функция, что было в директиве):

[javascript]
// до
.directive(‘counter’, function counter() {
return {
};
});

// после
.component(‘counter’, {

});
[/javascript]

Scope и BindToController становятся просто Bindings

В директиве мы можем задавать scope 3-мя способами: родительский(скоуп не создается), наследник от родительского, изолированный. Со временем мы приходим к выводу, что изолированный скоуп, где мы четко задаем входящие параметры, наилучший вариант. Так же каждый раз для изолированного скоупа нам приходиться прописывать bindToController, чтобы прокинуть данные со скоупа непосредственно на контроллер директивы.

Свойство компонента bindings позволяет использовать 2 в одном, так как компонент использует изолированный скоуп по умолчанию:

[javascript]
// before
.directive(‘counter’, function counter() {
return {
scope: {},
bindToController: {
count: ‘=’
}
};
});

// after
.component(‘counter’, {
bindings: {
count: ‘=’
}
});
[/javascript]

Controller и ControllerAs

Ничего не изменилось в способе задания контроллера, однако теперь controllerAs параметр по умолчанию, который задан как “$ctrl“: то есть если мы в контроллере напишем:

[javascript]
this.x = 5;
[/javascript]

то в шаблоне компонента потом можно будет обратиться вот так:

[html]
<div>{{$ctrl.x}}</div>
[/html]

Итак, что у нас получилось с контроллером для обоих случаев:

[javascript]
// до
.directive(‘counter’, function counter() {
return {
scope: {},
bindToController: {
count: ‘=’
},
controller: function () {
function increment() {
this.count++;
}
function decrement() {
this.count–;
}
this.increment = increment;
this.decrement = decrement;
},
controllerAs: ‘counter’
};
});

// после
.component(‘counter’, {
bindings: {
count: ‘=’
},
controller: function () {
function increment() {
this.count++;
}
function decrement() {
this.count–;
}
this.increment = increment;
this.decrement = decrement;
}
});
[/javascript]

Я очень упростил для понимания пункт из статьи, поэтому рекомендую также заглянуть в оригинал.

Шаблоны

В определении шаблонов есть небольшое различие: шаблон компонента может задаваться как функция, в которую инжектятся элемент и атрибуты:

[javascript]
{

template: function ($element, $attrs) {
// access to $element and $attrs
return ‘…’;
}

}
[/javascript]

Улучшенное require

Да, это свершилось! Теперь мы можем задать имя для контроллера, подключаемого к нашему компоненту, и обратиться к нему из контроллера( до этого только из метода link, а в контроллер оно попадало только путем ужасных костылей):

[javascript]
{

require: {
parent: ‘^parentComponent’
},
controller: function () {
// use this.parent to access required Objects
this.parent.foo();
}

}
[/javascript]

В данном случае мы определили подключаемый контроллер на свойстве parent.

Одностороннее связывание

Еще одна фишка компонентов и Angular1.5 это одностороннее связывание, которое определяется следующим синтаксисом:

[javascript]
{

bindings: {
oneWay: ‘<‘,
twoWay: ‘=’
},

}
[/javascript]

если мы задали свойство oneWay таким образом, то оно будет реагировать на изменения внешнего связанного объекта, при этом свои изменения передавать “наружу” не будет. И да, сразу отвечу на вопрос, который у вас наверное появился: работает только в одну сторону.

Нет никакого нового концепта

Если вы посмотрите на исходный код, то увидите что разработчики AngularJS особо не парились и сделали метод component() просто оболочкой над directive().

Обновляемся до Angular2

Как уже было сказано: использование метода .component() серьезно упростит переход на Angular2. Посмотрите как будет выглядеть ваш компонент во второй версии фреймворка(конечно, с новым синтаксисом шаблонов):

[javascript]
var Counter = ng
.Component({
selector: ‘counter’,
template: [

<div class="todo">’,
‘<input type="text" [(ng-model)]="count">’,
‘<button type="button" (click)="decrement();">-</button>’,
‘<button type="button" (click)="increment();">+</button>’,
‘</div>

].join(”)
})
.Class({
constructor: function () {
this.count = 0;
},
increment: function () {
this.count++;
},
decrement: function () {
this.count–;
}
});
[/javascript]

]]>
https://stepansuvorov.com/blog/2016/02/angularjs-%d0%be%d1%82-directive-%d0%ba-component/feed/ 10
AngularJS советы от команды PayPal https://stepansuvorov.com/blog/2015/11/angularjs-%d1%81%d0%be%d0%b2%d0%b5%d1%82%d1%8b-%d0%be%d1%82-%d0%ba%d0%be%d0%bc%d0%b0%d0%bd%d0%b4%d1%8b-paypal/ https://stepansuvorov.com/blog/2015/11/angularjs-%d1%81%d0%be%d0%b2%d0%b5%d1%82%d1%8b-%d0%be%d1%82-%d0%ba%d0%be%d0%bc%d0%b0%d0%bd%d0%b4%d1%8b-paypal/#comments Sun, 08 Nov 2015 10:09:15 +0000 http://stepansuvorov.com/blog/?p=2874 Continue reading ]]> Перевод/формат статьи “Sane, scalable Angular apps are tricky, but not impossible. Lessons learned from PayPal Checkout.”

(Очень радует, что на зло всем критикам появляется все больше и больше серьезных приложений на AngularJS)

Все нижеизложенные рекомендации касается только Angular 1.x, мы не говорим по второй версии фреймворка, для которой вероятно существуют свои рекомендации.

Не используйте ng-controller

ng-controller – это что-то типа тумблера для быстрого включения магии Ангуляра, вы добавляете атрибут на страницу – и вы на коне: все сразу стало динамическим, теперь можно использовать переменные из scope:

<div ng-controller="myController">
  <strong>{{foo}}</strong>
</div>
    • Этот паттерн не заставляет нас делать строгое связывание контроллера с шаблоном, которое свойственно для компонент-ориентированных приложений
    • При таком подходе вы можете использовать контроллер только в одном месте и только с определенными правилами того, что вы помещаете в $scope
  • Существует слишком много в корне отличных способов подключения контроллера к приложению, что само по себе создает сложности в последующем разборе кода
[javascript]
$routeProvider.
      when('/phones', {
        templateUrl: 'partials/phone-list.html',
        controller: 'PhoneListCtrl'
      });
[/javascript]

В чем проблема? Это работает только для стейтов. Если я захочу использовать мой компонент в месте, не связанном ни с одним из стейтов, то мне придется использовать уже другой паттерн подключения контроллера.

Так как же избежать такой неоднородности? Как выбрать один правильный вариант, и ответ..

Делайте все используя директивы

Серьезно, абсолютно все, включая страницы целиком:

[javascript]
myapp.directive('foo', function() {
    return {
        scope: {},
        template: myTemplate,
        controller: function($scope) {
            $scope.foo = 'bar';
        }
    };
});
[/javascript]

Теперь мы можем использовать компонент <foo></foo> для любых случаев:

  • это всегда гарантирует использование одного и того же контроллера и шаблона
  • вы можете передавать атрибуты для изменения поведения
  • вы можете определить коллбэк, который будет вызван, когда в вашем элементе произойдет какое-то событие
  • он имеет изолированный скоуп, то есть: вам не нужно переживать за лексическое окружение родителя

Но как тогда настроить роутер? Да вообще-то довольно просто:

[javascript]
$routeProvider.
      when('/phones', {
        template: '&amp;amp;amp;amp;lt;phone-list&amp;amp;amp;amp;gt;&amp;amp;amp;amp;lt;/phone-list&amp;amp;amp;amp;gt;',
      });
[/javascript]

Теперь все приложение в одном компоненте, включая страницу целиком, и при этом все ваши компоненты сделаны единообразно. Это серьезно повышает читабельность, понятность кода, а главное – его повторное использование.

Всегда используйте изолированный scope

Директивы по умолчанию наследуют родительский скоуп. Это не самая лучшая архитектура. Мы рекомендуем использовать изолированный скоуп:

scope: {}

гарантируя тем самым, что мы не можем случайно изменить родительский контекст. Это правило помогает нам избавиться от случайных абстракций между компонентом и его родителем.

Еще одна забавная вещь предложенная нам AngularJS:

scope: true

которая создает новый скоуп для директивы, но при этом прототипно наследуя его от родительского. С точки зрения компонентно-ориентированого приложения это ужасно. Просто используйте изолированный скоуп.

Связывайте свойство объекта, а не сам объект

AngularJs предоставляет двойное связывание через ng-model. Но при этом вот так лучше не делать:

<input type="text" ng-model="username" />

Видите проблему? Нет? А она есть! Суть в том, что вы пытаетесь связать свойство родительского скоупа, и при этом не обязательно понимаете какой из скоупов это будет.

Это уже где только не обсуждалось, отметим лишь только то, что, если вы так делаете, вы обрекаете себя на проблемы, которые будет трудно отладить в будущем. Например: для того же случая с ng-if, когда вы создаете новый скоуп и уже совсем не понятно будет ли ваш код продолжать дальше работать или нет.

Так как гарантировать, что ng-model всегда связана со скоупом вашей директивы? Просто запомнить:

  1. Создавайте объект-контейнер внутри вашей директивы
  2. Связывайте ваш инпут со свойством этого объекта, но не свойством объекта $scope

то есть:

<input type="text" ng-model="user.name" />

(прим.: либо всегда используйте синтаксис Controller As)

Ограничьте использование $rootScope

Вы когда нибудь слышали, что глобальные переменные – это зло? Так вот представьте, что $rootScope – это тоже глобальная переменная. По мере того как ваше приложение растет, становится все сложнее отслеживать помещенные туда свойства.

Не храните значение состояния там, лучше..

Храните состояние как можно ближе к элементу

Если у вас есть 3 компонента на странице и они используют одни и те же данные, то просто заверните их в еще один компонент и определите данные на полученном скоупе.

Это очень просто сделать с директивами, определив какие параметры вы хотите принять:

[javascript]
myapp.directive('foo', function() {
    return {
        scope: {
            bar: '='
        },
        template: myTemplate,
        controller: function($scope) {
            console.log($scope.bar);
        }
    };
});
[/javascript]

И передать их соответственно:

<foo bar="baz"></foo>

Забудьте о сервисах и провайдерах

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

Просто используйте фабрики(factories) для всего:

[javascript]
myapp.factory('foo', function() {
  return something;
});
[/javascript]

И теперь вы можете задать foo как зависимость, и фабрика вернет функцию, константу, объект или что-то еще.

Это все что вам нужно знать, когда вы начинаете работать с сервисами.

Забудьте о module.config

Существует не так много строк кода, которые действительно должны быть помещены в секцию module.config. Как правило все настройки могут легко уместиться в секции module.run, и даже, собрав все воедино в run, должно получиться не много кода, потому что за исключением настроек роутера вся инициализация UI может быть выполнена внутри главной(top) директиве страницы.

Разработчики AngularJS зачем-то сделали отличие в использовании ‘provider‘ по сравнению с остальными сервисами(service, factory, config, value). Только провайдеры могут быть доступны на этапе конфигурации, и  только там.

Мой совет: просто забудьте что существуют провайдеры и блоки конфигурации.

Исключением могут быть только встроенные сервисы AngularJS, которые можно настраивать только с помощью провайдеров, например $routeProvider.

Будьте осторожны с использованием событий на scope

Когда у вас есть 2 компонента, которые должны обмениваться данными, вы наверное думаете, что наиболее удобный способ реализовать – это будет генерировать события в одном элементе и считывать в другом (используя $scope.$emit/$broadcast и $scope.$on).

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

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

Да, конечно, существуют, задачи, которые можно и нужно решать с помощью событий, например: когда вы переходите из одного стейта в другой и хотите показать индикатор загрузки, достаточно просто отправить событие ‘loading‘. Но это скорее исключение.

Почувствуйте удобство работы с $exceptionHandler

Это то место, где собираются все не отловленные ошибки вашего приложения. Angular оборачивает все котроллеры, сервисы, промисы и все остальное в блок try/catch и предоставляет глобальный обработчик исключительных ситуаций.

По умолчанию этот обработчик лишь выводит в консоль, но вы наверняка захотите предоставить пользователю вашего приложения страницу ошибки, или хотя бы какое-то сообщение в UI, вместе с регистрацией ошибки на сервере в базе.

Логирование ошибок на сервере

Мы для себя сделали довольно простой буфер, что отправляет накопившиеся лог-сообщения по изменению стейта а также по временному интервалу. Это позволяет следить за всем, что происходит на стороне клиента, при этом не делая 100500 запросов.

Очень надеемся в скором времени выложить наш буфер в опен-сорс. Это крайне необходимый инструмент для анализа ошибок на стороне клиента в продакшене.

Используйте ui-router

У Angular есть свой роутер, но он предоставляет только базовую функциональность и работает исключительно с плоской структурой стейтов.

В свою очередь angular-ui-router позволяет делать неограниченное количество вложенных роутов. Наверняка вам уже приходилось делать что-то типа переключения между вложенными вью, так вот в ui-router это можно сделать из коробки. И так же важно, что все стейты хранятся в одном месте.

Например: в PayPal Checkout у нас такая структура:

  • Корневой стейт
    • Просмотр оплаты
      • Сайдбар
        • Добавление новой кредитной карточки

С использованием ui-router получится следующий url:

#/checkout/review/sidebar/addcard

и при перезагрузки страницы или возвращении назад на эту страницу мы всегда знаем где юзер должен находиться без потери фокуса.

Будьте осторожны с промисами и обработкой ошибок

Промисы используются практически везде в AngularJS, и вы сталкиваетесь с ними с первого же $http запроса. Однако, в некоторых случаях они ведут себя довольно коварно. В первую очередь это связано с тем, что нет единого потока выполнения и нет единого места обработки всех невыполненных промисов.  Рассмотрим такой пример:

[javascript]
$http.get('/foo/bar').then(function(result) {
  console.log('Success!', result);
});
[/javascript]

 в этом случае мы не перехватываем возможную ошибку. Важно не лениться всегда обрабатывать отмененные промисы:

[javascript]
$http.get('/foo/bar').then(function(result) {
  console.log('Success!', result);
}).catch(function(err) {
  console.error(err.stack);
});
[/javascript]

Но на этом история не заканчивается. Обычно в случае появлении ошибки промис просто отменяется. Однако в AngularJS есть различие между отмененным промисом и ошибкой/исключением (thrown error). Если промис отменен, то есть возвращается $q.reject(err), я могу легко отловить ошибку и обработать так, как я захочу с помощью catch(). А вот в случае исключительной ситуации, когда что-то кидает ошибку с помощью throw, то Angular ведет себя довольно неожиданно: да, промис отменяется, как и в стандартной ситуации, и вызывается метод catch(), но даже если есть catch() метод, то ошибка все равно будет отправлена в $exceptionHandler.

То есть, если вы обрабатываете ошибки в обоих местах ($exceptionHandler и catch()), то получится так, что вы обработаете одну и ту же ошибку дважды.

Чтобы уменьшить количество таких случаев, мы вам рекомендуем использовать  “throw” в асинхронных операциях только для случаев, которые нельзя обработать. Для бизнес логики – только $q.reject().

Постарайтесь избегать ленивой загрузки (lazy loading)

Ангулар позволяет регистрировать модули (и соответственно все его составляющие: сервисы, директивы..) только на этапе инициализации приложения. Существуют различные решения, как это можно обойти. Но, поверьте, оно того не стоит.

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

Будьте осторожны с циклом дайджеста(digest cycle)

Я не буду вам рассказывать о производительности AngularJS, сказано уже достаточно. Но общее понимание принципов работы цикла дайджеста действительно важно, когда вы обновляете что-то в scope.

В общем случае: Ангуляр запускает дайджест, чтобы проверить что изменилось в объекте. А, так как объект не имеет своих наблюдателей (observers), то фремворку придется проходить весь объект.

И это может привести к коварной ошибке, когда в $scope у вас есть метод, который каждый раз возвращает новый объект. Например:

[javascript]
$scope.foo = function() {
  return {bar: 'baz'};
};
[/javascript]

и Angular будет вызывать этот метод снова и снова, так как будет думать что скоуп каждый раз обновляется. И появится фатальная ошибка превышения допустимого рекурсивного количества вызовов дайджеста.

Лучше сделать так:

[javascript]
var data = {bar: 'baz'};
$scope.foo = function() {
    return data;
};
[/javascript]

Это вроде бы все. Соблюдение этих правил позволило нам создать относительно масштабируемое приложение с большим числом компонентов, которые могут использоваться в разных местах, и что так же немаловажно – мы используем единый подход к решению однотипных ситуаций.

Надеемся эти советы помогут вам минимизировать количество проблем связанных с расширением вашего AngularJS приложения.

]]>
https://stepansuvorov.com/blog/2015/11/angularjs-%d1%81%d0%be%d0%b2%d0%b5%d1%82%d1%8b-%d0%be%d1%82-%d0%ba%d0%be%d0%bc%d0%b0%d0%bd%d0%b4%d1%8b-paypal/feed/ 7
AngularJS: Зачем котроллеру ngModel нужны $formatters и $parsers https://stepansuvorov.com/blog/2015/11/angularjs-%d0%b7%d0%b0%d1%87%d0%b5%d0%bc-%d0%ba%d0%be%d1%82%d1%80%d0%be%d0%bb%d0%bb%d0%b5%d1%80%d1%83-ngmodel-%d0%bd%d1%83%d0%b6%d0%bd%d1%8b-formatters-%d0%b8-parsers/ https://stepansuvorov.com/blog/2015/11/angularjs-%d0%b7%d0%b0%d1%87%d0%b5%d0%bc-%d0%ba%d0%be%d1%82%d1%80%d0%be%d0%bb%d0%bb%d0%b5%d1%80%d1%83-ngmodel-%d0%bd%d1%83%d0%b6%d0%bd%d1%8b-formatters-%d0%b8-parsers/#comments Wed, 04 Nov 2015 17:50:36 +0000 http://stepansuvorov.com/blog/?p=2893 Continue reading ]]> Небольшая заметка о том, для чего нужны $formatters и $parsers в контроллере ngModel директивы и когда их можно применять.

Вкратце:

  • $formatters определяют как модель будет представлена во вью
  • $parsers определяют как значения из вью будет записаны в модель

Сразу же пример кода:

[javascript]
//model -> view
ngModel.$formatters.push(function(modelValue) {
return modelValue.toUpperCase();
});

//view -> model
ngModel.$parsers.push(function(viewValue) {
return viewValue.toLowerCase();
});
[/javascript]

Полный пример кода тут.

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

Кроме форматирования ввода/вывода мы можем использовать парсер-форматеры также для проверки данных:

[javascript]
ngModel.$parsers.unshift(function checkForEven(viewValue){
if (parseInt(viewValue)%2 === 0) {
ngModel.$setValidity(‘evenNumber’,true);
}
else{
ngModel.$setValidity(‘evenNumber’, false);
}
return viewValue;
});
[/javascript]

в данном случае мы разрешаем использовать только четные числа.

Если нужно валидировать не только данные вводимые в инпут, но и данные передаваемые во вью из модели, то эту же проверку необходимо добавить и в форматеры. Оба случая можно посмотреть тут.

Мы можем задавать как один парсер/форматер, так и не сколько, и они будут выполнены в порядке, в котором они указаны в массиве, именно поэтому, если вы хотите чтобы валидаторы выполнились первыми мы делаем не push(), а unshift().

]]>
https://stepansuvorov.com/blog/2015/11/angularjs-%d0%b7%d0%b0%d1%87%d0%b5%d0%bc-%d0%ba%d0%be%d1%82%d1%80%d0%be%d0%bb%d0%bb%d0%b5%d1%80%d1%83-ngmodel-%d0%bd%d1%83%d0%b6%d0%bd%d1%8b-formatters-%d0%b8-parsers/feed/ 1