├── .eslintrc ├── .gitignore ├── .textlintrc ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── assets └── logos │ ├── angular-small.svg │ ├── angular.svg │ ├── angularjs-small.svg │ ├── react-small.svg │ ├── react.svg │ ├── vue-small.svg │ └── vue.svg ├── index.js ├── package-lock.json └── package.json /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "parserOptions": { 4 | "sourceType": "module", 5 | "ecmaVersion": 6, 6 | "ecmaFeatures": { 7 | "jsx": true 8 | } 9 | }, 10 | "extends": "eslint:recommended", 11 | "env": { 12 | "es6": true, 13 | "browser": true 14 | }, 15 | "globals": { 16 | "angular": true, 17 | "React": true, 18 | "Vue": true, 19 | "require": true 20 | }, 21 | "plugins": [ 22 | "react" 23 | ], 24 | "rules": { 25 | "camelcase": "error", 26 | "comma-dangle": ["error", "always-multiline"], 27 | "indent": ["error", 2], 28 | "new-cap": "error", 29 | "no-console": 0, 30 | "no-else-return": "error", 31 | "no-multi-spaces": "error", 32 | "no-var": "error", 33 | "no-whitespace-before-property": "error", 34 | "no-unused-vars": ["error", { "varsIgnorePattern": "React"}], 35 | "quotes": ["error", "single"], 36 | "semi": ["error", "always"] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.textlintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "eslint": { 4 | "configFile": "./.eslintrc", 5 | "langs": ["js", "jsx", "ts"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [angular.js]: ./assets/logos/angularjs-small.svg 2 | [angular]: ./assets/logos/angular-small.svg 3 | [react]: ./assets/logos/react-small.svg 4 | [vue]: ./assets/logos/vue-small.svg 5 | 6 |

Frontend Frameworks Code Comparison

7 | 8 |
9 | 10 | ![./assets/logos/angular.svg](./assets/logos/angular.svg) 11 | ![./assets/logos/react.svg](./assets/logos/react.svg) 12 | ![./assets/logos/vue.svg](./assets/logos/vue.svg) 13 | 14 |
15 | 16 | [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Code%20comparison%20of%20modern%20web%20frameworks%2C%20based%20on%20React%2C%20Angular%20and%20Vue.js&url=https://github.com/feimosi/frameworks-code-comparison) 17 | 18 | Comparison of different approaches in writing web applications. Based on React, Angular, AngularJS and Vue.js. It is especially useful when migrating between frameworks or switching projects often. 19 | 20 | All examples follow the current best practices and conventions that are used inside the community of a given framework. Angular code is written in TypeScript. 21 | 22 |

:warning: Work in progress! PRs and Feedback are welcome :warning:

23 | 24 | > Note regarding framework naming: 25 | > - AngularJS refers to Angular v1.x 26 | > - Angular refers to Angular v2+ 27 | > 28 | > See: http://angularjs.blogspot.com/2017/01/branding-guidelines-for-angular-and.html 29 | 30 |

Table of contents

31 | 32 | * [Table of contents](#table-of-contents) 33 | * [Simple component](#simple-component) 34 | * [Dependency injection](#dependency-injection) 35 | * [Templates](#templates) 36 | * [Interpolation](#interpolation) 37 | * [Handling DOM Events](#handling-dom-events) 38 | * [Inputs and Outputs](#inputs-and-outputs) 39 | * [Default inputs](#default-inputs) 40 | * [Lifecycle methods](#lifecycle-methods) 41 | * [Conditional rendering](#conditional-rendering) 42 | * [Lists](#lists) 43 | * [Filters](#filters) 44 | * [Child nodes](#child-nodes) 45 | * [Transclusion and Containment](#transclusion-and-containment) 46 | * [Inject HTML template](#inject-html-template) 47 | * [Class toggling](#class-toggling) 48 | * [Data binding](#data-binding) 49 | * [Forms](#forms) 50 | * [Styling](#styling) 51 | 52 | # Simple component 53 | 54 | ## ![angular.js] AngularJS 55 | 56 | Since AngularJS 1.5 we have a new syntax (backported from Angular 2) to built [component-based applications](https://docs.angularjs.org/guide/component#component-based-application-architecture) using `component` type. 57 | 58 | ```js 59 | export class ChangePasswordController { 60 | constructor($log, Auth, Notification) { 61 | 'ngInject'; 62 | 63 | this.$log = $log; 64 | this.Auth = Auth; 65 | this.Notification = Notification; 66 | } 67 | 68 | $onInit() { 69 | this.password = ''; 70 | } 71 | 72 | changePassword() { 73 | this.Auth.changePassword(this.password).then(() => { 74 | this.Notification.info('Password has been changed successfully.'); 75 | }).catch(error => { 76 | this.$log.error(error); 77 | this.Notification.error('There was an error. Please try again.'); 78 | }); 79 | } 80 | } 81 | ``` 82 | 83 | Every component has to be declared inside a module. After that, it will be available to every other component. 84 | 85 | ```js 86 | import angular from 'angular'; 87 | import template from './changePassword.html'; 88 | import ChangePasswordController from './changePassword.controller.scss'; 89 | import './changePassword.scss'; 90 | 91 | const component = { 92 | bindings: {}, 93 | template, 94 | controller: ChangePasswordController, 95 | }; 96 | 97 | export const module = angular 98 | .module('app.changePassword', []) 99 | .component('changePassword', component); 100 | ``` 101 | 102 | :link: https://docs.angularjs.org/guide/component 103 | 104 | ## ![angular] Angular 105 | 106 | ```ts 107 | import { Component } from '@angular/core'; 108 | import { Logger } from 'services/logger'; 109 | import { Auth } from 'services/auth'; 110 | import { Notification } from 'services/notification'; 111 | 112 | @Component({ 113 | selector: 'change-password', 114 | templateUrl: './ChangePassword.component.html', 115 | styleUrls: ['./ChangePassword.component.scss'], 116 | }) 117 | export class ChangePasswordComponent { 118 | password: string = ''; 119 | 120 | constructor( 121 | private logger: Logger, 122 | private auth: Auth, 123 | private notification: Notification, 124 | ) {} 125 | 126 | changePassword() { 127 | this.auth.changePassword(this.password).subscribe(() => { 128 | this.notification.info('Password has been changes successfully'); 129 | }).catch(error => { 130 | this.logger.error(error); 131 | this.notification.error('There was an error. Please try again'); 132 | }); 133 | } 134 | } 135 | ``` 136 | 137 | Every component has to be declared inside a module in order to be used within this module's other components (it's not available outside of). 138 | 139 | ```ts 140 | import { NgModule } from '@angular/core'; 141 | import { CommonModule } from '@angular/common'; 142 | 143 | import { ChangePasswordComponent } from './change-password.component'; 144 | 145 | @NgModule({ 146 | imports: [CommonModule], 147 | declarations: [ChangePasswordComponent], 148 | }) 149 | export class ChangePasswordModule {} 150 | ``` 151 | 152 | :link: https://angular.io/api/core/Component 153 | 154 | ## ![react] React 155 | 156 | ```jsx 157 | import Logger from 'utils/logger'; 158 | import Auth from 'actions/auth'; 159 | import Notification from 'utils/notification'; 160 | 161 | export class ChangePassword { 162 | state = { 163 | password: '', 164 | }; 165 | 166 | changePassword() { 167 | Auth.changePassword(this.state.password).then(() => { 168 | Notification.info('Password has been changed successfully.'); 169 | }).catch(error => { 170 | Logger.error(error); 171 | Notification.error('There was an error. Please try again.'); 172 | }); 173 | } 174 | 175 | render() { 176 | return
{ /* template */ }
; 177 | } 178 | } 179 | ``` 180 | 181 | :link: https://reactjs.org/docs/react-component.html 182 | 183 | ## ![vue] Vue.js 184 | 185 | ```js 186 | import Vue from 'vue'; 187 | import Logger from 'utils/logger'; 188 | import Auth from 'actions/auth'; 189 | import Notification from 'utils/notification'; 190 | 191 | Vue.component('change-password', { 192 | template: '
{{ /* template */ }}
', 193 | data() { 194 | return { 195 | password: '', 196 | }; 197 | }, 198 | methods: { 199 | changePassword() { 200 | Auth.changePassword(this.state.password).then(() => { 201 | Notification.info('Password has been changed successfully.'); 202 | }).catch(error => { 203 | Logger.error(error); 204 | Notification.error('There was an error. Please try again.'); 205 | }); 206 | }, 207 | }, 208 | }); 209 | ``` 210 | 211 | :link: https://vuejs.org/v2/guide/components.html 212 | 213 | # Dependency injection 214 | 215 | ## ![angular.js] AngularJS 216 | 217 | In AngularJS the constructor is being used to inject dependencies, which is done implicitly by the [$inject](https://docs.angularjs.org/api/auto/service/$injector) service. 218 | 219 | The `'ngInject'` annotation can be used, which allows automatic method annotation by the ng-annotate plugin (e.g. [ng-annotate-loader](https://www.npmjs.com/package/ng-annotate-loader) for Webpack). This is essential to counter [minification problems](https://docs.angularjs.org/guide/di#dependency-annotation). 220 | 221 | ```js 222 | export class ChangePasswordController { 223 | constructor($log, Auth, Notification) { 224 | 'ngInject'; 225 | 226 | this.$log = $log; 227 | this.Auth = Auth; 228 | this.Notification = Notification; 229 | } 230 | 231 | handleEvent() { 232 | this.Notification.info('Password changed successfully'); 233 | this.$log.info('Password changed successfully'); 234 | } 235 | } 236 | ``` 237 | 238 | :link: https://docs.angularjs.org/guide/di 239 | 240 | ## ![angular] Angular 241 | 242 | You specify the definition of the dependencies in the constructor (leveraging TypeScript's constructor syntax for declaring parameters and properties simultaneously). 243 | 244 | ```ts 245 | import { Component } from '@angular/core'; 246 | import { Logger } from 'services/logger'; 247 | import { Auth } from 'services/auth'; 248 | import { Notification } from 'services/notification'; 249 | 250 | @Component({ 251 | selector: 'change-password', 252 | templateUrl: './ChangePassword.component.html', 253 | }) 254 | export class ChangePasswordComponent { 255 | constructor( 256 | private logger: Logger, 257 | private auth: Auth, 258 | private notification: Notification, 259 | ) {} 260 | 261 | handleEvent() { 262 | this.notification.info('Password changed successfully'); 263 | this.logger.info('Password changed successfully'); 264 | } 265 | } 266 | ``` 267 | 268 | :link: https://angular.io/guide/dependency-injection 269 | 270 | ## ![react] React 271 | 272 | There's no special injection mechanism. ES2015 modules are used for dependency management. 273 | 274 | ```jsx 275 | import React, { Component } from 'react'; 276 | import Logger from 'utils/logger'; 277 | import Notification from 'utils/notification'; 278 | 279 | export class ChangePassword extends Component { 280 | handleEvent = () => { 281 | Notification.info('Password changed successfully'); 282 | Logger.info('Password changed successfully'); 283 | } 284 | 285 | render() { 286 | return
{ /* template */ }
; 287 | } 288 | } 289 | ``` 290 | 291 | ## ![vue] Vue.js 292 | 293 | Vue.js uses ES2015 modules for dependency management: 294 | 295 | ```js 296 | import Vue from 'vue'; 297 | import Logger from 'utils/logger'; 298 | import Notification from 'utils/notification'; 299 | 300 | Vue.component('change-password', { 301 | template: '
{{ /* template */ }}
', 302 | methods: { 303 | handleEvent() { 304 | Notification.info('Password changed successfully'); 305 | Logger.info('Password changed successfully'); 306 | }, 307 | }, 308 | }); 309 | ``` 310 | 311 | There's also [provide and inject](https://vuejs.org/v2/api/#provide-inject) mechanism which is primarily provided for advanced plugin / component library use cases: 312 | 313 | Parent component: 314 | ```js 315 | import Notification from 'utils/notification'; 316 | import Vue from 'vue'; 317 | 318 | new Vue({ 319 | el: '#app', 320 | provide: { 321 | notification: Notification, 322 | }, 323 | }); 324 | ``` 325 | 326 | Child component: 327 | ```js 328 | import Vue from 'vue'; 329 | 330 | Vue.component('change-password', { 331 | inject: ['notification'], 332 | template: '
{{ /* template */ }}
', 333 | methods: { 334 | handleEvent() { 335 | this.notification.info('Event handled successfully'); 336 | }, 337 | }, 338 | }); 339 | ``` 340 | 341 | # Templates 342 | 343 | ## ![angular.js] AngularJS 344 | 345 | Templates in AngularJS are compiled by the [$compile](https://docs.angularjs.org/api/ng/service/$compile) service. 346 | Values of component properties must be one of the following: 347 | - string binding (defined as `@`) 348 | - expression binding (defined as `<`) 349 | - reference binding (defined as `&`) 350 | 351 | ```html 352 | 355 | Save 356 | 357 | ``` 358 | 359 | ## ![angular] Angular 360 | 361 | There are three kinds of attributes that can be passed: 362 | - text binding, e.g. `size="string"` 363 | - property binding, e.g. `[disabled]="value"` 364 | - event binding, e.g. `(click)="eventHandler()"` 365 | 366 | ```html 367 | 370 | Save 371 | 372 | ``` 373 | 374 | ## ![react] React 375 | 376 | Templates in React are written inside the JavaScript file using the [JSX language](https://reactjs.org/docs/jsx-in-depth.html). This allows us to utilize all JavaScript capabilities. JSX uses the uppercase and lowercase convention to distinguish between the user-defined components and DOM elements. 377 | 378 | ```jsx 379 | 384 | Save 385 | ; 386 | ``` 387 | 388 | ## ![vue] Vue.js 389 | 390 | Component properties can be passed in as: 391 | - literal (as strings) e.g. `size="big"` 392 | - dynamic (using [v-bind](https://vuejs.org/v2/api/#v-bind) or [`:shorthand`](https://vuejs.org/v2/guide/syntax.html#v-bind-Shorthand) with actual values) e.g. `v-bind:disabled="true"` 393 | 394 | Events can be listened to using [`v-on`](https://vuejs.org/v2/guide/events.html#Method-Event-Handlers) or [`@shorthand`](https://vuejs.org/v2/guide/syntax.html#v-on-Shorthand) combined with the event name, and a method name as the value, e.g `v-on:click="saveContent"`. 395 | 396 | ```html 397 | 402 | Save 403 | 404 | ``` 405 | 406 | :link: https://vuejs.org/v2/guide/syntax.html 407 | 408 | # Interpolation 409 | 410 | ## ![angular.js] AngularJS 411 | 412 | In AngularJS interpolation is the process of data-binding values of the `scope` to the HTML. You can also interpolate more complicated values e.g. expressions or function invocations. 413 | 414 | 415 | ```html 416 | {{ $ctrl.image.alt }} 417 | ``` 418 | 419 | We use [`ng-src`](https://docs.angularjs.org/api/ng/directive/ngSrc) instead of the regular `src` attribute so that AngularJS can set it up before the browser will try to load the image. 420 | 421 | Another way to "bind" data is to use [`ng-bind`](https://docs.angularjs.org/api/ng/directive/ngBind). This allows us to counter the issue with a raw state being displayed before AngularJS compiles the template. 422 | 423 | ```html 424 | 428 | 429 | ``` 430 | 431 | :link: https://docs.angularjs.org/guide/interpolation 432 | 433 | ## ![angular] Angular 434 | 435 | Angular is similar to AngularJS, so we use double curly braces (`{{ }}`) for interpolation. Since Angular offers property binding you often have a choice to use it [instead of interpolation](https://angular.io/guide/template-syntax#property-binding-or-interpolation). 436 | 437 | ```html 438 | {{ image.alt }} 439 | ``` 440 | 441 | `[src]` presents property binding while the `alt` attribute is being interpolated. 442 | 443 | :link: https://angular.io/guide/template-syntax#interpolation 444 | 445 | ## ![react] React 446 | 447 | React uses single curly braces for interpolation. Any JavaScript can be interpolated. 448 | 449 | ```jsx 450 | {; 451 | ``` 452 | 453 | :link: https://reactjs.org/docs/introducing-jsx.html#embedding-expressions-in-jsx 454 | 455 | ## ![vue] Vue.js 456 | 457 | ```html 458 | {{ image.alt }} 459 | ``` 460 | 461 | You can also perform one-time interpolations that do not update on data change by using the [v-once](https://vuejs.org/v2/api/#v-once) directive, 462 | 463 | ```html 464 | Hello {{ username }}! 465 | ``` 466 | 467 | :link: https://vuejs.org/v2/guide/syntax.html#Interpolations 468 | 469 | # Handling DOM Events 470 | 471 | ## ![angular.js] AngularJS 472 | 473 | Handlers of native events are bound using provided [built-in directives](https://docs.angularjs.org/api/ng/directive) e.g. 474 | [`ng-click`](https://docs.angularjs.org/api/ng/directive/ngClick), [`ng-focus`](https://docs.angularjs.org/api/ng/directive/ngFocus), [`ng-keypress`](https://docs.angularjs.org/api/ng/directive/ngKeypress). 475 | 476 | ```js 477 | class MenuTopbarController { 478 | $onInit() { 479 | this.selected = null; 480 | } 481 | 482 | handleClick(item) { 483 | if (this.selected !== item) { 484 | this.selected = item; 485 | if (typeof item.callback === 'function') { 486 | item.callback(); 487 | } 488 | } 489 | } 490 | } 491 | 492 | const menuTopbar = { 493 | bindings: { 494 | items: '<', 495 | }, 496 | template: require('./menuTopbar.html'), 497 | controller: MenuTopbarController, 498 | }; 499 | 500 | angular.module('app') 501 | .component('menuTopbar', menuTopbar); 502 | ``` 503 | 504 | ```html 505 | 511 | ``` 512 | 513 | :link: https://docs.angularjs.org/tutorial/step_12 514 | 515 | ## ![angular] Angular 516 | 517 | There's a special syntax for binding to element's events with `()`. The target inside the `()` is an event we want to listen for. 518 | 519 | ```ts 520 | export interface MenuItem { 521 | label: string; 522 | callback?: Function; 523 | } 524 | ``` 525 | 526 | ```ts 527 | import { Component, Input, Output, EventEmitter } from '@angular/core'; 528 | import { MenuItem } from './menu-item.interface'; 529 | 530 | @Component({ 531 | selector: 'menu-topbar', 532 | template: require('./menuTopbar.html'), 533 | }) 534 | export class MenuTopbarComponent { 535 | private selected = null; 536 | @Input() items: MenuItem[]; 537 | 538 | handleClick(item: MenuItem) { 539 | if (this.selected !== item) { 540 | this.selected = item; 541 | if (item.callback) { 542 | item.callback(); 543 | } 544 | } 545 | } 546 | } 547 | ``` 548 | 549 | ```html 550 | 556 | ``` 557 | 558 | To bind to component's host element events, you can use [`HostListener`](https://angular.io/api/core/HostListener) decorator. 559 | 560 | ```ts 561 | @Component({ 562 | selector: 'menu-topbar', 563 | template: require('./menuTopbar.html'), 564 | }) 565 | export class MenuTopbarComponent { 566 | @HostListener('mouseenter') onMouseEnter() { 567 | this.highlight('#DDD'); 568 | } 569 | 570 | @HostListener('mouseleave') onMouseLeave() { 571 | this.highlight(null); 572 | } 573 | 574 | private highlight(color) { 575 | /* ... */ 576 | } 577 | ``` 578 | 579 | ## ![react] React 580 | 581 | Handling events with React elements is very similar to handling events on DOM elements. There are two syntactic differences though. 582 | 583 | - React events are named using camelCase, rather than lowercase e.g. (onClick, onFocus, onKeyPress). 584 | - With JSX you pass a function as the event handler, rather than a string. 585 | 586 | Your event handlers will be passed instances of [`SyntheticEvent`](https://reactjs.org/docs/events.html), a cross-browser wrapper around the browser’s native event. It has the same interface as the browser’s native event, including [`stopPropagation()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation) and [`preventDefault()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault), except the events work identically across all browsers. 587 | 588 | ```jsx 589 | import React, { Component } from 'react'; 590 | 591 | export class MenuTopbar extends Component { 592 | state = { 593 | selected: null, 594 | }; 595 | 596 | handleClick(item) { 597 | if (this.selected !== item) { 598 | this.setState({ selected: item }); 599 | if (item.callback) { 600 | item.callback(); 601 | } 602 | } 603 | } 604 | 605 | render() { 606 | return ( 607 | 618 | ); 619 | } 620 | } 621 | ``` 622 | 623 | :link: https://reactjs.org/docs/handling-events.html 624 | 625 | ## ![vue] Vue.js 626 | 627 | We can use the v-on directive (or `@` shorthand) to listen to DOM events and run some JavaScript when they’re triggered. 628 | 629 | Vue also provides event modifiers (directive postfixes denoted by a dot). 630 | 631 | - `.stop` - stopPropagation 632 | - `.prevent` - preventDefault 633 | - `.capture` - use capture mode 634 | - `.self` - only trigger handler if event.target is the element itself 635 | - `.once` - the event will be triggered at most once 636 | 637 | ```js 638 | Vue.component('menu-topbar', { 639 | data: { 640 | selected: null, 641 | }, 642 | methods: { 643 | handleClick: (item) => { 644 | if (this.selected !== item) { 645 | this.selected = item; 646 | if (item.callback) { 647 | item.callback(); 648 | } 649 | } 650 | }, 651 | }, 652 | }); 653 | ``` 654 | 655 | ```html 656 | 665 | ``` 666 | 667 | :link: https://vuejs.org/v2/guide/events.html 668 | 669 | # Inputs and Outputs 670 | 671 | ## ![angular.js] AngularJS 672 | 673 | Inputs are defined by either `@` (string binding) or `<` (one-way binding) while outputs are defined by the `&` symbol. Passing arguments requires you to use an object in a child component which is then mapped to function parameters defined in the template. 674 | 675 | ```js 676 | class UserPreviewComponent { 677 | $onInit() { 678 | this.editedUser = { 679 | name: this.user.name, 680 | email: this.user.email, 681 | }; 682 | } 683 | 684 | submitEdit() { 685 | this.onEdit({ user: this.editedUser }); 686 | } 687 | } 688 | 689 | const component = { 690 | bindings: { 691 | user: '<', 692 | onEdit: '&', 693 | }, 694 | template: require('./userPreview.html'), 695 | controller: UserPreviewComponent, 696 | }; 697 | 698 | export default angular.module.component('userPreview', component); 699 | ``` 700 | 701 | ```html 702 |
703 | 704 | 705 | 706 |
707 | ``` 708 | 709 | In a parent component: 710 | 711 | ```js 712 | class SettingsComponent { 713 | user = { 714 | name: 'John Smith', 715 | email: 'john.smith@example.com', 716 | }; 717 | 718 | editUser(user) { 719 | this.user = Object.assign({}, this.user, user); 720 | console.log('Name of the edited user is', user.name); 721 | } 722 | } 723 | 724 | const component = { 725 | template: require('./settings.html'), 726 | controller: SettingsComponent, 727 | }; 728 | 729 | export default angular.module.component('settings', component); 730 | ``` 731 | 732 | ```html 733 | 735 | 736 | ``` 737 | 738 | ## ![angular] Angular 739 | 740 | Inputs are defined using the [@Input](https://angular.io/api/core/Input) decorator while outputs using the [@Output](https://angular.io/api/core/Output) decorator. 741 | 742 | ```ts 743 | @Component({ 744 | selector: 'user-preview', 745 | template: require('./userPreview.html'), 746 | }) 747 | export class UserPreviewComponent { 748 | private editedUser: User; 749 | @Input() user: User; 750 | @Output() onEdit: EventEmitter = new EventEmitter(); 751 | 752 | ngOnInit() { 753 | this.editedUser = { 754 | name: this.user.name, 755 | email: this.user.email, 756 | }; 757 | } 758 | 759 | submitEdit() { 760 | this.onEdit.emit(this.editedUser); 761 | } 762 | } 763 | ``` 764 | 765 | ```html 766 |
767 | 768 | 769 | 770 |
771 | ``` 772 | 773 | In a parent component: 774 | 775 | ```ts 776 | @Component({ 777 | selector: 'settings', 778 | template: require('./settings.html'), 779 | }) 780 | export class SettingsComponent { 781 | user: User = { 782 | name: 'John Smith', 783 | email: 'john.smith@example.com', 784 | }; 785 | 786 | editUser(user: User) { 787 | this.user = Object.assign({}, this.user, user); 788 | console.log('User has been edited: ', user); 789 | } 790 | } 791 | ``` 792 | 793 | ```html 794 | 797 | ``` 798 | 799 | :link: https://angular.io/guide/component-interaction 800 | 801 | ## ![react] React 802 | 803 | ```jsx 804 | import React, { Component } from 'react'; 805 | import PropTypes from 'prop-types'; 806 | import { User } from 'utils'; 807 | 808 | class UserPreviewComponent extends Component { 809 | submitEdit = () => { 810 | this.props.onEdit({ 811 | name: this.state.name, 812 | email: this.state.email, 813 | }); 814 | }; 815 | 816 | handleInputChange({ target }) { 817 | this.setState({ 818 | [target.name]: target.value, 819 | }); 820 | } 821 | 822 | render() { 823 | return ( 824 |
825 | 831 | 837 | 838 |
839 | ); 840 | } 841 | } 842 | 843 | UserPreviewComponent.propTypes = { 844 | user: PropTypes.instanceOf(User), 845 | onEdit: PropTypes.func, 846 | }; 847 | ``` 848 | 849 | In a parent component: 850 | 851 | ```jsx 852 | import React, { Component } from 'react'; 853 | import { User } from 'utils'; 854 | 855 | export class SettingsComponent extends Component { 856 | state = { 857 | user: { 858 | name: 'John Smith', 859 | email: 'john.smith@example.com', 860 | }, 861 | }; 862 | 863 | editUser(user: User){ 864 | this.setState({ 865 | user: Object.assign({}, this.state.user, user), 866 | }); 867 | console.log('User has been edited: ', user); 868 | } 869 | 870 | render() { 871 | return ( 872 | this.editUser(user) } 875 | /> 876 | ); 877 | } 878 | } 879 | ``` 880 | 881 | ## ![vue] Vue.js 882 | 883 | Child component: 884 | 885 | ```js 886 | Vue.component('child', { 887 | props: { 888 | class: { 889 | type: String, 890 | required: true, 891 | }, 892 | loading: Boolean, 893 | message: String, 894 | }, 895 | template: '', 896 | }); 897 | ``` 898 | 899 | Usage in the parent component: 900 | 901 | ```html 902 | 903 | ``` 904 | 905 | :link: https://vuejs.org/v2/guide/components.html#Props 906 | 907 | # Default inputs 908 | 909 | ## ![angular.js] AngularJS 910 | 911 | There's no built-in mechanism for default inputs, so we assign them programmatically in the `$onChanges` hook. 912 | 913 | ```js 914 | class CoursesListController { 915 | $onChanges(bindings) { 916 | if (typeof bindings.displayPurchased.currentValue === 'undefined') { 917 | this.displayPurchased = true; 918 | } 919 | if (typeof bindings.displayAvailable.currentValue === 'undefined') { 920 | this.displayAvailable = true; 921 | } 922 | } 923 | } 924 | 925 | const component = { 926 | bindings: { 927 | displayPurchased: '<', 928 | displayAvailable: '<', 929 | }, 930 | templateUrl: './coursesList.component.html', 931 | controller: CoursesListController, 932 | }; 933 | 934 | export default component; 935 | ``` 936 | 937 | ## ![angular] Angular 938 | 939 | ```ts 940 | import { Component } from '@angular/core'; 941 | 942 | @Component({ 943 | selector: 'courses-list', 944 | templateUrl: './coursesList.component.html', 945 | }) 946 | export class CoursesListController { 947 | displayPurchased: boolean = true; 948 | displayAvailable: boolean = true; 949 | } 950 | ``` 951 | 952 | ## ![react] React 953 | 954 | ```jsx 955 | import React, { Component } from 'react'; 956 | import PropTypes from 'prop-types'; 957 | 958 | export class CoursesListController extends { Component } { 959 | static propTypes = { 960 | displayPurchased: PropTypes.bool, 961 | displayAvailable: PropTypes.bool, 962 | }; 963 | 964 | static defaultProps = { 965 | displayPurchased: true, 966 | displayAvailable: true, 967 | }; 968 | 969 | render() { 970 | return
{ /* template */ }
; 971 | } 972 | } 973 | ``` 974 | 975 | :link: https://reactjs.org/docs/typechecking-with-proptypes.html#default-prop-values 976 | 977 | ## ![vue] Vue.js 978 | 979 | ```js 980 | import Vue from 'vue'; 981 | 982 | Vue.component('courses-list', { 983 | template: '
{{ /* template */ }}
', 984 | props: { 985 | displayPurchased: { 986 | type: Boolean, 987 | default: true, 988 | }, 989 | displayAvailable: { 990 | type: Boolean, 991 | default: true, 992 | }, 993 | }, 994 | }); 995 | ``` 996 | 997 | # Lifecycle methods 998 | 999 | ## ![angular.js] AngularJS 1000 | 1001 | #### `$onInit()` 1002 | 1003 | Called when the component has been constructed and has its bindings have been initialized. 1004 | 1005 | #### `$postLink()` 1006 | 1007 | Called after the component and its children have been linked (mounted). 1008 | 1009 | #### `$onChanges(changes)` 1010 | 1011 | Called whenever one-way bindings are updated. 1012 | 1013 | #### `$doCheck()` 1014 | 1015 | Called on each turn of the digest cycle. 1016 | 1017 | #### `$onDestroy()` 1018 | 1019 | Called when the component (a controller with its containing scope) is being destroyed. 1020 | 1021 | :link: https://docs.angularjs.org/api/ng/service/$compile 1022 | 1023 | ### ![angular] Angular 1024 | 1025 | #### [`ngOnChanges()`](https://angular.io/guide/lifecycle-hooks#onchanges) 1026 | 1027 | Respond when Angular (re)sets data-bound input properties. The method receives a `SimpleChanges` object of current and previous property values. 1028 | 1029 | Called before `ngOnInit()` and whenever one or more data-bound input properties change. 1030 | 1031 | ```ts 1032 | export class PeekABooComponent extends PeekABoo implements OnChanges { 1033 | // only called for/if there is an @input variable set by parent. 1034 | ngOnChanges(changes: SimpleChanges) { 1035 | let changesMsgs: string[] = []; 1036 | for (let propName in changes) { 1037 | if (propName === 'name') { 1038 | let name = changes['name'].currentValue; 1039 | changesMsgs.push(`name ${this.verb} to "${name}"`); 1040 | } else { 1041 | changesMsgs.push(propName + ' ' + this.verb); 1042 | } 1043 | } 1044 | this.logIt(`OnChanges: ${changesMsgs.join('; ')}`); 1045 | this.verb = 'changed'; // next time it will be a change 1046 | } 1047 | } 1048 | ``` 1049 | 1050 | #### [`ngOnInit()`](https://angular.io/guide/lifecycle-hooks#oninit) 1051 | 1052 | Initialize the directive/component after Angular first displays the data-bound properties and sets the directive/component's input properties. 1053 | 1054 | Called once, after the first `ngOnChanges()`. 1055 | 1056 | ```ts 1057 | export class PeekABoo implements OnInit { 1058 | constructor(private logger: LoggerService) { } 1059 | 1060 | // implement OnInit's `ngOnInit` method 1061 | ngOnInit() { this.logIt(`OnInit`); } 1062 | 1063 | logIt(msg: string) { 1064 | this.logger.log(`#${nextId++} ${msg}`); 1065 | } 1066 | } 1067 | ``` 1068 | 1069 | #### [`ngDoCheck()`](https://angular.io/guide/lifecycle-hooks#docheck) 1070 | 1071 | Detect and act upon changes that Angular can't or won't detect on its own. 1072 | 1073 | Called during every change detection run, immediately after `ngOnChanges()` and `ngOnInit()`. 1074 | 1075 | ```ts 1076 | export class PeekABooComponent extends PeekABoo implements DoCheck { 1077 | ngDoCheck() { 1078 | this.logIt(`DoCheck`); 1079 | } 1080 | } 1081 | ``` 1082 | 1083 | #### [`ngAfterContentInit()`](https://angular.io/guide/lifecycle-hooks#aftercontent-hooks) 1084 | 1085 | Respond after Angular projects external content into the component's view. 1086 | Called once after the first `ngDoCheck()`. 1087 | 1088 | ```ts 1089 | export class PeekABooComponent extends PeekABoo implements AfterContentInit { 1090 | ngAfterContentInit() { this.logIt(`AfterContentInit`); } 1091 | } 1092 | ``` 1093 | 1094 | #### [`ngAfterContentChecked()`](https://angular.io/guide/lifecycle-hooks#aftercontent-hooks) 1095 | 1096 | Respond after Angular checks the content projected into the component. 1097 | 1098 | Called after the `ngAfterContentInit()` and every subsequent `ngDoCheck()` 1099 | 1100 | ```ts 1101 | export class PeekABooComponent extends PeekABoo implements AfterContentChecked { 1102 | // Beware! Called frequently! 1103 | // Called in every change detection cycle anywhere on the page 1104 | ngAfterContentChecked() { this.logIt(`AfterContentChecked`); } 1105 | } 1106 | ``` 1107 | 1108 | #### [`ngAfterViewInit()`](https://angular.io/guide/lifecycle-hooks#afterview) 1109 | 1110 | Respond after Angular initializes the component's views and child views. 1111 | 1112 | Called once after the first `ngAfterContentChecked()`. 1113 | 1114 | ```ts 1115 | export class AfterViewComponent implements AfterViewChecked, AfterViewInit { 1116 | ngAfterViewInit() { 1117 | // viewChild is set after the view has been initialized 1118 | this.logIt('AfterViewInit'); 1119 | this.doSomething(); 1120 | } 1121 | } 1122 | ``` 1123 | 1124 | #### [`ngAfterViewChecked()`](https://angular.io/guide/lifecycle-hooks#afterview) 1125 | 1126 | Respond after Angular checks the component's views and child views. 1127 | 1128 | Called after the `ngAfterViewInit` and every subsequent `ngAfterContentChecked()` 1129 | 1130 | ```ts 1131 | export class AfterViewComponent implements AfterViewChecked, AfterViewInit 1132 | ngAfterViewChecked() { 1133 | // viewChild is updated after the view has been checked 1134 | if (this.prevHero === this.viewChild.hero) { 1135 | this.logIt('AfterViewChecked (no change)'); 1136 | } else { 1137 | this.prevHero = this.viewChild.hero; 1138 | this.logIt('AfterViewChecked'); 1139 | this.doSomething(); 1140 | } 1141 | } 1142 | } 1143 | ``` 1144 | 1145 | #### [`ngOnDestroy()`](https://angular.io/guide/lifecycle-hooks#ondestroy) 1146 | 1147 | Cleanup just before Angular destroys the directive/component. Unsubscribe Observables and detach event handlers to avoid memory leaks. 1148 | 1149 | Called just before Angular destroys the directive/component. 1150 | 1151 | ```ts 1152 | @Directive({ 1153 | selector: '[destroyDirective]' 1154 | }) 1155 | 1156 | export class OnDestroyDirective implements OnDestroy { 1157 | sayHello: number; 1158 | constructor() { 1159 | this.sayHiya = window.setInterval(() => console.log('hello'), 1000); 1160 | } 1161 | ngOnDestroy() { 1162 | window.clearInterval(this.sayHiya); 1163 | } 1164 | } 1165 | ``` 1166 | 1167 | ## ![react] React 1168 | 1169 | #### [`componentWillMount()`](https://reactjs.org/docs/react-component.html#componentwillmount) 1170 | 1171 | Is invoked just before rendering. Modifying the state here won't trigger a re-render. 1172 | 1173 | #### [`componentDidMount()`](https://reactjs.org/docs/react-component.html#componentdidmount) 1174 | 1175 | Is invoked after render. Useful for the initialization that require DOM nodes. 1176 | 1177 | #### [`componentWillReceiveProps(nextProps)`](https://reactjs.org/docs/react-component.html#componentwillreceiveprops) 1178 | 1179 | Is only called after rendering, but before receiving new props. Because React may call this method without props changing, it is recommended to manually implement a check to see if there's a difference. 1180 | 1181 | #### [`shouldComponentUpdate(nextProps, nextState)`](https://reactjs.org/docs/react-component.html#shouldcomponentupdate) 1182 | 1183 | This method is called before receiving new props or a change of state. By default, it returns true which means re-rendering is triggered by any change. Modifying this method allows you to only re-render in intended scenarios. 1184 | 1185 | #### [`componentWillUpdate(nextProps, nextState)`](https://reactjs.org/docs/react-component.html#componentwillupdate) 1186 | 1187 | Is invoked whenever `shouldComponentUpdate` returns true before rendering. Note: You can't use `this.setState()` here. 1188 | 1189 | #### [`componentDidUpdate(prevProps, prevState)`](https://reactjs.org/docs/react-component.html#componentdidupdate) 1190 | 1191 | Is invoked after rendering, but not after the initial render. This method is useful for manipulating the DOM when updated. 1192 | 1193 | #### [`componentWillUnmount()`](https://reactjs.org/docs/react-component.html#componentwillunmount) 1194 | 1195 | Is invoked immediately before a component is unmounted and destroyed. Useful for resource cleanup. 1196 | 1197 | #### [`componentDidCatch(error,info)`](https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html) 1198 | 1199 | Is invoked when Javascript throws an error anywhere in the component's tree. Useful for catching errors, showing a fallback interface, and logging errors without breaking the entire application. 1200 | 1201 | ## ![vue] Vue.js 1202 | 1203 | #### [`beforeCreate`](https://vuejs.org/v2/api/#beforeCreate) 1204 | 1205 | Called synchronously immediately after the instance has been initialized, but before data observation and event/watcher setup. On every Vue instance lifecycle, `this` points to the vm instance itself. 1206 | 1207 | ```javascript 1208 | new Vue({ 1209 | beforeCreate: function () { 1210 | console.log('this method called before instance created') 1211 | } 1212 | }) 1213 | ``` 1214 | 1215 | #### [`created`](https://vuejs.org/v2/api/#created) 1216 | 1217 | Called synchronously after the instance is created. At this stage, the instance has finished processing the options, which means the following have been set up: data observation, computed properties, methods, watch/event callbacks. However, the mounting phase has not been started, and the `$el` property will not be available yet. 1218 | 1219 | ```javascript 1220 | new Vue({ 1221 | created: function () { 1222 | console.log('this method called after instance created') 1223 | } 1224 | }) 1225 | ``` 1226 | 1227 | #### [`beforeMount`](https://vuejs.org/v2/api/#beforeMount) 1228 | 1229 | Called right before the mounting begins: the render function is about to be called for the first time. 1230 | 1231 | _This hook is not called during server-side rendering._ 1232 | 1233 | ```javascript 1234 | new Vue({ 1235 | beforeMount: function () { 1236 | console.log('this method called before mounting an instance') 1237 | } 1238 | }) 1239 | ``` 1240 | 1241 | #### [`mounted`](https://vuejs.org/v2/api/#mounted) 1242 | 1243 | Called after the instance has been mounted, where [el](https://vuejs.org/v2/api/#el) is replaced by the newly created `vm.$el`. If the root instance is mounted to an in-document element, `vm.$el` will also be in-document when `mounted` is called. 1244 | 1245 | Note that `mounted` does not guarantee that all child components have also been mounted. If you want to wait until the entire view has been rendered, you can use [vm.$nextTick](https://vuejs.org/v2/api/#Vue-nextTick) inside of mounted: 1246 | 1247 | ```javascript 1248 | new Vue({ 1249 | mounted: function () { 1250 | this.$nextTick(function () { 1251 | // Code that will run only after the 1252 | // entire view has been rendered 1253 | }) 1254 | } 1255 | }) 1256 | ``` 1257 | 1258 | #### [`beforeUpdate`](https://vuejs.org/v2/api/#beforeUpdate) 1259 | 1260 | Called whenever the data changes, before the virtual DOM is re-rendered and patched. 1261 | 1262 | You can perform further state changes in this hook and they will not trigger additional re-renders. 1263 | 1264 | _This hook is not called during server-side rendering._ 1265 | 1266 | #### [`updated`](https://vuejs.org/v2/api/#updated) 1267 | 1268 | Called after a data change causes the virtual DOM to be re-rendered and patched. 1269 | 1270 | The component’s DOM will have been updated when this hook is called, so you can perform DOM-dependent operations here. However, in most cases you should avoid changing state inside the hook. 1271 | 1272 | Note that `updated` does not guarantee that all child components have also been re-rendered. If you want to wait until the entire view has been re-rendered, you can use [vm.$nextTick](https://vuejs.org/v2/api/#Vue-nextTick) inside of `updated`: 1273 | 1274 | ```javascript 1275 | updated: function () { 1276 | this.$nextTick(function () { 1277 | // Code that will run only after the 1278 | // entire view has been re-rendered 1279 | }) 1280 | } 1281 | ``` 1282 | 1283 | #### [`activated`](https://vuejs.org/v2/api/#activated) 1284 | 1285 | Called when a kept-alive component is activated. 1286 | 1287 | _This hook is not called during server-side rendering._ 1288 | 1289 | #### [`deactivated`](https://vuejs.org/v2/api/#deactivated) 1290 | 1291 | Called when a kept-alive component is deactivated. 1292 | 1293 | _This hook is not called during server-side rendering._ 1294 | 1295 | #### [`beforeDestroy`](https://vuejs.org/v2/api/#beforeDestroy) 1296 | 1297 | Called right before a Vue instance is destroyed. At this stage the instance is still fully functional. 1298 | 1299 | _This hook is not called during server-side rendering._ 1300 | 1301 | #### [`destroyed`](https://vuejs.org/v2/api/#destroyed) 1302 | 1303 | Called after a Vue instance has been destroyed. When this hook is called, all directives of the Vue instance have been unbound, all event listeners have been removed, and all child Vue instances have also been destroyed. 1304 | 1305 | _This hook is not called during server-side rendering._ 1306 | 1307 | 1308 | #### [`errorCaptured`](https://vuejs.org/v2/api/#errorCaptured) 1309 | 1310 | Called when an error from any descendent component is captured. 1311 | 1312 | # Conditional rendering 1313 | 1314 | ## ![angular.js] AngularJS 1315 | 1316 | Angularjs 1.x has three ways to perform conditional rendering: `ng-if`, `ng-switch` and `ng-hide/ng-show`. 1317 | 1318 | ```js 1319 | export class RegistrationController { 1320 | registrationCompleted = false; 1321 | displaySpecialOffer = false; 1322 | displayStatus = 'Registered'; 1323 | } 1324 | ``` 1325 | ```html 1326 |
1327 | 1328 |
1329 | 1330 |
1331 |
1332 | 1333 |
1334 |
1335 | 1336 |
1337 | 1338 |
1339 |
1340 | 1341 |
1342 | ``` 1343 | 1344 | ## ![angular] Angular 1345 | 1346 | Since Angular 4.0.0, alongside standard `ngIf`, it is possible to use `ngIf;else` or `ngIf;then;else` using `` with an alias `#aliasName`. 1347 | 1348 | ```ts 1349 | import { Component } from '@angular/core'; 1350 | 1351 | @Component({ 1352 | selector: 'registration', 1353 | template: '', 1354 | }) 1355 | export class RegistrationComponent { 1356 | registrationCompleted: boolean = false; 1357 | displaySpecialOffer: boolean = false; 1358 | } 1359 | ``` 1360 | 1361 | ```html 1362 |
1363 | 1364 |
1365 | 1366 |
1367 | 1368 |
1369 | 1370 | 1371 | 1372 | 1373 | ``` 1374 | 1375 | :link: https://angular.io/api/common/NgIf 1376 | 1377 | ## ![react] React 1378 | 1379 | The most common approach to conditional rendering is by using the ternary operator: 1380 | `{ condition ? : null }` 1381 | 1382 | ```jsx 1383 | import React, { Component } from 'react'; 1384 | import PropTypes from 'prop-types'; 1385 | 1386 | export class Registration extends Component { 1387 | state = { 1388 | registrationCompleted: false, 1389 | }; 1390 | 1391 | propTypes = { 1392 | displaySpecialOffer: PropTypes.bool, 1393 | } 1394 | 1395 | render() { 1396 | return ( 1397 |
1398 | { this.props.displaySpecialOffer ? : null } 1399 | 1400 | { this.state.registrationCompleted ? ( 1401 | 1402 | ) : ( 1403 | 1404 | ) } 1405 |
1406 | ); 1407 | } 1408 | } 1409 | ``` 1410 | 1411 | ## ![vue] Vue.js 1412 | 1413 | Vue.js has three directives to perform conditional rendering: `v-if`, `v-else-if` and `v-else`. 1414 | 1415 | ```html 1416 | 1428 | ``` 1429 | 1430 | :link: https://vuejs.org/v2/guide/conditional.html 1431 | 1432 | # Lists 1433 | 1434 | ## ![angular.js] AngularJS 1435 | 1436 | [ngRepeat](https://docs.angularjs.org/api/ng/directive/ngRepeat) 1437 | 1438 | ```js 1439 | export class BookListComponentCtrl { 1440 | constructor() { 1441 | this.books = [ 1442 | { 1443 | id: 1, 1444 | title: 'Eloquent JavaScript', 1445 | author: 'Marijn Haverbeke', 1446 | }, 1447 | { 1448 | id: 2, 1449 | title: 'JavaScript: The Good Parts', 1450 | author: 'Douglas Crockford', 1451 | }, 1452 | { 1453 | id: 3, 1454 | title: 'JavaScript: The Definitive Guide', 1455 | author: 'David Flanagan', 1456 | }, 1457 | ]; 1458 | } 1459 | } 1460 | ``` 1461 | 1462 | ```html 1463 |
    1464 |
  • 1465 | {{ book.title }} by {{ book.author }} 1466 |
  • 1467 |
1468 | ``` 1469 | 1470 | ## ![angular] Angular 1471 | 1472 | [ngFor](https://angular.io/guide/template-syntax#ngfor) 1473 | 1474 | ```ts 1475 | export interface Book { 1476 | id: number; 1477 | title: string; 1478 | author: string; 1479 | } 1480 | ``` 1481 | 1482 | ```ts 1483 | import { Component } from '@angular/core'; 1484 | import { Book } from './book.interface'; 1485 | 1486 | @Component({ 1487 | selector: 'book-list', 1488 | template: ` 1489 |
    1490 |
  • 1491 | {{ book.title }} by {{ book.author }} 1492 |
  • 1493 |
1494 | ` 1495 | }) 1496 | export class BookListComponent { 1497 | this.books: Book[] = [ 1498 | { 1499 | id: 1, 1500 | title: "Eloquent JavaScript", 1501 | author: "Marijn Haverbeke" 1502 | }, 1503 | { 1504 | id: 2, 1505 | title: "JavaScript: The Good Parts", 1506 | author: "Douglas Crockford" 1507 | }, 1508 | { 1509 | id: 3, 1510 | title: "JavaScript: The Definitive Guide", 1511 | author: "David Flanagan" 1512 | } 1513 | ]; 1514 | 1515 | trackById(book) { 1516 | return book.id; 1517 | } 1518 | } 1519 | ``` 1520 | 1521 | ## ![react] React 1522 | 1523 | [Lists and Keys](https://reactjs.org/docs/lists-and-keys.html) 1524 | 1525 | ```jsx 1526 | import React, { Component } from 'react'; 1527 | 1528 | export class BookList extends Component { 1529 | state = { 1530 | books: [ 1531 | { 1532 | id: 1, 1533 | title: 'Eloquent JavaScript', 1534 | author: 'Marijn Haverbeke', 1535 | }, 1536 | { 1537 | id: 2, 1538 | title: 'JavaScript: The Good Parts', 1539 | author: 'Douglas Crockford', 1540 | }, 1541 | { 1542 | id: 3, 1543 | title: 'JavaScript: The Definitive Guide', 1544 | author: 'David Flanagan', 1545 | }, 1546 | ], 1547 | }; 1548 | 1549 | render() { 1550 | const { books } = this.state; 1551 | 1552 | return ( 1553 |
    1554 | { books.map(book => { 1555 | return ( 1556 |
  • 1557 | { book.title } by { book.author } 1558 |
  • 1559 | ); 1560 | }) } 1561 |
1562 | ); 1563 | } 1564 | } 1565 | ``` 1566 | 1567 | ## ![vue] Vue.js 1568 | 1569 | ```html 1570 | 1577 | ``` 1578 | 1579 | ```js 1580 | export default { 1581 | data() { 1582 | return { 1583 | books: [ 1584 | { 1585 | id: 1, 1586 | title: 'Eloquent JavaScript', 1587 | author: 'Marijn Haverbeke', 1588 | }, 1589 | { 1590 | id: 2, 1591 | title: 'JavaScript: The Good Parts', 1592 | author: 'Douglas Crockford', 1593 | }, 1594 | { 1595 | id: 3, 1596 | title: 'JavaScript: The Definitive Guide', 1597 | author: 'David Flanagan', 1598 | }, 1599 | ], 1600 | }; 1601 | }, 1602 | }; 1603 | ``` 1604 | 1605 | # Filters 1606 | 1607 | ## ![angular.js] AngularJS 1608 | 1609 | AngularJS provides filters to transform data. There are several [built-in filters](https://docs.angularjs.org/api/ng/filter) available and you can make your own custom filters as well. 1610 | 1611 | Filters can be applied to view templates using the following syntax: 1612 | 1613 | ```html 1614 |

{{ price | currency }}

1615 | ``` 1616 | 1617 | Chaining of filters is also possible: 1618 | 1619 | ```html 1620 |

{{ name | uppercase | appendTitle }}

1621 | ``` 1622 | 1623 | Custom Filters: 1624 | 1625 | ```js 1626 | angular.module('app', []) 1627 | .filter('reverse', function() { 1628 | return (input = '', uppercase = false) => { 1629 | const out = input.split('').reverse().join(''); 1630 | 1631 | return uppercase ? out.toUpperCase() : out; 1632 | }; 1633 | }); 1634 | ``` 1635 | 1636 | :link: https://docs.angularjs.org/guide/filter 1637 | 1638 | ## ![angular] Angular 1639 | 1640 | In Angular filters are called [pipes](https://angular.io/guide/pipes). The built-in pipes available in Angular are: DatePipe, UpperCasePipe, LowerCasePipe, CurrencyPipe, and PercentPipe. 1641 | 1642 | Apart from the built-in pipes, you can create your own, custom pipes. 1643 | 1644 | Create a custom pipe: 1645 | this pipe transforms a given URL to a safe style URL, that way it can be used in hyperlinks, for example or 1665 | ``` 1666 | 1667 | Note: `[src]` above is an input to the component which 'lives' above the `iframe`. 1668 | 1669 | :link: https://angular.io/guide/pipes 1670 | 1671 | ## ![react] React 1672 | 1673 | React doesn't provide any specific filtering mechanism. This can simply be achieved by using ordinary JavaScript functions: 1674 | 1675 | ```jsx 1676 | export function reverse(input = '', uppercase = false) { 1677 | const out = input.split('').reverse().join(''); 1678 | 1679 | return uppercase ? out.toUpperCase() : out; 1680 | } 1681 | ``` 1682 | 1683 | ```jsx 1684 | import React, { Component } from 'react'; 1685 | import { reverse } from 'utils'; 1686 | 1687 | export class App extends Component { 1688 | render() { 1689 | return ( 1690 |
1691 | { reverse(this.props.input) } 1692 |
1693 | ); 1694 | } 1695 | } 1696 | ``` 1697 | 1698 | Filter chaining can be achieved using function composition: 1699 | 1700 | ```jsx 1701 |
1702 | { truncate(reverse(this.props.input)) } 1703 |
; 1704 | ``` 1705 | 1706 | ## ![vue] Vue.js 1707 | 1708 | Vue.js provides filters to allow for simple text formatting. The filter utilizes the `|` character which is appended to the expression followed by the filter's name. Vue does not come with any pre-built filters. 1709 | 1710 | Filters can be used within mustache interpolations: 1711 | 1712 | ```html 1713 |

{{ name | lowercase }}

1714 | ``` 1715 | 1716 | Filters can also be used within the `v-bind` directive: 1717 | 1718 | ```html 1719 |
1720 | ``` 1721 | 1722 | When creating filters, the function always receives the expression's value: 1723 | 1724 | ```js 1725 | new Vue({ 1726 | el: '#app', 1727 | template: '

{{ message | lowercase }}

', 1728 | filters: { 1729 | lowercase(word) { 1730 | return word.toLowerCase(); 1731 | }, 1732 | }, 1733 | data: { 1734 | message: 'Hello World', 1735 | }, 1736 | }); 1737 | ``` 1738 | 1739 | Filters can also be chained: 1740 | 1741 | ```html 1742 |

{{ description | lowercase | truncate }}

1743 | ``` 1744 | 1745 | Filters can be created locally like the above example and only be available within that component. Filters can also be declared globally: 1746 | 1747 | ```js 1748 | Vue.filter('lowercase', word => word.toLowerCase()); 1749 | ``` 1750 | 1751 | For global filters to work, they should be declared before the Vue instance. 1752 | 1753 | :link: https://vuejs.org/v2/guide/filters.html 1754 | 1755 | # Child nodes 1756 | 1757 | ## ![angular.js] AngularJS 1758 | 1759 | Inside a [component](https://docs.angularjs.org/guide/component), we have access to the child node by injecting `$element` to the controller. This object contains a [jqLite](https://docs.angularjs.org/api/ng/function/angular.element) wrapped instance of the DOM element. Accessing `$element[0]` will return the bare DOM element. 1760 | 1761 | Transclusion is also supported - using `ng-transclude` (See [Transclusion and Containment](#transclusion-and-containment) section). 1762 | 1763 | ```js 1764 | class TextInputController { 1765 | constructor($element) { 1766 | 'ngInject'; 1767 | 1768 | this.$element = $element; 1769 | } 1770 | 1771 | // The $element can be used after the link stage 1772 | $postLink() { 1773 | const input = this.$element.find('input'); 1774 | input.on('change', console.log); 1775 | } 1776 | } 1777 | 1778 | const component = { 1779 | controller: TextInputController, 1780 | template: ` 1781 |
1782 | 1783 |
1784 | `, 1785 | }; 1786 | ``` 1787 | 1788 | ## ![angular] Angular 1789 | 1790 | Angular provides two ways to deal with child nodes: `ViewChild` and `ContentChild`. They both have the same purpose, but there are different use cases for them. 1791 | 1792 | * [`ViewChild`](https://angular.io/api/core/ViewChild) works with the **internal DOM of your component**, defined by you in the component's template. You have to use the `@ViewChild` decorator to get the DOM element reference. 1793 | 1794 | * [`ContentChild`](https://angular.io/api/core/ContentChild) works with de **DOM supplied to your component by its end-user** (See [Transclusion and Containment](#transclusion-and-containment)). You have to use the `@ContentChild` decorator to get the DOM element reference. 1795 | 1796 | ```ts 1797 | import { 1798 | Component, 1799 | Input, 1800 | ViewChild, 1801 | ContentChild, 1802 | AfterViewInit, 1803 | AfterContentInit, 1804 | } from '@angular/core'; 1805 | 1806 | @Component({ 1807 | selector: 'child', 1808 | template: ` 1809 |

Hello, I'm your child #{{ number }}!

1810 | `, 1811 | }) 1812 | export class Child { 1813 | @Input() number: number; 1814 | } 1815 | 1816 | @Component({ 1817 | selector: 'parent', 1818 | template: ` 1819 | 1820 | 1821 | `, 1822 | }) 1823 | export class Parent implements AfterViewInit, AfterContentInit { 1824 | @ViewChild(Child) viewChild: Child; 1825 | @ContentChild(Child) contentChild: Child; 1826 | 1827 | ngAfterViewInit() { 1828 | // ViewChild element is only available when the 1829 | // ngAfterViewInit lifecycle hook is reached. 1830 | console.log(this.viewChild); 1831 | } 1832 | 1833 | ngAfterContentInit() { 1834 | // ContentChild element is only available when the 1835 | // ngAfterContentInit lifecycle hook is reached. 1836 | console.log(this.contentChild); 1837 | } 1838 | } 1839 | 1840 | @Component({ 1841 | selector: 'app', 1842 | template: ` 1843 | 1844 | 1845 | 1846 | 1847 | `, 1848 | }) 1849 | export class AppComponent { } 1850 | ``` 1851 | 1852 | `ViewChild` and `ContentChild` only work with a **single** DOM element. You can use [`ViewChildren`](https://angular.io/api/core/ViewChildren) and [`ContentChildren`](https://angular.io/api/core/ContentChildren) in order to get **multiple elements**. Both return the elements wrapped in a [`QueryList`](https://angular.io/api/core/QueryList). 1853 | 1854 | ## ![react] React 1855 | 1856 | In React, we have two options to deal with child nodes: [`refs`](https://reactjs.org/docs/refs-and-the-dom) and [`children`](https://reactjs.org/docs/jsx-in-depth.html#children-in-jsx). With `refs`, you have access to the real DOM element. The `children` property lets you manipulate the underlying [React elements](https://reactjs.org/blog/2015/12/18/react-components-elements-and-instances.html). 1857 | 1858 | #### refs 1859 | 1860 | `ref` is a special attribute we can pass to a React element that receives a callback and call it with the corresponding DOM node. 1861 | 1862 | ```jsx 1863 | import React, { Component } from 'react'; 1864 | 1865 | // In order to access child nodes from parents, we can pass the `ref` callback 1866 | // to the children as props. 1867 | const TextInput = ({ inputRef }) => ( 1868 |
1869 | 1870 |
1871 | ); 1872 | 1873 | class Parent extends Component { 1874 | componentDidMount() { 1875 | // Refs are only executed after mounting and unmounting. Now `this.textInput` 1876 | // references a real DOM node. So, we can use the raw DOM API 1877 | // (to focus the input, for example) 1878 | this.textInput.focus(); 1879 | } 1880 | 1881 | render() { 1882 | // The child's `inputRef` prop receives the `ref` callback. 1883 | // We can use the callback to store the DOM element in an instance variable. 1884 | return ( 1885 |
1886 | 1887 | { this.textInput = node; }} /> 1889 |
1890 | ); 1891 | } 1892 | } 1893 | 1894 | ``` 1895 | 1896 | #### children 1897 | 1898 | `children` is a special prop available in all React component instances. You can use it to control _how_ and _where_ the underlying React elements will be rendered. 1899 | 1900 | ```jsx 1901 | import React, { Component } from 'react'; 1902 | 1903 | // children is just a prop. In this case, the value of `children` will be 1904 | // what you pass to the component as a child node. 1905 | const Heading = ({ children }) => ( 1906 |

1907 | {children} 1908 |

1909 | ); 1910 | 1911 | // `this.props.children` refers to whatever is a valid node inside the element. 1912 | class Layout extends Component { 1913 | render() { 1914 | return ( 1915 |
1916 | {this.props.children} 1917 |
1918 | ); 1919 | } 1920 | } 1921 | 1922 | const App = () => ( 1923 |
1924 | I am the child! 1925 | 1926 | We are 1927 | {'the'} 1928 | Children! 1929 | 1930 |
1931 | ); 1932 | ``` 1933 | 1934 | ## ![vue] Vue.js 1935 | 1936 | > TODO 1937 | 1938 | # Transclusion and Containment 1939 | 1940 | ## Basic 1941 | 1942 | ## ![angular.js] AngularJS 1943 | 1944 | ```js 1945 | angular.module('app.layout', []) 1946 | .component('layout', { 1947 | bindings: { 1948 | theme: '@', 1949 | }, 1950 | controller: LayoutController, 1951 | transclude: true, 1952 | template: ` 1953 |
1954 | 1955 |
1956 | `, 1957 | }).component('pageContent', { 1958 | template: '
Some content
', 1959 | }).component('pageFooter', { 1960 | template: '
Some content
', 1961 | }); 1962 | ``` 1963 | ```html 1964 | 1965 | 1966 | 1967 | 1968 | ``` 1969 | 1970 | ## ![angular] Angular 1971 | 1972 | ```ts 1973 | @Component({ 1974 | selector: 'layout', 1975 | template: ` 1976 |
1977 | 1978 |
1979 | `, 1980 | }) 1981 | export class Layout {} 1982 | 1983 | @Component({ 1984 | selector: 'page-content', 1985 | template: '
Some content
', 1986 | }) 1987 | export class PageContent {} 1988 | 1989 | @Component({ 1990 | selector: 'page-footer', 1991 | template: '
Some content
', 1992 | }) 1993 | export class PageFooter {} 1994 | ``` 1995 | ```html 1996 | 1997 | 1998 | 1999 | 2000 | ``` 2001 | 2002 | ## ![react] React 2003 | 2004 | ```jsx 2005 | const Layout = ({ children, theme }) => ( 2006 |
2007 | {children} 2008 |
2009 | ); 2010 | 2011 | const PageContent = () => ( 2012 |
Some content
2013 | ); 2014 | 2015 | const PageFooter = () => ( 2016 |
Some content
2017 | ); 2018 | 2019 | 2020 | 2021 | 2022 | ; 2023 | ``` 2024 | 2025 | ## ![vue] Vue.js 2026 | 2027 | > TODO 2028 | 2029 | ## Multiple slots 2030 | 2031 | ## ![angular.js] AngularJS 2032 | 2033 | ```js 2034 | angular.module('app.layout', []) 2035 | .component('landingSection', { 2036 | bindings: {}, 2037 | controller: LandingSectionController, 2038 | transclude: { 2039 | contentSlot: '?content', // '?' indicates an optional slot 2040 | iconSlot: '?icon', 2041 | }, 2042 | template: ` 2043 |
2044 | 2045 |
2046 | This is the default value 2047 | 2048 |
2049 | `, 2050 | }).component('pageContent', { 2051 | template: '
Some content
', 2052 | }); 2053 | ``` 2054 | ```html 2055 |
2056 |

Page title

2057 | 2058 | 2059 | 2060 |
2061 | ``` 2062 | 2063 | ## ![angular] Angular 2064 | 2065 | > TODO 2066 | 2067 | ## ![react] React 2068 | 2069 | ```jsx 2070 | const Layout = ({ children, theme }) => ( 2071 |
2072 |
{children.header}
2073 |
{children.content}
2074 |
{children.footer}
2075 |
2076 | ); 2077 | 2078 | const Header = () => ( 2079 |

My Header

2080 | ); 2081 | 2082 | const Footer = () => ( 2083 |
My Footer
2084 | ); 2085 | 2086 | const Content = () => ( 2087 |
Some fancy content
2088 | ); 2089 | 2090 | 2091 | {{ 2092 | header:
, 2093 | content: , 2094 | footer: