Продвинутый

ngular JS

Современный веб

Три слона:

  • HTML
  • CSS
  • JS

HTML

HTML – декларативный язык разметки документов.

HTML DOM – объектная модель документа

<html>
	<head>
		<meta>
		<meta>
		<title>Test title</title>
		<link ...>
	</head>
	<body>
		<div>
			<img>
			<h1></h1>
			<p></p>
			<div></div>
		</div>
		<script>
		</script>
	</body>
</html>

Список дел

<!DOCTYPE html>
<html>
<head>
<title>Список дел</title>
</head>
<body>
    <div>
        <header>Список дел на день</header>
        <ul>
            <li>Составить список дел на день</li>
	    <li>Выполнить первый пункт списка</li>
	    <li>Осознать, что два пункта уже выполнено</li>
	    <li>Отдохнуть</li>
	</ul>
    </div>
</body>
</html>

CSS

CSS – декларативный язык описания внешнего вида.

.articles-tree-block {
	height: 100%;
	overflow: hidden;
	width: 30%;
	float: left;
	padding-left: 10px;
	padding-top: 20px;
	padding-bottom: 10px;
	position: relative;
}

Добавим немного CSS

@CHARSET "UTF-8";
* { box-sizing: border-box; }
body {
	text-align: center;
	font-size: 16px;
	background-color: #f5f5f5;
}
.todo-list {
	margin-top: 30px;
	display: inline-block;
}
.todo-list>header {
	text-transform: uppercase;
	font-weight: bold;
}
ul {
	text-align: left;
	list-style: none;
	padding: 0px;
}
li {
	border: 1px solid grey;
	background-color: #FFFFFF;
	padding: 5px;
	margin: 4px;
}
li.checked {
	text-decoration: line-through;
	color: #b9b9b9;
}
<div class="todo-list">
    <header>Список дел на день</header>
    <ul>
        <li class="checked">Составить список дел на день</li>
	<li class="checked">Выполнить первый пункт списка</li>
	<li>Осознать, что два пункта уже выполнено</li>
	<li>Отдохнуть</li>
    </ul>
</div>

JavaScript

Простой скриптовый язык для добавления динамики на страницы

function myFunction() {
    var x, text;

    // Get the value of the input field with id="numb"
    x = document.getElementById("numb").value;

    // If x is Not a Number or less than one or greater than 10
    if (isNaN(x) || x < 1 || x > 10) {
        text = "Input not valid";
    } else {
        text = "Input OK";
    }
    document.getElementById("demo").innerHTML = text;
}

Добавим немного JS

<body onload="init()">
    <div class="todo-list">
	<header>Список дел на день</header>
	<ul id="tasks">
	</ul>
    </div>
</body>
<script src="app.js"></script>
var tasks = [{
  text: 'Составить список дел на день',
  checked: true
}, {
  text: 'Выполнить первый пункт списка',
  checked: true
}, {
  text: 'Осознать, что два пункта уже выполнено'
}, {
  text: 'Отдохнуть'
}];

function init() {
  var list = document.getElementById('tasks');
  for (var i = 0; i < tasks.length; i++) {
    var task = tasks[i];
    var elem = document.createElement('li');
    elem.textContent = task.text;
    if (task.checked) {
      elem.className = 'checked';
    }
    list.appendChild(elem);
  }
}

Angular JS

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

Особенности:

  • Привязки данных
  • Директивы – швейцарский нож для управления DOM
  • Поддержка форм и их валидации
  • Построение компонентой архитектуры

Основы ангуляра

  • Шаблоны – обычный HTML с дополнительной разметкой

  • Директивы – особые элементы разметки

  • Scope'ы – контекст, связывающий шаблон и контроллер

  • Контроллеры – js-функции, управляющие представлением

  • Сервисы – js-объекты, хранящие бизнес-логику

  • Представление – скомпилированный шаблон

  • Внедрение зависимостей – механизм связывания между собой сервисов, контроллеров и т. д.

  • Выражения – инструмент вставки данных из scope'а в шаблон

Что за?..

Всё очень просто.

Шаблон

Компилятор

Контроллер

Представление

Сервисы

Директивы

Выражения

Директивы

Специальный элемент ангуляра, предназначенный для манипуляций с DOM.

Некоторые встроенные директивы:

  • ng-app
  • ng-controller
  • ng-repeat
<body ng-app="app" ng-controller="controller">
    <ul>
        <li ng-repeat="item in items">{{item}}</li>
    </ul>
</body>

ng-app

Директива, определяющая область действия ангуляра. Все элементы-потомки связываются с соответствующим приложением ангуляра

<body ng-app="taskApp">
    ...
</body>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.js"></script>
angular.module('taskApp', [])

После привязки в шаблонах можно использовать контроллеры, директивы и т. д. из приложения

ng-controller

Директива, привязывающая контроллер к элементу, создаёт общий scope.

Контроллер – js-функция, заполняющая модель данными и коллбэками

angular.module('taskApp', []).controller('taskController', controller)

function controller($scope) {
  $scope.tasks = [{
    text: 'Составить список дел на день',
    checked: true
  }, {
    text: 'Выполнить первый пункт списка',
    checked: true
  }, {
    text: 'Осознать, что два пункта уже выполнено'
  }, {
    text: 'Отдохнуть'
  }];
}
<body ng-app="taskApp" ng-controller="taskController">
</body>

ng-repeat и выражения

Директива, которая создает новый элемент по шаблону для каждого элемента коллекции из модели

<body ng-app="taskApp" ng-controller="tasksController">
    <div class="todo-list">
	<header>Список дел на день</header>
	<ul>
	    <li ng-repeat="task in tasks" ng-class="task.checked ? 'checked' : ''">
                {{task.text}}
            </li>
	</ul>
    </div>
</body>

Выражение – js-код внутри {{ }}, результат выполнения которого будет вставлен вместо выражения

Сервисы

Сервисы содержат бизнес-логику, не зависящую от представления, например, откуда берутся данные.

angular.module('taskApp', []).service('taskService', taskService)
.controller('tasksController', tasksController)

function tasksController($scope, taskService) {
  $scope.tasks = taskService.getTasks();
}

function taskService() {
  var tasks = [{
    text: 'Составить список дел на день',
    checked: true
  }, {
    text: 'Выполнить первый пункт списка',
    checked: true
  }, {
    text: 'Осознать, что два пункта уже выполнено'
  }, {
    text: 'Отдохнуть'
  }];

  return {
    getTasks: function() {
      return tasks;
    }
  }
}

Привязка функций контроллера к элементам управления

Ряд директив для привязки событий интерфейса к функциям контроллера:

  • ng-click
  • ng-keypressed
  • ng-changed
  • ng-submit
  • ng-change

Добавим отметку о выполнении задачи

<li ng-repeat="task in tasks" ng-class="{'checked': task.checked}">
    <input type="checkbox" ng-checked="task.checked" ng-click="taskStateChanged(task)">
    {{task.text}}
</li>
function tasksController($scope, taskService) {
  $scope.taskStateChanged = taskStateChanged;

  function taskStateChanged(task) {
    taskService.taskStateChanged(task);
  }
}

function taskService() {
  return {
    taskStateChanged: function(task) {
      task.checked = !task.checked;
    }
  }
}

Добавим создание задачи

<form ng-submit="addTask()">
    <input type="text" ng-model="newTaskText" placeholder="Добавить задачу">
</form>
angular.module('taskApp', [])
.service('taskService', taskService)
.controller('tasksController', tasksController)

function tasksController($scope, taskService) {
  $scope.addTask = function() {
    if ($scope.newTaskText) {
      taskService.addTask($scope.newTaskText);
      $scope.newTaskText = '';
    }
  }
}

function taskService() {
  return {
    addTask: function(text) {
      var task = {
        text: text,
        checked: false
      };
      tasks.push(task);
    }
  }
}

Angular Routing

Ангуляр позволяет переключаться между различными представлениями на основе URL страницы

angular-route/#/

angular-route/#/1

$routeProvider

angular.module('taskApp', ['ngRoute'])

.config(routeConfig);

function routeConfig($routeProvider) {
  $routeProvider

  .when('/', {
    templateUrl: 'views/taskList.html',
    controller: 'tasksController'
  })

  .when('/:id', {
    templateUrl: 'views/taskCard.html',
    controller: 'taskController',
    resolve: {
      task: function($route, taskService) {
        return taskService.getTask($route.current.params.id);
      }
    }
  })

  .otherwise({
    redirectTo: '/'
  });
}

Основные элементы роута:

  • Шаблон
  • Контроллер
  • Resolve

ngRoute – модуль роутинга

Модификации html

1. Подключить библиотеку angular-route

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-route.js"></script>

2. Добавить директиву ng-view, которая показывает, в какой элемент будет вставлен шаблон текущего роута

<body ng-app="taskApp" ng-view>
</body>

3. Создать новые шаблоны

<h3>{{task.text}}</h3>
<div ng-if="task.checked" class="task-done">Выполнено!</div>
<div ng-if="!task.checked" class="task-undone">Не выполнено!</div>
<div class="todo-list">
    <header>Список дел на день</header>
    <ul>
	<li ng-repeat="task in tasks" ng-class="task.checked ? 'checked' : ''">
	    <input type="checkbox" ng-checked="task.checked" ng-click="taskStateChanged(task)">
	    {{task.text}}
	    <button ng-click="openTask(task)">></button>
	</li>
    </ul>
    <form ng-submit="addTask()">
	<input type="text" ng-model="newTaskText" placeholder="Добавить задачу">
    </form>
</div>

taskCard.html

taskList.html

Правим сервис и контроллеры

1. Добавляем в сервис задач метод получения задачи по id

  getTask: function(id) {
    for (var i = 0; i < tasks.length; i++) {
      if (tasks[i].id == id) { return tasks[i]; }
    }
    return null;
  }

2. Добавляем контроллер для представления задачи

angular.module('taskApp', ['ngRoute'])
.controller('taskController', taskController);

function taskController($scope, task) {
  $scope.task = task;
}

Результат

Директивы

Их мощь может сравниться только с их сложностью.

Возможности:

  • Можно использовать как элемент, атрибут, css-класс
  • Можно прикреплять шаблон и контроллер
  • Можно взаимодействовать с другими директивами
  • Можно создавать изолированный scope
  • Transclusion (что-то невменяемо крутое)
  • Можно вмешиваться в процесс компиляции шаблона
  • Можно манипулировать с DOM после компиляции шаблона

Объявление директивы

angular.module('taskApp')
.directive('tasks', taskDirective);

function taskDirective() {
  return {
    controller: tasksController,
    templateUrl: 'views/taskList.html',
    restrict: 'E'
  }
}

directive – метод объявления директивы, принимает название и функцию, возвращающую объект определения директивы

Атрибуты директивы

  • multiElement – для создания директив-интервалов
  • priority – управляет порядком компиляции директив
  • terminal – отмена компиляции менее приоритетных директив
  • scope – параметры создания нового scope
  • bindToController – для биндинга свойств scope к контроллеру
  • controller – контроллер шаблона директивы
  • require – директивы, необходимые для нашей директивы
  • controllerAs – для указания псевдонима контроллера
  • restrict – режимы использования директивы
  • templateNamespace – тип разметки шаблона
  • template – строка с шаблоном директивы
  • templateUrl – URL шаблона директивы
  • transclude – включение transclusion
  • compile – функция для изменения процесса компиляции шаблона
  • link – функция для обработки скомпилированного шаблона

restrict

restrict определяет, как можно будет использовать директиву в шаблонах.

Варианты использования:

  • Как html-тэг – E
  • Как html-аттрибут – A
  • Как css-класс – C
  • Внутри комментария – M
restrict: 'EAC' // использовать и как тэг, и как атрибут, и как css-класс
restrict: 'E' // только как тэг
restrict: 'EA' // как тэг или как аттрибут
<!-- Как тэг -->
<bar-chart></bar-chart>

<!-- Как атрибут -->
<canvas bar-chart></canvas>

<!-- Как css-класс -->
<canvas class="bar-chart"></canvas>

<!-- Как комментарий -->
<!-- directive: bar-chart -->
<canvas></canvas>

template и templateUrl

angular.module('taskApp')
.directive('tasks', taskDirective);

function taskDirective() {
  return {
    templateUrl: 'views/taskList.html',
    restrict: 'E'
  }
}
<div class="todo-list">
    <header>Список дел на день</header>
    <ul>
	<li ng-repeat="task in tasks">
	    {{task.text}}
	</li>
    </ul>
</div>
<tasks></tasks>

+

+

<tasks>
    <div class="todo-list">
        <header>Список дел на день</header>
        <ul>
    	    <li ng-repeat="task in tasks">
    	        {{task.text}}
    	    </li>
        </ul>
    </div>
</tasks>

=

Иерархия скоупов

controller

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

  • обладает дополнительными локальными переменными
  • может обрабатывать дополнительные события
  • может использоваться для взаимодействия директив
angular.module('taskApp')
.directive('tasks', taskDirective);

function taskDirective() {
  return {
    controller: tasksController,
    templateUrl: 'views/taskList.html',
    restrict: 'E'
  }
}

function tasksController($scope, taskService, $location) {
  $scope.tasks = taskService.getTasks();
  $scope.openTask = openTask;
  $scope.addTask = addTask;
}

controllerAs

controllerAs – механизм, используемый для чёткого отделения свойств контроллера от остальных объектов scope

<div class="todo-list">
    <header>Список дел на день</header>
    <ul>
        <li ng-repeat="task in ctrl.tasks">
            {{task.text}}
            <button ng-click="ctrl.openTask(task)">
            </button>
        </li>
    </ul>
    <form ng-submit="ctrl.addTask()">
        <input type="text" ng-model="newTask">
    </form>
</div>
angular.module('taskApp')
.directive('tasks', taskDirective);

function taskDirective() {
  return {
    controller: tasksController,
    controllerAs: 'ctrl',
    templateUrl: 'views/taskList.html',
    restrict: 'E'
  }
}

function tasksController(taskService, $location) {
  var self = this;
  self.tasks = taskService.getTasks();
  self.addTask = addTask;

  function addTask() {
    if (self.newTask) {
      var task = taskService.addTask(self.newTask);
      self.tasks.push(task);
      self.newTask = '';
    }
  }
}

Изолированный scope

Недостатки наследования scope:

  • возможные коллизии имён
  • неявное прокидывание данных
  • нарушение инкапсуляции

Решение – изолированный scope директивы

Для активации – атрибут директивы scope 

scope: {
   tasks: '<',
   openTask: '&',
   addTask: '&'
}

Виды биндинга:

  • @ – односторонний биндинг текста
  • < – односторонний биндинг js-объекта
  • & – биндинг callback-функции
  • = – двусторонний биндинг

Пример изолированного scope

return {
  controller: tasksController,
  controllerAs: 'ctrl',
  templateUrl: 'views/taskList.html',
  restrict: 'E',
  scope: {
    tasks: '<',
    openTask: '&',
    addTask: '&'
  },
  bindToController: true
}
function tasksController() {
  var self = this;
  self.add = function() {
    self.addTask({
      text: self.newTask
    });
    self.newTask = null;
  };

  self.open = function(task) {
    self.openTask({
      task: task
    });
  };
}
  .when('/', {
    template: '<tasks tasks="ctrl.tasks" add-task="ctrl.add(text)"'
              + 'open-task="ctrl.open(task)"></tasks>',
    controller: tasksViewCtrl,
    controllerAs: 'ctrl'
  })
function tasksViewCtrl(taskService, $location) {
  var self = this;
  self.tasks = taskService.getTasks();
  self.add = function(text) {
    // adding task
  };
  self.open = function(task) {
    $location.path(task.id);
  }
}

link

link – функция для выполнения операций после компиляции шаблона.

Обычно используется для модификации DOM и привязки функций к событиям DOM-элементов

var cm;

function link(scope, elem, attrs, controller) {
  var cmConfig = {
    mode: attrs.mode || 'text/plain',
    theme: 'dracula',
    lineWrapping: true,
    lineNumbers: true,
    autofocus: true
  };
  cm = CodeMirror(elem[0].children[0].children[1], cmConfig);
  cm.on("change", digest);
  controller.cm = cm;
  controller.digest = digest;

  function digest() {
    if (!$rootScope.$$phase) {
      scope.$digest();
    }
  }
}

Директивы?

Компоненты!

Введены:            Angular 1.5

Цель:                   упростить разработку UI-компонентов

Способ:               ввести новое API, в которое добавить 

                              необходимый минимум из API директив

Особенности:    изолированный scope

                              автоматический controllerAs

                              bindToController всегда равен true

                              может быть только элементом

Объявление

.component('tasks', {
  controller: tasksController,
  controllerAs: 'ctrl',
  templateUrl: 'views/taskList.html',
  bindings: {
    tasks: '<',
    openTask: '&',
    addTask: '&'
  }
});

Метод component, принимающий название компонента и объект определения компонента

Атрибуты

  • controller – контроллер шаблона
  • controllerAs – псевдоним контроллера, по умолчанию $ctrl
  • template – строка с шаблоном
  • templateUrl – URL шаблона
  • bindings – внешние зависимости компонента
  • require – требуемые директивы
  • transclude – Transclusion (и тут!)

Директивы или компоненты?

Директивы – для операций с DOM

Компоненты – для связи шаблона с контроллером

Что же в результате?

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

angular.module('taskApp')

.component('tasks', {
  controller: tasksController,
  controllerAs: 'ctrl',
  templateUrl: 'views/taskList.html',
  bindings: {
    tasks: '<',
    openTask: '&',
    addTask: '&'
  }
});

function tasksController() {
  var self = this;

  self.add = function() {
    self.addTask({
      text: self.newTask
    });
    self.newTask = null;
  };

  self.open = function(task) {
    self.openTask({
      task: task
    });
  };

}

Экстра

Несколько фич ангуляра, которые могут помочь при разработке

Отслеживание изменений

$scope.$watch(obj, callback)

Ангуляр отслеживает изменение переданного объекта

function ctrl($scope) {
    var self = this;
    $scope.$watch(getOriginData, dataChanged);

    function getOriginData() {
        return self.data;
    }

    function dataChanged() {
        self.myData = filter(data);
    }
}

Использование геттеров и сеттеров в контроллерах с механизмом controllerAs

function Ctrl() {
}

Ctrl.prototype = {
    set data(data) {
        this.myData = this.filter(data);
    }
}

Два типа отношений scope'ов

Родитель-потомок:

Иерархия scope'ов, образуемая с помощью свойств parent и children scope'ов

Прототипное наследование:

каждый scope в качестве прототипа имеет своего родителя

Обычные scope'ы – оба типа отношений

Изолированные scope'ы – только отношения родитель-потомок

Система событий ангуляра

Методы:

  • $broadcast – отправить сообщение всем дочерним scope'ам
  • $emit – отправить сообщение всем родительским scope'ам
  • $on – подписывает на конкретное сообщение

$emit

$broadcast

$http

Сервис ангуляра по выполнению AJAX-запросов

Каждый метод возвращает Promise

Методы:

  • $http.get
  • $http.head
  • $http.post
  • $http.put
  • $http.delete
  • $http.jsonp
  • $http.patch
angular.module('taskApp')
.service('taskService', taskService);

function taskService($http) {

  this.getTasks = function() {
    return $http.get('/rest/tasks/').then(returnData);
  };

  this.addTask = function(task) {
    return $http.post('/rest/tasks/', task).then(returnData);
  };

  this.getTask = function(id) {
    return $http.get('/rest/tasks/' + id).then(returnData);
  };

  this.updateTask = function(task) {
    return $http.put('/rest/tasks/' + id, task).then(returnData);
  };

  function returnData(resposne) {
    return response.data;
  }
}

Слайды с мастер-класса

Слайды с мастер-класса

body (ng-app) : $rootScope

div (ng-view) : $scope
Свойства: tasks, changeState, open, add

li (ng-repeat) : $scope

Свойство: task

li (ng-repeat) : $scope

Свойство: task

li (ng-repeat) : $scope

Свойство: task

li (ng-repeat) : $scope

Свойство: task

li (ng-repeat) : $scope

Свойство: task

Спасибо за внимание!

Made with Slides.com